@revealui/auth 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/useMFA.d.ts +83 -0
- package/dist/react/useMFA.d.ts.map +1 -0
- package/dist/react/useMFA.js +182 -0
- package/dist/react/usePasskey.d.ts +88 -0
- package/dist/react/usePasskey.d.ts.map +1 -0
- package/dist/react/usePasskey.js +203 -0
- package/dist/react/useSession.d.ts.map +1 -1
- package/dist/react/useSession.js +16 -5
- package/dist/react/useSignIn.d.ts +9 -3
- package/dist/react/useSignIn.d.ts.map +1 -1
- package/dist/react/useSignIn.js +32 -10
- package/dist/react/useSignOut.d.ts.map +1 -1
- package/dist/react/useSignUp.d.ts.map +1 -1
- package/dist/react/useSignUp.js +25 -9
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +75 -4
- package/dist/server/brute-force.d.ts +10 -1
- package/dist/server/brute-force.d.ts.map +1 -1
- package/dist/server/brute-force.js +17 -3
- package/dist/server/errors.d.ts.map +1 -1
- package/dist/server/index.d.ts +16 -6
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +11 -5
- package/dist/server/magic-link.d.ts +52 -0
- package/dist/server/magic-link.d.ts.map +1 -0
- package/dist/server/magic-link.js +111 -0
- package/dist/server/mfa.d.ts +87 -0
- package/dist/server/mfa.d.ts.map +1 -0
- package/dist/server/mfa.js +263 -0
- package/dist/server/oauth.d.ts +37 -0
- package/dist/server/oauth.d.ts.map +1 -1
- package/dist/server/oauth.js +135 -3
- package/dist/server/passkey.d.ts +132 -0
- package/dist/server/passkey.d.ts.map +1 -0
- package/dist/server/passkey.js +257 -0
- package/dist/server/password-reset.d.ts +15 -0
- package/dist/server/password-reset.d.ts.map +1 -1
- package/dist/server/password-reset.js +44 -1
- package/dist/server/password-validation.d.ts.map +1 -1
- package/dist/server/providers/github.d.ts.map +1 -1
- package/dist/server/providers/github.js +18 -2
- package/dist/server/providers/google.d.ts.map +1 -1
- package/dist/server/providers/google.js +18 -2
- package/dist/server/providers/vercel.d.ts.map +1 -1
- package/dist/server/providers/vercel.js +18 -2
- package/dist/server/rate-limit.d.ts +10 -1
- package/dist/server/rate-limit.d.ts.map +1 -1
- package/dist/server/rate-limit.js +61 -43
- package/dist/server/session.d.ts +48 -1
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +125 -6
- package/dist/server/signed-cookie.d.ts +32 -0
- package/dist/server/signed-cookie.d.ts.map +1 -0
- package/dist/server/signed-cookie.js +67 -0
- package/dist/server/storage/database.d.ts +1 -1
- package/dist/server/storage/database.d.ts.map +1 -1
- package/dist/server/storage/database.js +15 -7
- package/dist/server/storage/in-memory.d.ts.map +1 -1
- package/dist/server/storage/in-memory.js +7 -7
- package/dist/server/storage/index.d.ts +11 -3
- package/dist/server/storage/index.d.ts.map +1 -1
- package/dist/server/storage/index.js +18 -4
- package/dist/server/storage/interface.d.ts +1 -1
- package/dist/server/storage/interface.d.ts.map +1 -1
- package/dist/server/storage/interface.js +1 -1
- package/dist/types.d.ts +20 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/utils/database.d.ts.map +1 -1
- package/dist/utils/database.js +9 -2
- package/package.json +26 -8
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn Passkey Module
|
|
3
|
+
*
|
|
4
|
+
* Implements passkey registration, authentication, and management using
|
|
5
|
+
* @simplewebauthn/server v13. Passkeys enable passwordless authentication
|
|
6
|
+
* via biometrics, security keys, or platform authenticators.
|
|
7
|
+
*
|
|
8
|
+
* @see https://simplewebauthn.dev/
|
|
9
|
+
*/
|
|
10
|
+
import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, VerifiedRegistrationResponse, WebAuthnCredential } from '@simplewebauthn/server';
|
|
11
|
+
export interface PasskeyConfig {
|
|
12
|
+
/** Maximum passkeys per user (default: 10) */
|
|
13
|
+
maxPasskeysPerUser: number;
|
|
14
|
+
/** Challenge TTL in ms (default: 5 minutes) */
|
|
15
|
+
challengeTtlMs: number;
|
|
16
|
+
/** Relying Party ID — domain name (default: 'localhost') */
|
|
17
|
+
rpId: string;
|
|
18
|
+
/** Relying Party name — user-visible (default: 'RevealUI') */
|
|
19
|
+
rpName: string;
|
|
20
|
+
/** Expected origin(s) for verification (default: 'http://localhost:4000') */
|
|
21
|
+
origin: string | string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function configurePasskey(overrides: Partial<PasskeyConfig>): void;
|
|
24
|
+
export declare function resetPasskeyConfig(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Generate WebAuthn registration options for a user.
|
|
27
|
+
*
|
|
28
|
+
* The returned object should be passed to the browser's navigator.credentials.create().
|
|
29
|
+
* The challenge is embedded in the options and should be stored server-side
|
|
30
|
+
* for verification.
|
|
31
|
+
*
|
|
32
|
+
* @param userId - User's database ID
|
|
33
|
+
* @param userEmail - User's email (used as userName in WebAuthn)
|
|
34
|
+
* @param existingCredentialIds - Credential IDs to exclude (prevent re-registration)
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateRegistrationChallenge(userId: string, userEmail: string, existingCredentialIds?: string[]): Promise<PublicKeyCredentialCreationOptionsJSON>;
|
|
37
|
+
/**
|
|
38
|
+
* Verify a WebAuthn registration response from the browser.
|
|
39
|
+
*
|
|
40
|
+
* @param response - The RegistrationResponseJSON from the browser
|
|
41
|
+
* @param expectedChallenge - The challenge from generateRegistrationChallenge
|
|
42
|
+
* @param expectedOrigin - Override origin (defaults to config.origin)
|
|
43
|
+
* @returns Verified registration data including credential info
|
|
44
|
+
* @throws If verification fails
|
|
45
|
+
*/
|
|
46
|
+
export declare function verifyRegistration(response: RegistrationResponseJSON, expectedChallenge: string, expectedOrigin?: string | string[]): Promise<VerifiedRegistrationResponse>;
|
|
47
|
+
/**
|
|
48
|
+
* Store a verified passkey credential in the database.
|
|
49
|
+
*
|
|
50
|
+
* Enforces the per-user passkey limit before insertion.
|
|
51
|
+
*
|
|
52
|
+
* @param userId - User's database ID
|
|
53
|
+
* @param credential - Verified credential from verifyRegistration
|
|
54
|
+
* @param deviceName - Optional user-friendly name (e.g., "MacBook Pro Touch ID")
|
|
55
|
+
* @returns The stored passkey record
|
|
56
|
+
*/
|
|
57
|
+
export declare function storePasskey(userId: string, credential: {
|
|
58
|
+
id: string;
|
|
59
|
+
publicKey: Uint8Array;
|
|
60
|
+
counter: number;
|
|
61
|
+
transports?: string[];
|
|
62
|
+
aaguid?: string;
|
|
63
|
+
backedUp?: boolean;
|
|
64
|
+
}, deviceName?: string): Promise<{
|
|
65
|
+
id: string;
|
|
66
|
+
userId: string;
|
|
67
|
+
credentialId: string;
|
|
68
|
+
deviceName: string | null;
|
|
69
|
+
createdAt: Date;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Generate WebAuthn authentication options.
|
|
73
|
+
*
|
|
74
|
+
* The returned object should be passed to the browser's navigator.credentials.get().
|
|
75
|
+
*
|
|
76
|
+
* @param allowCredentials - Optional list of credential IDs to allow
|
|
77
|
+
*/
|
|
78
|
+
export declare function generateAuthenticationChallenge(allowCredentials?: {
|
|
79
|
+
id: string;
|
|
80
|
+
transports?: string[];
|
|
81
|
+
}[]): Promise<PublicKeyCredentialRequestOptionsJSON>;
|
|
82
|
+
/**
|
|
83
|
+
* Verify a WebAuthn authentication response and update the credential counter.
|
|
84
|
+
*
|
|
85
|
+
* @param response - The AuthenticationResponseJSON from the browser
|
|
86
|
+
* @param credential - The stored credential (id, publicKey, counter)
|
|
87
|
+
* @param expectedChallenge - The challenge from generateAuthenticationChallenge
|
|
88
|
+
* @param expectedOrigin - Override origin (defaults to config.origin)
|
|
89
|
+
* @returns Verification result with new counter value
|
|
90
|
+
*/
|
|
91
|
+
export declare function verifyAuthentication(response: AuthenticationResponseJSON, credential: WebAuthnCredential, expectedChallenge: string, expectedOrigin?: string | string[]): Promise<{
|
|
92
|
+
verified: boolean;
|
|
93
|
+
newCounter: number;
|
|
94
|
+
}>;
|
|
95
|
+
/**
|
|
96
|
+
* List all passkeys for a user (safe for client — excludes publicKey and counter).
|
|
97
|
+
*/
|
|
98
|
+
export declare function listPasskeys(userId: string): Promise<{
|
|
99
|
+
id: string;
|
|
100
|
+
credentialId: string;
|
|
101
|
+
deviceName: string | null;
|
|
102
|
+
backedUp: boolean;
|
|
103
|
+
createdAt: Date;
|
|
104
|
+
lastUsedAt: Date | null;
|
|
105
|
+
}[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Delete a passkey. Blocks deletion if it's the user's last sign-in method.
|
|
108
|
+
*
|
|
109
|
+
* @param userId - User's database ID
|
|
110
|
+
* @param passkeyId - The passkey record ID to delete
|
|
111
|
+
* @throws If this is the user's only sign-in method
|
|
112
|
+
*/
|
|
113
|
+
export declare function deletePasskey(userId: string, passkeyId: string): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Rename a passkey's device name.
|
|
116
|
+
*
|
|
117
|
+
* @param userId - User's database ID
|
|
118
|
+
* @param passkeyId - The passkey record ID to rename
|
|
119
|
+
* @param name - New friendly name
|
|
120
|
+
*/
|
|
121
|
+
export declare function renamePasskey(userId: string, passkeyId: string, name: string): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Count a user's sign-in credentials (passkeys + password).
|
|
124
|
+
*
|
|
125
|
+
* @param userId - User's database ID
|
|
126
|
+
* @returns Passkey count and whether user has a password set
|
|
127
|
+
*/
|
|
128
|
+
export declare function countUserCredentials(userId: string): Promise<{
|
|
129
|
+
passkeyCount: number;
|
|
130
|
+
hasPassword: boolean;
|
|
131
|
+
}>;
|
|
132
|
+
//# sourceMappingURL=passkey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passkey.d.ts","sourceRoot":"","sources":["../../src/server/passkey.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebAuthn Passkey Module
|
|
3
|
+
*
|
|
4
|
+
* Implements passkey registration, authentication, and management using
|
|
5
|
+
* @simplewebauthn/server v13. Passkeys enable passwordless authentication
|
|
6
|
+
* via biometrics, security keys, or platform authenticators.
|
|
7
|
+
*
|
|
8
|
+
* @see https://simplewebauthn.dev/
|
|
9
|
+
*/
|
|
10
|
+
import crypto from 'node:crypto';
|
|
11
|
+
import { getClient } from '@revealui/db/client';
|
|
12
|
+
import { passkeys, users } from '@revealui/db/schema';
|
|
13
|
+
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from '@simplewebauthn/server';
|
|
14
|
+
import { and, count, eq } from 'drizzle-orm';
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
maxPasskeysPerUser: 10,
|
|
17
|
+
challengeTtlMs: 5 * 60 * 1000,
|
|
18
|
+
rpId: 'localhost',
|
|
19
|
+
rpName: 'RevealUI',
|
|
20
|
+
origin: 'http://localhost:4000',
|
|
21
|
+
};
|
|
22
|
+
let config = { ...DEFAULT_CONFIG };
|
|
23
|
+
export function configurePasskey(overrides) {
|
|
24
|
+
config = { ...DEFAULT_CONFIG, ...overrides };
|
|
25
|
+
}
|
|
26
|
+
export function resetPasskeyConfig() {
|
|
27
|
+
config = { ...DEFAULT_CONFIG };
|
|
28
|
+
}
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Registration
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Generate WebAuthn registration options for a user.
|
|
34
|
+
*
|
|
35
|
+
* The returned object should be passed to the browser's navigator.credentials.create().
|
|
36
|
+
* The challenge is embedded in the options and should be stored server-side
|
|
37
|
+
* for verification.
|
|
38
|
+
*
|
|
39
|
+
* @param userId - User's database ID
|
|
40
|
+
* @param userEmail - User's email (used as userName in WebAuthn)
|
|
41
|
+
* @param existingCredentialIds - Credential IDs to exclude (prevent re-registration)
|
|
42
|
+
*/
|
|
43
|
+
export async function generateRegistrationChallenge(userId, userEmail, existingCredentialIds) {
|
|
44
|
+
const excludeCredentials = existingCredentialIds?.map((id) => ({
|
|
45
|
+
id,
|
|
46
|
+
}));
|
|
47
|
+
// Encode userId as Uint8Array for WebAuthn userID field
|
|
48
|
+
const userIdBytes = new TextEncoder().encode(userId);
|
|
49
|
+
const options = await generateRegistrationOptions({
|
|
50
|
+
rpName: config.rpName,
|
|
51
|
+
rpID: config.rpId,
|
|
52
|
+
userName: userEmail,
|
|
53
|
+
userID: userIdBytes,
|
|
54
|
+
userDisplayName: userEmail,
|
|
55
|
+
excludeCredentials,
|
|
56
|
+
authenticatorSelection: {
|
|
57
|
+
residentKey: 'preferred',
|
|
58
|
+
userVerification: 'preferred',
|
|
59
|
+
},
|
|
60
|
+
timeout: config.challengeTtlMs,
|
|
61
|
+
});
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Verify a WebAuthn registration response from the browser.
|
|
66
|
+
*
|
|
67
|
+
* @param response - The RegistrationResponseJSON from the browser
|
|
68
|
+
* @param expectedChallenge - The challenge from generateRegistrationChallenge
|
|
69
|
+
* @param expectedOrigin - Override origin (defaults to config.origin)
|
|
70
|
+
* @returns Verified registration data including credential info
|
|
71
|
+
* @throws If verification fails
|
|
72
|
+
*/
|
|
73
|
+
export async function verifyRegistration(response, expectedChallenge, expectedOrigin) {
|
|
74
|
+
const verification = await verifyRegistrationResponse({
|
|
75
|
+
response,
|
|
76
|
+
expectedChallenge,
|
|
77
|
+
expectedOrigin: expectedOrigin ?? config.origin,
|
|
78
|
+
expectedRPID: config.rpId,
|
|
79
|
+
});
|
|
80
|
+
if (!(verification.verified && verification.registrationInfo)) {
|
|
81
|
+
throw new Error('Passkey registration verification failed');
|
|
82
|
+
}
|
|
83
|
+
return verification;
|
|
84
|
+
}
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Credential Storage
|
|
87
|
+
// =============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Store a verified passkey credential in the database.
|
|
90
|
+
*
|
|
91
|
+
* Enforces the per-user passkey limit before insertion.
|
|
92
|
+
*
|
|
93
|
+
* @param userId - User's database ID
|
|
94
|
+
* @param credential - Verified credential from verifyRegistration
|
|
95
|
+
* @param deviceName - Optional user-friendly name (e.g., "MacBook Pro Touch ID")
|
|
96
|
+
* @returns The stored passkey record
|
|
97
|
+
*/
|
|
98
|
+
export async function storePasskey(userId, credential, deviceName) {
|
|
99
|
+
const db = getClient();
|
|
100
|
+
// Enforce per-user passkey limit
|
|
101
|
+
const [result] = await db
|
|
102
|
+
.select({ total: count() })
|
|
103
|
+
.from(passkeys)
|
|
104
|
+
.where(eq(passkeys.userId, userId));
|
|
105
|
+
const currentCount = result?.total ?? 0;
|
|
106
|
+
if (currentCount >= config.maxPasskeysPerUser) {
|
|
107
|
+
throw new Error(`Maximum of ${config.maxPasskeysPerUser} passkeys per user reached`);
|
|
108
|
+
}
|
|
109
|
+
const id = crypto.randomUUID();
|
|
110
|
+
const now = new Date();
|
|
111
|
+
await db.insert(passkeys).values({
|
|
112
|
+
id,
|
|
113
|
+
userId,
|
|
114
|
+
credentialId: credential.id,
|
|
115
|
+
publicKey: Buffer.from(credential.publicKey),
|
|
116
|
+
counter: credential.counter,
|
|
117
|
+
transports: credential.transports ?? null,
|
|
118
|
+
aaguid: credential.aaguid ?? null,
|
|
119
|
+
deviceName: deviceName ?? null,
|
|
120
|
+
backedUp: credential.backedUp ?? false,
|
|
121
|
+
createdAt: now,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
id,
|
|
125
|
+
userId,
|
|
126
|
+
credentialId: credential.id,
|
|
127
|
+
deviceName: deviceName ?? null,
|
|
128
|
+
createdAt: now,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// Authentication
|
|
133
|
+
// =============================================================================
|
|
134
|
+
/**
|
|
135
|
+
* Generate WebAuthn authentication options.
|
|
136
|
+
*
|
|
137
|
+
* The returned object should be passed to the browser's navigator.credentials.get().
|
|
138
|
+
*
|
|
139
|
+
* @param allowCredentials - Optional list of credential IDs to allow
|
|
140
|
+
*/
|
|
141
|
+
export async function generateAuthenticationChallenge(allowCredentials) {
|
|
142
|
+
const options = await generateAuthenticationOptions({
|
|
143
|
+
rpID: config.rpId,
|
|
144
|
+
allowCredentials: allowCredentials?.map((cred) => ({
|
|
145
|
+
id: cred.id,
|
|
146
|
+
transports: cred.transports,
|
|
147
|
+
})),
|
|
148
|
+
timeout: config.challengeTtlMs,
|
|
149
|
+
userVerification: 'preferred',
|
|
150
|
+
});
|
|
151
|
+
return options;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Verify a WebAuthn authentication response and update the credential counter.
|
|
155
|
+
*
|
|
156
|
+
* @param response - The AuthenticationResponseJSON from the browser
|
|
157
|
+
* @param credential - The stored credential (id, publicKey, counter)
|
|
158
|
+
* @param expectedChallenge - The challenge from generateAuthenticationChallenge
|
|
159
|
+
* @param expectedOrigin - Override origin (defaults to config.origin)
|
|
160
|
+
* @returns Verification result with new counter value
|
|
161
|
+
*/
|
|
162
|
+
export async function verifyAuthentication(response, credential, expectedChallenge, expectedOrigin) {
|
|
163
|
+
const verification = await verifyAuthenticationResponse({
|
|
164
|
+
response,
|
|
165
|
+
expectedChallenge,
|
|
166
|
+
expectedOrigin: expectedOrigin ?? config.origin,
|
|
167
|
+
expectedRPID: [config.rpId],
|
|
168
|
+
credential,
|
|
169
|
+
});
|
|
170
|
+
if (verification.verified) {
|
|
171
|
+
// Update counter and lastUsedAt in the database
|
|
172
|
+
const db = getClient();
|
|
173
|
+
await db
|
|
174
|
+
.update(passkeys)
|
|
175
|
+
.set({
|
|
176
|
+
counter: verification.authenticationInfo.newCounter,
|
|
177
|
+
lastUsedAt: new Date(),
|
|
178
|
+
})
|
|
179
|
+
.where(eq(passkeys.credentialId, credential.id));
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
verified: verification.verified,
|
|
183
|
+
newCounter: verification.authenticationInfo.newCounter,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// Management
|
|
188
|
+
// =============================================================================
|
|
189
|
+
/**
|
|
190
|
+
* List all passkeys for a user (safe for client — excludes publicKey and counter).
|
|
191
|
+
*/
|
|
192
|
+
export async function listPasskeys(userId) {
|
|
193
|
+
const db = getClient();
|
|
194
|
+
const rows = await db
|
|
195
|
+
.select({
|
|
196
|
+
id: passkeys.id,
|
|
197
|
+
credentialId: passkeys.credentialId,
|
|
198
|
+
deviceName: passkeys.deviceName,
|
|
199
|
+
backedUp: passkeys.backedUp,
|
|
200
|
+
createdAt: passkeys.createdAt,
|
|
201
|
+
lastUsedAt: passkeys.lastUsedAt,
|
|
202
|
+
})
|
|
203
|
+
.from(passkeys)
|
|
204
|
+
.where(eq(passkeys.userId, userId));
|
|
205
|
+
return rows;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Delete a passkey. Blocks deletion if it's the user's last sign-in method.
|
|
209
|
+
*
|
|
210
|
+
* @param userId - User's database ID
|
|
211
|
+
* @param passkeyId - The passkey record ID to delete
|
|
212
|
+
* @throws If this is the user's only sign-in method
|
|
213
|
+
*/
|
|
214
|
+
export async function deletePasskey(userId, passkeyId) {
|
|
215
|
+
const { passkeyCount, hasPassword } = await countUserCredentials(userId);
|
|
216
|
+
if (passkeyCount <= 1 && !hasPassword) {
|
|
217
|
+
throw new Error('Cannot delete last sign-in method');
|
|
218
|
+
}
|
|
219
|
+
const db = getClient();
|
|
220
|
+
await db.delete(passkeys).where(and(eq(passkeys.id, passkeyId), eq(passkeys.userId, userId)));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Rename a passkey's device name.
|
|
224
|
+
*
|
|
225
|
+
* @param userId - User's database ID
|
|
226
|
+
* @param passkeyId - The passkey record ID to rename
|
|
227
|
+
* @param name - New friendly name
|
|
228
|
+
*/
|
|
229
|
+
export async function renamePasskey(userId, passkeyId, name) {
|
|
230
|
+
const db = getClient();
|
|
231
|
+
await db
|
|
232
|
+
.update(passkeys)
|
|
233
|
+
.set({ deviceName: name })
|
|
234
|
+
.where(and(eq(passkeys.id, passkeyId), eq(passkeys.userId, userId)));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Count a user's sign-in credentials (passkeys + password).
|
|
238
|
+
*
|
|
239
|
+
* @param userId - User's database ID
|
|
240
|
+
* @returns Passkey count and whether user has a password set
|
|
241
|
+
*/
|
|
242
|
+
export async function countUserCredentials(userId) {
|
|
243
|
+
const db = getClient();
|
|
244
|
+
const [passkeyResult] = await db
|
|
245
|
+
.select({ total: count() })
|
|
246
|
+
.from(passkeys)
|
|
247
|
+
.where(eq(passkeys.userId, userId));
|
|
248
|
+
const [userResult] = await db
|
|
249
|
+
.select({ password: users.password })
|
|
250
|
+
.from(users)
|
|
251
|
+
.where(eq(users.id, userId))
|
|
252
|
+
.limit(1);
|
|
253
|
+
return {
|
|
254
|
+
passkeyCount: passkeyResult?.total ?? 0,
|
|
255
|
+
hasPassword: userResult?.password != null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
@@ -43,6 +43,21 @@ export declare function validatePasswordResetToken(tokenId: string, token: strin
|
|
|
43
43
|
* @returns Success result
|
|
44
44
|
*/
|
|
45
45
|
export declare function resetPasswordWithToken(tokenId: string, token: string, newPassword: string): Promise<PasswordResetResult>;
|
|
46
|
+
export interface ChangePasswordResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Change password for an authenticated user.
|
|
52
|
+
*
|
|
53
|
+
* Verifies the current password before updating. Does not touch sessions —
|
|
54
|
+
* the caller is responsible for revoking other sessions if desired.
|
|
55
|
+
*
|
|
56
|
+
* @param userId - Authenticated user ID
|
|
57
|
+
* @param currentPassword - Plain-text current password to verify
|
|
58
|
+
* @param newPassword - Plain-text new password to hash and store
|
|
59
|
+
*/
|
|
60
|
+
export declare function changePassword(userId: string, currentPassword: string, newPassword: string): Promise<ChangePasswordResult>;
|
|
46
61
|
/**
|
|
47
62
|
* Invalidates a password reset token
|
|
48
63
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-reset.d.ts","sourceRoot":"","sources":["../../src/server/password-reset.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"password-reset.d.ts","sourceRoot":"","sources":["../../src/server/password-reset.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwE5F;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqCxB;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA4E9B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAqC/B;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChG"}
|
|
@@ -37,7 +37,10 @@ function generateSalt() {
|
|
|
37
37
|
export async function generatePasswordResetToken(email) {
|
|
38
38
|
try {
|
|
39
39
|
const db = getClient();
|
|
40
|
-
// Find user by email
|
|
40
|
+
// Find user by email — intentionally does NOT check user.password.
|
|
41
|
+
// OAuth-only users (password: null) can use this flow to set a password,
|
|
42
|
+
// giving them a fallback login method independent of their OAuth provider.
|
|
43
|
+
// This is safe because the reset link is sent to their verified email.
|
|
41
44
|
const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1);
|
|
42
45
|
if (!user) {
|
|
43
46
|
// Don't reveal if user exists (security best practice)
|
|
@@ -195,6 +198,46 @@ export async function resetPasswordWithToken(tokenId, token, newPassword) {
|
|
|
195
198
|
};
|
|
196
199
|
}
|
|
197
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Change password for an authenticated user.
|
|
203
|
+
*
|
|
204
|
+
* Verifies the current password before updating. Does not touch sessions —
|
|
205
|
+
* the caller is responsible for revoking other sessions if desired.
|
|
206
|
+
*
|
|
207
|
+
* @param userId - Authenticated user ID
|
|
208
|
+
* @param currentPassword - Plain-text current password to verify
|
|
209
|
+
* @param newPassword - Plain-text new password to hash and store
|
|
210
|
+
*/
|
|
211
|
+
export async function changePassword(userId, currentPassword, newPassword) {
|
|
212
|
+
try {
|
|
213
|
+
const db = getClient();
|
|
214
|
+
const [user] = await db
|
|
215
|
+
.select({ id: users.id, password: users.password })
|
|
216
|
+
.from(users)
|
|
217
|
+
.where(eq(users.id, userId))
|
|
218
|
+
.limit(1);
|
|
219
|
+
if (!user) {
|
|
220
|
+
return { success: false, error: 'User not found.' };
|
|
221
|
+
}
|
|
222
|
+
if (!user.password) {
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
error: 'No password is set on this account. Use the password reset link to set one.',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const currentValid = await bcrypt.compare(currentPassword, user.password);
|
|
229
|
+
if (!currentValid) {
|
|
230
|
+
return { success: false, error: 'Current password is incorrect.' };
|
|
231
|
+
}
|
|
232
|
+
const newHash = await bcrypt.hash(newPassword, 12);
|
|
233
|
+
await db.update(users).set({ password: newHash }).where(eq(users.id, userId));
|
|
234
|
+
return { success: true };
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
logger.error('Error changing password', error instanceof Error ? error : new Error(String(error)));
|
|
238
|
+
return { success: false, error: 'An unexpected error occurred.' };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
198
241
|
/**
|
|
199
242
|
* Invalidates a password reset token
|
|
200
243
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"password-validation.d.ts","sourceRoot":"","sources":["../../src/server/password-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"password-validation.d.ts","sourceRoot":"","sources":["../../src/server/password-validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,wBAAwB,CAgCnF;AAED;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/server/providers/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../src/server/providers/github.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOzF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAqCrF;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAoD1E"}
|
|
@@ -31,7 +31,15 @@ export async function exchangeCode(code, redirectUri) {
|
|
|
31
31
|
}),
|
|
32
32
|
});
|
|
33
33
|
if (!response.ok) {
|
|
34
|
-
|
|
34
|
+
let detail = '';
|
|
35
|
+
try {
|
|
36
|
+
const err = (await response.json());
|
|
37
|
+
detail = err.error_description ?? err.error ?? '';
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Response body not JSON — use status only
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`GitHub token exchange failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
35
43
|
}
|
|
36
44
|
const data = (await response.json());
|
|
37
45
|
if (data.error) {
|
|
@@ -51,7 +59,15 @@ export async function fetchUser(accessToken) {
|
|
|
51
59
|
};
|
|
52
60
|
const userResponse = await fetch('https://api.github.com/user', { headers });
|
|
53
61
|
if (!userResponse.ok) {
|
|
54
|
-
|
|
62
|
+
let detail = '';
|
|
63
|
+
try {
|
|
64
|
+
const err = (await userResponse.json());
|
|
65
|
+
detail = err.message ?? '';
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Response body not JSON
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`GitHub user fetch failed: ${userResponse.status}${detail ? ` — ${detail}` : ''}`);
|
|
55
71
|
}
|
|
56
72
|
const user = (await userResponse.json());
|
|
57
73
|
let email = user.email ?? null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../../src/server/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../../src/server/providers/google.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CASzF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BrF;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgC1E"}
|
|
@@ -27,7 +27,15 @@ export async function exchangeCode(code, redirectUri) {
|
|
|
27
27
|
}),
|
|
28
28
|
});
|
|
29
29
|
if (!response.ok) {
|
|
30
|
-
|
|
30
|
+
let detail = '';
|
|
31
|
+
try {
|
|
32
|
+
const err = (await response.json());
|
|
33
|
+
detail = err.error_description ?? err.error ?? '';
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Response body not JSON — use status only
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Google token exchange failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
31
39
|
}
|
|
32
40
|
const data = (await response.json());
|
|
33
41
|
if (!data.access_token || typeof data.access_token !== 'string') {
|
|
@@ -41,7 +49,15 @@ export async function fetchUser(accessToken) {
|
|
|
41
49
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
42
50
|
});
|
|
43
51
|
if (!response.ok) {
|
|
44
|
-
|
|
52
|
+
let detail = '';
|
|
53
|
+
try {
|
|
54
|
+
const err = (await response.json());
|
|
55
|
+
detail = err.error?.message ?? '';
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Response body not JSON
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Google userinfo fetch failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
45
61
|
}
|
|
46
62
|
const data = (await response.json());
|
|
47
63
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/server/providers/vercel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"vercel.d.ts","sourceRoot":"","sources":["../../../src/server/providers/vercel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMzF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BrF;AAED,wBAAsB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkC1E"}
|
|
@@ -23,7 +23,15 @@ export async function exchangeCode(code, redirectUri) {
|
|
|
23
23
|
}),
|
|
24
24
|
});
|
|
25
25
|
if (!response.ok) {
|
|
26
|
-
|
|
26
|
+
let detail = '';
|
|
27
|
+
try {
|
|
28
|
+
const err = (await response.json());
|
|
29
|
+
detail = err.error_description ?? err.error ?? '';
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Response body not JSON — use status only
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Vercel token exchange failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
27
35
|
}
|
|
28
36
|
const data = (await response.json());
|
|
29
37
|
return data.access_token;
|
|
@@ -34,7 +42,15 @@ export async function fetchUser(accessToken) {
|
|
|
34
42
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
35
43
|
});
|
|
36
44
|
if (!response.ok) {
|
|
37
|
-
|
|
45
|
+
let detail = '';
|
|
46
|
+
try {
|
|
47
|
+
const err = (await response.json());
|
|
48
|
+
detail = err.error?.message ?? '';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Response body not JSON
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Vercel user fetch failed: ${response.status}${detail ? ` — ${detail}` : ''}`);
|
|
38
54
|
}
|
|
39
55
|
const data = (await response.json());
|
|
40
56
|
const u = data.user;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Rate Limiting Utilities
|
|
3
3
|
*
|
|
4
4
|
* Rate limiting for authentication endpoints using storage abstraction.
|
|
5
|
-
* Supports in-memory (dev)
|
|
5
|
+
* Supports in-memory (dev) or database (production) backends.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
8
|
* Rate limit configuration
|
|
@@ -12,6 +12,15 @@ export interface RateLimitConfig {
|
|
|
12
12
|
windowMs: number;
|
|
13
13
|
blockDurationMs?: number;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Override default rate limit configuration globally.
|
|
17
|
+
* Per-call config parameters still take precedence.
|
|
18
|
+
*/
|
|
19
|
+
export declare function configureRateLimit(overrides: Partial<RateLimitConfig>): void;
|
|
20
|
+
/**
|
|
21
|
+
* Reset rate limit configuration to defaults (for testing).
|
|
22
|
+
*/
|
|
23
|
+
export declare function resetRateLimitConfig(): void;
|
|
15
24
|
/**
|
|
16
25
|
* Checks if an action should be rate limited
|
|
17
26
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/server/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/server/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAUD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAE5E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AA+BD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAA8B,GACrC,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4DnE;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/D;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAA8B,GACrC,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqBhE"}
|