forked from ZyntariHQ/Invoisio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.service.ts
More file actions
121 lines (105 loc) · 3.97 KB
/
Copy pathauth.service.ts
File metadata and controls
121 lines (105 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { Injectable, BadRequestException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../../infra/prisma/prisma.service';
import { ConnectWalletDto } from './dto/connect-wallet.dto';
import { VerifySignatureDto } from './dto/verify-signature.dto';
import { randomBytes } from 'crypto';
import { SiweMessage } from 'siwe';
@Injectable()
export class AuthService {
constructor(
private jwtService: JwtService,
private prisma: PrismaService,
) {}
async requestNonce(walletAddress: string) {
const nonce = randomBytes(16).toString('hex');
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
const user = await this.prisma.user.upsert({
where: { walletAddress },
update: { nonce, nonceExpiresAt: expiresAt },
create: {
walletAddress,
nonce,
nonceExpiresAt: expiresAt,
},
});
// Return EVM chain details for SIWE (Base chain defaults)
const chainId = parseInt(process.env.EVM_CHAIN_ID || '84532', 10);
const domain = 'invoisio.app';
return { nonce, expiresAt: user.nonceExpiresAt, chainId, domain };
}
async connectWallet(connectWalletDto: ConnectWalletDto) {
// Require prior nonce issuance
const existing = await this.prisma.user.findUnique({ where: { walletAddress: connectWalletDto.walletAddress } });
if (!existing || !existing.nonce) {
throw new BadRequestException('Nonce required. Request nonce first.');
}
// Verify signature before connecting
await this.verifySignature({
walletAddress: connectWalletDto.walletAddress,
signature: connectWalletDto.signature,
message: connectWalletDto.message,
});
const user = await this.prisma.user.upsert({
where: { walletAddress: connectWalletDto.walletAddress },
update: { nonce: null, nonceExpiresAt: null },
create: {
walletAddress: connectWalletDto.walletAddress,
},
});
const payload = { sub: user.id, walletAddress: user.walletAddress };
return {
token: this.jwtService.sign(payload),
user: {
id: user.id,
walletAddress: user.walletAddress,
},
};
}
async disconnectWallet() {
return { success: true };
}
async getWalletStatus() {
return { connected: true };
}
async verifySignature(verifySignatureDto: VerifySignatureDto) {
const { walletAddress, signature, message } = verifySignatureDto;
const user = await this.prisma.user.findUnique({ where: { walletAddress } });
if (!user || !user.nonce || !user.nonceExpiresAt) {
throw new BadRequestException('Nonce not found. Request nonce first.');
}
if (new Date(user.nonceExpiresAt).getTime() < Date.now()) {
throw new BadRequestException('Nonce expired. Request a new nonce.');
}
// Verify SIWE message signature (EVM / Base chain)
let parsed: SiweMessage;
try {
parsed = new SiweMessage(message);
} catch (e) {
throw new BadRequestException('Invalid SIWE message');
}
// Ensure nonce matches the one issued by the server
if (!parsed.nonce || parsed.nonce !== user.nonce) {
throw new BadRequestException('Nonce mismatch');
}
// Optional: enforce chainId if present
const expectedChainId = parseInt(process.env.EVM_CHAIN_ID || '84532', 10);
if (parsed.chainId && parsed.chainId !== expectedChainId) {
throw new BadRequestException('Invalid chain for signature');
}
const verification = await parsed.verify({ signature });
if (!verification.success) {
throw new BadRequestException('Signature invalid');
}
// Ensure address matches the walletAddress provided
if (parsed.address?.toLowerCase() !== walletAddress.toLowerCase()) {
throw new BadRequestException('Address mismatch');
}
// Clear nonce on success to prevent replay
await this.prisma.user.update({
where: { walletAddress },
data: { nonce: null, nonceExpiresAt: null },
});
return { valid: true };
}
}