clearauth 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/CHANGELOG.md +235 -0
- package/LICENSE +21 -0
- package/README.md +417 -0
- package/dist/auth/handler.d.ts +38 -0
- package/dist/auth/handler.js +483 -0
- package/dist/auth/handler.js.map +1 -0
- package/dist/auth/login.d.ts +69 -0
- package/dist/auth/login.js +103 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/register.d.ts +72 -0
- package/dist/auth/register.js +122 -0
- package/dist/auth/register.js.map +1 -0
- package/dist/auth/reset-password.d.ts +106 -0
- package/dist/auth/reset-password.js +213 -0
- package/dist/auth/reset-password.js.map +1 -0
- package/dist/auth/utils.d.ts +58 -0
- package/dist/auth/utils.js +121 -0
- package/dist/auth/utils.js.map +1 -0
- package/dist/auth/verify-email.d.ts +70 -0
- package/dist/auth/verify-email.js +137 -0
- package/dist/auth/verify-email.js.map +1 -0
- package/dist/createMechAuth.d.ts +178 -0
- package/dist/createMechAuth.js +215 -0
- package/dist/createMechAuth.js.map +1 -0
- package/dist/database/schema.d.ts +135 -0
- package/dist/database/schema.js +37 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/edge.d.ts +4 -0
- package/dist/edge.js +6 -0
- package/dist/edge.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +100 -0
- package/dist/handler.js +213 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +22 -0
- package/dist/logger.js +40 -0
- package/dist/logger.js.map +1 -0
- package/dist/mech-kysely.d.ts +22 -0
- package/dist/mech-kysely.js +88 -0
- package/dist/mech-kysely.js.map +1 -0
- package/dist/mech-sql-client.d.ts +85 -0
- package/dist/mech-sql-client.js +155 -0
- package/dist/mech-sql-client.js.map +1 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.js +10 -0
- package/dist/node.js.map +1 -0
- package/dist/oauth/arctic-providers.d.ts +60 -0
- package/dist/oauth/arctic-providers.js +94 -0
- package/dist/oauth/arctic-providers.js.map +1 -0
- package/dist/oauth/callbacks.d.ts +155 -0
- package/dist/oauth/callbacks.js +286 -0
- package/dist/oauth/callbacks.js.map +1 -0
- package/dist/oauth/github.d.ts +47 -0
- package/dist/oauth/github.js +136 -0
- package/dist/oauth/github.js.map +1 -0
- package/dist/oauth/google.d.ts +49 -0
- package/dist/oauth/google.js +104 -0
- package/dist/oauth/google.js.map +1 -0
- package/dist/oauth/handler.d.ts +31 -0
- package/dist/oauth/handler.js +277 -0
- package/dist/oauth/handler.js.map +1 -0
- package/dist/password-hasher-argon2.d.ts +7 -0
- package/dist/password-hasher-argon2.js +16 -0
- package/dist/password-hasher-argon2.js.map +1 -0
- package/dist/password-hasher.d.ts +12 -0
- package/dist/password-hasher.js +115 -0
- package/dist/password-hasher.js.map +1 -0
- package/dist/react.d.ts +152 -0
- package/dist/react.js +296 -0
- package/dist/react.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cors.d.ts +65 -0
- package/dist/utils/cors.js +152 -0
- package/dist/utils/cors.js.map +1 -0
- package/dist/utils/normalize-auth-path.d.ts +1 -0
- package/dist/utils/normalize-auth-path.js +8 -0
- package/dist/utils/normalize-auth-path.js.map +1 -0
- package/dist/validation.d.ts +23 -0
- package/dist/validation.js +70 -0
- package/dist/validation.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Registration
|
|
3
|
+
*
|
|
4
|
+
* Handles user registration with email and password:
|
|
5
|
+
* - Email and password validation
|
|
6
|
+
* - Password hashing using Argon2id
|
|
7
|
+
* - User creation with email_verified=false
|
|
8
|
+
* - Email verification token generation
|
|
9
|
+
* - Initial session creation
|
|
10
|
+
*/
|
|
11
|
+
import type { Kysely } from 'kysely';
|
|
12
|
+
import type { Database, User } from '../database/schema.js';
|
|
13
|
+
import type { RequestContext } from '../types.js';
|
|
14
|
+
import type { PasswordHasher } from '../password-hasher.js';
|
|
15
|
+
/**
|
|
16
|
+
* Register a new user with email and password
|
|
17
|
+
*
|
|
18
|
+
* Creates a new user account with the provided email and password.
|
|
19
|
+
* The password is hashed using Argon2id before storage.
|
|
20
|
+
* An email verification token is generated and stored.
|
|
21
|
+
* An initial session is created for the user.
|
|
22
|
+
*
|
|
23
|
+
* @param db - Kysely database instance
|
|
24
|
+
* @param email - User's email address
|
|
25
|
+
* @param password - User's password (plain text, will be hashed)
|
|
26
|
+
* @param context - Optional request context (IP address, user agent)
|
|
27
|
+
* @returns User record, session ID, and verification token
|
|
28
|
+
* @throws {AuthError} If validation fails or user already exists
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const result = await registerUser(db, 'user@example.com', 'SecurePass123!', {
|
|
33
|
+
* ipAddress: '192.168.1.1',
|
|
34
|
+
* userAgent: 'Mozilla/5.0...'
|
|
35
|
+
* })
|
|
36
|
+
* console.log(result.user.id, result.sessionId, result.verificationToken)
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerUser(db: Kysely<Database>, email: string, password: string, context?: RequestContext, passwordHasher?: PasswordHasher): Promise<{
|
|
40
|
+
user: User;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
verificationToken: string;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Register user result type (for HTTP responses)
|
|
46
|
+
*/
|
|
47
|
+
export interface RegisterUserResult {
|
|
48
|
+
user: {
|
|
49
|
+
id: string;
|
|
50
|
+
email: string;
|
|
51
|
+
email_verified: boolean;
|
|
52
|
+
name: string | null;
|
|
53
|
+
avatar_url: string | null;
|
|
54
|
+
created_at: Date;
|
|
55
|
+
};
|
|
56
|
+
sessionId: string;
|
|
57
|
+
verificationToken: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Convert registration result to public format
|
|
61
|
+
*
|
|
62
|
+
* Removes sensitive fields like password_hash and provider IDs.
|
|
63
|
+
*
|
|
64
|
+
* @param result - Registration result
|
|
65
|
+
* @returns Public registration result
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export declare function toPublicRegisterResult(result: {
|
|
69
|
+
user: User;
|
|
70
|
+
sessionId: string;
|
|
71
|
+
verificationToken: string;
|
|
72
|
+
}): RegisterUserResult;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Registration
|
|
3
|
+
*
|
|
4
|
+
* Handles user registration with email and password:
|
|
5
|
+
* - Email and password validation
|
|
6
|
+
* - Password hashing using Argon2id
|
|
7
|
+
* - User creation with email_verified=false
|
|
8
|
+
* - Email verification token generation
|
|
9
|
+
* - Initial session creation
|
|
10
|
+
*/
|
|
11
|
+
import { createSession } from '../oauth/callbacks.js';
|
|
12
|
+
import { createPbkdf2PasswordHasher } from '../password-hasher.js';
|
|
13
|
+
import { isValidEmail, validatePassword, normalizeEmail, generateSecureToken, createAuthError, } from './utils.js';
|
|
14
|
+
const defaultPasswordHasher = createPbkdf2PasswordHasher();
|
|
15
|
+
/**
|
|
16
|
+
* Email verification token expiration (24 hours)
|
|
17
|
+
*/
|
|
18
|
+
const VERIFICATION_TOKEN_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
19
|
+
/**
|
|
20
|
+
* Register a new user with email and password
|
|
21
|
+
*
|
|
22
|
+
* Creates a new user account with the provided email and password.
|
|
23
|
+
* The password is hashed using Argon2id before storage.
|
|
24
|
+
* An email verification token is generated and stored.
|
|
25
|
+
* An initial session is created for the user.
|
|
26
|
+
*
|
|
27
|
+
* @param db - Kysely database instance
|
|
28
|
+
* @param email - User's email address
|
|
29
|
+
* @param password - User's password (plain text, will be hashed)
|
|
30
|
+
* @param context - Optional request context (IP address, user agent)
|
|
31
|
+
* @returns User record, session ID, and verification token
|
|
32
|
+
* @throws {AuthError} If validation fails or user already exists
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const result = await registerUser(db, 'user@example.com', 'SecurePass123!', {
|
|
37
|
+
* ipAddress: '192.168.1.1',
|
|
38
|
+
* userAgent: 'Mozilla/5.0...'
|
|
39
|
+
* })
|
|
40
|
+
* console.log(result.user.id, result.sessionId, result.verificationToken)
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export async function registerUser(db, email, password, context, passwordHasher) {
|
|
44
|
+
const hasher = passwordHasher ?? defaultPasswordHasher;
|
|
45
|
+
// Validate email
|
|
46
|
+
if (!isValidEmail(email)) {
|
|
47
|
+
throw createAuthError('Invalid email address', 'INVALID_EMAIL', 400);
|
|
48
|
+
}
|
|
49
|
+
// Validate password
|
|
50
|
+
const passwordError = validatePassword(password);
|
|
51
|
+
if (passwordError) {
|
|
52
|
+
throw createAuthError(passwordError, 'INVALID_PASSWORD', 400);
|
|
53
|
+
}
|
|
54
|
+
// Normalize email
|
|
55
|
+
const normalizedEmail = normalizeEmail(email);
|
|
56
|
+
// Check if user already exists
|
|
57
|
+
const existingUser = await db
|
|
58
|
+
.selectFrom('users')
|
|
59
|
+
.select('id')
|
|
60
|
+
.where('email', '=', normalizedEmail)
|
|
61
|
+
.executeTakeFirst();
|
|
62
|
+
if (existingUser) {
|
|
63
|
+
throw createAuthError('User with this email already exists', 'EMAIL_EXISTS', 409);
|
|
64
|
+
}
|
|
65
|
+
// Hash password
|
|
66
|
+
const passwordHash = await hasher.hash(password);
|
|
67
|
+
// Create user
|
|
68
|
+
const newUser = {
|
|
69
|
+
email: normalizedEmail,
|
|
70
|
+
email_verified: false,
|
|
71
|
+
password_hash: passwordHash,
|
|
72
|
+
github_id: null,
|
|
73
|
+
google_id: null,
|
|
74
|
+
name: null,
|
|
75
|
+
avatar_url: null,
|
|
76
|
+
};
|
|
77
|
+
const user = await db.insertInto('users').values(newUser).returningAll().executeTakeFirstOrThrow();
|
|
78
|
+
// Generate email verification token
|
|
79
|
+
const verificationToken = generateSecureToken(32); // 256 bits of entropy
|
|
80
|
+
const expiresAt = new Date(Date.now() + VERIFICATION_TOKEN_EXPIRY);
|
|
81
|
+
const newToken = {
|
|
82
|
+
token: verificationToken,
|
|
83
|
+
user_id: user.id,
|
|
84
|
+
email: normalizedEmail,
|
|
85
|
+
expires_at: expiresAt,
|
|
86
|
+
};
|
|
87
|
+
await db.insertInto('email_verification_tokens').values(newToken).execute();
|
|
88
|
+
// Create initial session
|
|
89
|
+
const sessionId = await createSession(db, user.id, 2592000, {
|
|
90
|
+
ipAddress: context?.ipAddress,
|
|
91
|
+
userAgent: context?.userAgent,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
user,
|
|
95
|
+
sessionId,
|
|
96
|
+
verificationToken,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert registration result to public format
|
|
101
|
+
*
|
|
102
|
+
* Removes sensitive fields like password_hash and provider IDs.
|
|
103
|
+
*
|
|
104
|
+
* @param result - Registration result
|
|
105
|
+
* @returns Public registration result
|
|
106
|
+
* @internal
|
|
107
|
+
*/
|
|
108
|
+
export function toPublicRegisterResult(result) {
|
|
109
|
+
return {
|
|
110
|
+
user: {
|
|
111
|
+
id: result.user.id,
|
|
112
|
+
email: result.user.email,
|
|
113
|
+
email_verified: result.user.email_verified,
|
|
114
|
+
name: result.user.name,
|
|
115
|
+
avatar_url: result.user.avatar_url,
|
|
116
|
+
created_at: result.user.created_at,
|
|
117
|
+
},
|
|
118
|
+
sessionId: result.sessionId,
|
|
119
|
+
verificationToken: result.verificationToken,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/auth/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAGrD,OAAO,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AAClE,OAAO,EACL,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,eAAe,GAChB,MAAM,YAAY,CAAA;AAEnB,MAAM,qBAAqB,GAAG,0BAA0B,EAAE,CAAA;AAE1D;;GAEG;AACH,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,2BAA2B;AAEjF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAoB,EACpB,KAAa,EACb,QAAgB,EAChB,OAAwB,EACxB,cAA+B;IAE/B,MAAM,MAAM,GAAG,cAAc,IAAI,qBAAqB,CAAA;IAEtD,iBAAiB;IACjB,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,eAAe,CAAC,uBAAuB,EAAE,eAAe,EAAE,GAAG,CAAC,CAAA;IACtE,CAAC;IAED,oBAAoB;IACpB,MAAM,aAAa,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAChD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,eAAe,CAAC,aAAa,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAA;IAC/D,CAAC;IAED,kBAAkB;IAClB,MAAM,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;IAE7C,+BAA+B;IAC/B,MAAM,YAAY,GAAG,MAAM,EAAE;SAC1B,UAAU,CAAC,OAAO,CAAC;SACnB,MAAM,CAAC,IAAI,CAAC;SACZ,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC;SACpC,gBAAgB,EAAE,CAAA;IAErB,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,eAAe,CAAC,qCAAqC,EAAE,cAAc,EAAE,GAAG,CAAC,CAAA;IACnF,CAAC;IAED,gBAAgB;IAChB,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEhD,cAAc;IACd,MAAM,OAAO,GAAY;QACvB,KAAK,EAAE,eAAe;QACtB,cAAc,EAAE,KAAK;QACrB,aAAa,EAAE,YAAY;QAC3B,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,IAAI;QACV,UAAU,EAAE,IAAI;KACjB,CAAA;IAED,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,CAAC,uBAAuB,EAAE,CAAA;IAElG,oCAAoC;IACpC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,EAAE,CAAC,CAAA,CAAC,sBAAsB;IACxE,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,yBAAyB,CAAC,CAAA;IAElE,MAAM,QAAQ,GAA8B;QAC1C,KAAK,EAAE,iBAAiB;QACxB,OAAO,EAAE,IAAI,CAAC,EAAE;QAChB,KAAK,EAAE,eAAe;QACtB,UAAU,EAAE,SAAS;KACtB,CAAA;IAED,MAAM,EAAE,CAAC,UAAU,CAAC,2BAA2B,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAA;IAE3E,yBAAyB;IACzB,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE;QAC1D,SAAS,EAAE,OAAO,EAAE,SAAS;QAC7B,SAAS,EAAE,OAAO,EAAE,SAAS;KAC9B,CAAC,CAAA;IAEF,OAAO;QACL,IAAI;QACJ,SAAS;QACT,iBAAiB;KAClB,CAAA;AACH,CAAC;AAkBD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAItC;IACC,OAAO;QACL,IAAI,EAAE;YACJ,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK;YACxB,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc;YAC1C,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI;YACtB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU;YAClC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU;SACnC;QACD,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;KAC5C,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password Reset
|
|
3
|
+
*
|
|
4
|
+
* Handles password reset flow:
|
|
5
|
+
* - Password reset token generation and storage
|
|
6
|
+
* - Token validation and expiration checking
|
|
7
|
+
* - Password update with new hash
|
|
8
|
+
* - Session invalidation for security
|
|
9
|
+
*/
|
|
10
|
+
import type { Kysely } from 'kysely';
|
|
11
|
+
import type { Database } from '../database/schema.js';
|
|
12
|
+
import type { PasswordHasher } from '../password-hasher.js';
|
|
13
|
+
/**
|
|
14
|
+
* Request password reset
|
|
15
|
+
*
|
|
16
|
+
* Generates a password reset token and stores it in the database.
|
|
17
|
+
* The token expires after 1 hour for security.
|
|
18
|
+
*
|
|
19
|
+
* **IMPORTANT:** This function does NOT return the token to prevent email enumeration.
|
|
20
|
+
* The token should be sent via email by the caller using an email service.
|
|
21
|
+
* The function always returns success, even if the user doesn't exist.
|
|
22
|
+
*
|
|
23
|
+
* @param db - Kysely database instance
|
|
24
|
+
* @param email - User's email address
|
|
25
|
+
* @param onTokenGenerated - Optional callback to send the token via email
|
|
26
|
+
* @returns Success status and email (for sending the token)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const result = await requestPasswordReset(db, 'user@example.com', async (email, token) => {
|
|
31
|
+
* await sendEmail({
|
|
32
|
+
* to: email,
|
|
33
|
+
* subject: 'Password Reset',
|
|
34
|
+
* template: 'password-reset',
|
|
35
|
+
* data: { token, resetUrl: `https://example.com/reset-password?token=${token}` }
|
|
36
|
+
* })
|
|
37
|
+
* })
|
|
38
|
+
* // Always returns { success: true } regardless of whether user exists
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function requestPasswordReset(db: Kysely<Database>, email: string, onTokenGenerated?: (email: string, token: string) => Promise<void>): Promise<{
|
|
42
|
+
success: true;
|
|
43
|
+
email: string;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Verify password reset token
|
|
47
|
+
*
|
|
48
|
+
* Checks if a password reset token is valid and not expired.
|
|
49
|
+
* Does not consume the token.
|
|
50
|
+
*
|
|
51
|
+
* @param db - Kysely database instance
|
|
52
|
+
* @param token - Password reset token
|
|
53
|
+
* @returns Validation result with user ID if valid
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const result = await verifyResetToken(db, 'abc123...')
|
|
58
|
+
* if (result.valid) {
|
|
59
|
+
* console.log('Token is valid for user:', result.userId)
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function verifyResetToken(db: Kysely<Database>, token: string): Promise<{
|
|
64
|
+
valid: boolean;
|
|
65
|
+
userId?: string;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* Reset password using token
|
|
69
|
+
*
|
|
70
|
+
* Updates the user's password with a new hash.
|
|
71
|
+
* Invalidates all existing sessions for security.
|
|
72
|
+
* Deletes the used reset token.
|
|
73
|
+
*
|
|
74
|
+
* @param db - Kysely database instance
|
|
75
|
+
* @param token - Password reset token
|
|
76
|
+
* @param newPassword - New password (plain text, will be hashed)
|
|
77
|
+
* @returns Success status
|
|
78
|
+
* @throws {AuthError} If token is invalid, expired, or password is invalid
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const result = await resetPassword(db, 'abc123...', 'NewSecurePass123!')
|
|
83
|
+
* if (result.success) {
|
|
84
|
+
* console.log('Password reset successful')
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function resetPassword(db: Kysely<Database>, token: string, newPassword: string, passwordHasher?: PasswordHasher): Promise<{
|
|
89
|
+
success: boolean;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Clean up expired password reset tokens
|
|
93
|
+
*
|
|
94
|
+
* Removes all expired password reset tokens from the database.
|
|
95
|
+
* This should be run periodically as a background job.
|
|
96
|
+
*
|
|
97
|
+
* @param db - Kysely database instance
|
|
98
|
+
* @returns Number of tokens deleted
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const deleted = await cleanupExpiredResetTokens(db)
|
|
103
|
+
* console.log(`Cleaned up ${deleted} expired reset tokens`)
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function cleanupExpiredResetTokens(db: Kysely<Database>): Promise<number>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password Reset
|
|
3
|
+
*
|
|
4
|
+
* Handles password reset flow:
|
|
5
|
+
* - Password reset token generation and storage
|
|
6
|
+
* - Token validation and expiration checking
|
|
7
|
+
* - Password update with new hash
|
|
8
|
+
* - Session invalidation for security
|
|
9
|
+
*/
|
|
10
|
+
import { deleteAllUserSessions } from '../oauth/callbacks.js';
|
|
11
|
+
import { generateSecureToken, normalizeEmail, validatePassword, createAuthError } from './utils.js';
|
|
12
|
+
import { isValidPasswordResetToken } from '../database/schema.js';
|
|
13
|
+
import { createPbkdf2PasswordHasher } from '../password-hasher.js';
|
|
14
|
+
/**
|
|
15
|
+
* Password reset token expiration (1 hour for security)
|
|
16
|
+
*/
|
|
17
|
+
const RESET_TOKEN_EXPIRY = 60 * 60 * 1000; // 1 hour in milliseconds
|
|
18
|
+
const defaultPasswordHasher = createPbkdf2PasswordHasher();
|
|
19
|
+
/**
|
|
20
|
+
* Request password reset
|
|
21
|
+
*
|
|
22
|
+
* Generates a password reset token and stores it in the database.
|
|
23
|
+
* The token expires after 1 hour for security.
|
|
24
|
+
*
|
|
25
|
+
* **IMPORTANT:** This function does NOT return the token to prevent email enumeration.
|
|
26
|
+
* The token should be sent via email by the caller using an email service.
|
|
27
|
+
* The function always returns success, even if the user doesn't exist.
|
|
28
|
+
*
|
|
29
|
+
* @param db - Kysely database instance
|
|
30
|
+
* @param email - User's email address
|
|
31
|
+
* @param onTokenGenerated - Optional callback to send the token via email
|
|
32
|
+
* @returns Success status and email (for sending the token)
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const result = await requestPasswordReset(db, 'user@example.com', async (email, token) => {
|
|
37
|
+
* await sendEmail({
|
|
38
|
+
* to: email,
|
|
39
|
+
* subject: 'Password Reset',
|
|
40
|
+
* template: 'password-reset',
|
|
41
|
+
* data: { token, resetUrl: `https://example.com/reset-password?token=${token}` }
|
|
42
|
+
* })
|
|
43
|
+
* })
|
|
44
|
+
* // Always returns { success: true } regardless of whether user exists
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export async function requestPasswordReset(db, email, onTokenGenerated) {
|
|
48
|
+
const normalizedEmail = normalizeEmail(email);
|
|
49
|
+
// Look up user by email
|
|
50
|
+
const user = await db
|
|
51
|
+
.selectFrom('users')
|
|
52
|
+
.select(['id', 'email', 'password_hash'])
|
|
53
|
+
.where('email', '=', normalizedEmail)
|
|
54
|
+
.executeTakeFirst();
|
|
55
|
+
// If user doesn't exist, return success but don't send email
|
|
56
|
+
// This prevents email enumeration attacks
|
|
57
|
+
if (!user) {
|
|
58
|
+
// Simulate work to prevent timing attacks
|
|
59
|
+
await generateSecureToken(32);
|
|
60
|
+
return { success: true, email: normalizedEmail };
|
|
61
|
+
}
|
|
62
|
+
// If user exists but has no password (OAuth-only), don't send reset email
|
|
63
|
+
// Return success to avoid revealing whether email exists
|
|
64
|
+
if (!user.password_hash) {
|
|
65
|
+
return { success: true, email: normalizedEmail };
|
|
66
|
+
}
|
|
67
|
+
// Delete any existing reset tokens for this user
|
|
68
|
+
await db.deleteFrom('password_reset_tokens').where('user_id', '=', user.id).execute();
|
|
69
|
+
// Generate password reset token
|
|
70
|
+
const resetToken = generateSecureToken(32); // 256 bits of entropy
|
|
71
|
+
const expiresAt = new Date(Date.now() + RESET_TOKEN_EXPIRY);
|
|
72
|
+
const newToken = {
|
|
73
|
+
token: resetToken,
|
|
74
|
+
user_id: user.id,
|
|
75
|
+
expires_at: expiresAt,
|
|
76
|
+
};
|
|
77
|
+
await db.insertInto('password_reset_tokens').values(newToken).execute();
|
|
78
|
+
// Call the optional callback to send the token via email
|
|
79
|
+
if (onTokenGenerated) {
|
|
80
|
+
await onTokenGenerated(normalizedEmail, resetToken);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
email: normalizedEmail,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Verify password reset token
|
|
89
|
+
*
|
|
90
|
+
* Checks if a password reset token is valid and not expired.
|
|
91
|
+
* Does not consume the token.
|
|
92
|
+
*
|
|
93
|
+
* @param db - Kysely database instance
|
|
94
|
+
* @param token - Password reset token
|
|
95
|
+
* @returns Validation result with user ID if valid
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const result = await verifyResetToken(db, 'abc123...')
|
|
100
|
+
* if (result.valid) {
|
|
101
|
+
* console.log('Token is valid for user:', result.userId)
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export async function verifyResetToken(db, token) {
|
|
106
|
+
if (!token || token.trim() === '') {
|
|
107
|
+
return { valid: false };
|
|
108
|
+
}
|
|
109
|
+
// Look up token in database
|
|
110
|
+
const tokenRecord = await db
|
|
111
|
+
.selectFrom('password_reset_tokens')
|
|
112
|
+
.selectAll()
|
|
113
|
+
.where('token', '=', token)
|
|
114
|
+
.executeTakeFirst();
|
|
115
|
+
if (!tokenRecord) {
|
|
116
|
+
return { valid: false };
|
|
117
|
+
}
|
|
118
|
+
// Check if token is expired
|
|
119
|
+
if (!isValidPasswordResetToken(tokenRecord)) {
|
|
120
|
+
// Delete expired token
|
|
121
|
+
await db.deleteFrom('password_reset_tokens').where('token', '=', token).execute();
|
|
122
|
+
return { valid: false };
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
valid: true,
|
|
126
|
+
userId: tokenRecord.user_id,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Reset password using token
|
|
131
|
+
*
|
|
132
|
+
* Updates the user's password with a new hash.
|
|
133
|
+
* Invalidates all existing sessions for security.
|
|
134
|
+
* Deletes the used reset token.
|
|
135
|
+
*
|
|
136
|
+
* @param db - Kysely database instance
|
|
137
|
+
* @param token - Password reset token
|
|
138
|
+
* @param newPassword - New password (plain text, will be hashed)
|
|
139
|
+
* @returns Success status
|
|
140
|
+
* @throws {AuthError} If token is invalid, expired, or password is invalid
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* const result = await resetPassword(db, 'abc123...', 'NewSecurePass123!')
|
|
145
|
+
* if (result.success) {
|
|
146
|
+
* console.log('Password reset successful')
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export async function resetPassword(db, token, newPassword, passwordHasher) {
|
|
151
|
+
const hasher = passwordHasher ?? defaultPasswordHasher;
|
|
152
|
+
if (!token || token.trim() === '') {
|
|
153
|
+
throw createAuthError('Reset token is required', 'INVALID_TOKEN', 400);
|
|
154
|
+
}
|
|
155
|
+
// Validate new password
|
|
156
|
+
const passwordError = validatePassword(newPassword);
|
|
157
|
+
if (passwordError) {
|
|
158
|
+
throw createAuthError(passwordError, 'INVALID_PASSWORD', 400);
|
|
159
|
+
}
|
|
160
|
+
// Look up token in database
|
|
161
|
+
const tokenRecord = await db
|
|
162
|
+
.selectFrom('password_reset_tokens')
|
|
163
|
+
.selectAll()
|
|
164
|
+
.where('token', '=', token)
|
|
165
|
+
.executeTakeFirst();
|
|
166
|
+
if (!tokenRecord) {
|
|
167
|
+
throw createAuthError('Invalid or expired reset token', 'INVALID_TOKEN', 400);
|
|
168
|
+
}
|
|
169
|
+
// Check if token is expired
|
|
170
|
+
if (!isValidPasswordResetToken(tokenRecord)) {
|
|
171
|
+
// Delete expired token
|
|
172
|
+
await db.deleteFrom('password_reset_tokens').where('token', '=', token).execute();
|
|
173
|
+
throw createAuthError('Reset token has expired', 'TOKEN_EXPIRED', 400);
|
|
174
|
+
}
|
|
175
|
+
// Hash new password
|
|
176
|
+
const passwordHash = await hasher.hash(newPassword);
|
|
177
|
+
// Update user's password
|
|
178
|
+
await db
|
|
179
|
+
.updateTable('users')
|
|
180
|
+
.set({ password_hash: passwordHash })
|
|
181
|
+
.where('id', '=', tokenRecord.user_id)
|
|
182
|
+
.execute();
|
|
183
|
+
// Delete used reset token
|
|
184
|
+
await db.deleteFrom('password_reset_tokens').where('token', '=', token).execute();
|
|
185
|
+
// Invalidate all sessions for security
|
|
186
|
+
await deleteAllUserSessions(db, tokenRecord.user_id);
|
|
187
|
+
return {
|
|
188
|
+
success: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Clean up expired password reset tokens
|
|
193
|
+
*
|
|
194
|
+
* Removes all expired password reset tokens from the database.
|
|
195
|
+
* This should be run periodically as a background job.
|
|
196
|
+
*
|
|
197
|
+
* @param db - Kysely database instance
|
|
198
|
+
* @returns Number of tokens deleted
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```ts
|
|
202
|
+
* const deleted = await cleanupExpiredResetTokens(db)
|
|
203
|
+
* console.log(`Cleaned up ${deleted} expired reset tokens`)
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export async function cleanupExpiredResetTokens(db) {
|
|
207
|
+
const result = await db
|
|
208
|
+
.deleteFrom('password_reset_tokens')
|
|
209
|
+
.where('expires_at', '<=', new Date())
|
|
210
|
+
.executeTakeFirst();
|
|
211
|
+
return Number(result.numDeletedRows ?? 0);
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=reset-password.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset-password.js","sourceRoot":"","sources":["../../src/auth/reset-password.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for email/password authentication including:
|
|
5
|
+
* - Email validation
|
|
6
|
+
* - Password validation
|
|
7
|
+
* - Secure token generation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Validate email format
|
|
11
|
+
*
|
|
12
|
+
* @param email - Email address to validate
|
|
13
|
+
* @returns True if valid, false otherwise
|
|
14
|
+
*/
|
|
15
|
+
export declare function isValidEmail(email: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Validate password strength
|
|
18
|
+
*
|
|
19
|
+
* @param password - Password to validate
|
|
20
|
+
* @param minLength - Minimum password length (default: 8)
|
|
21
|
+
* @returns Error message if invalid, null if valid
|
|
22
|
+
*/
|
|
23
|
+
export declare function validatePassword(password: string, minLength?: number): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Generate a secure random token
|
|
26
|
+
*
|
|
27
|
+
* Uses cryptographically secure random values from Web Crypto API.
|
|
28
|
+
*
|
|
29
|
+
* @param entropySize - Number of bytes of entropy (default: 32)
|
|
30
|
+
* @returns Base64url-encoded token
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const token = generateSecureToken(32) // 256 bits of entropy
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateSecureToken(entropySize?: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* Normalize email address
|
|
40
|
+
*
|
|
41
|
+
* Converts email to lowercase for consistent storage and comparison.
|
|
42
|
+
*
|
|
43
|
+
* @param email - Email address to normalize
|
|
44
|
+
* @returns Normalized email address
|
|
45
|
+
*/
|
|
46
|
+
export declare function normalizeEmail(email: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Authentication error types
|
|
49
|
+
*/
|
|
50
|
+
export declare class AuthError extends Error {
|
|
51
|
+
code: string;
|
|
52
|
+
statusCode: number;
|
|
53
|
+
constructor(message: string, code: string, statusCode?: number);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a standardized auth error
|
|
57
|
+
*/
|
|
58
|
+
export declare function createAuthError(message: string, code: string, statusCode?: number): AuthError;
|