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,121 @@
|
|
|
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
|
+
import { base64url } from 'oslo/encoding';
|
|
10
|
+
/**
|
|
11
|
+
* Email validation regex (RFC 5322 simplified)
|
|
12
|
+
*
|
|
13
|
+
* Must have:
|
|
14
|
+
* - Local part before @
|
|
15
|
+
* - @ symbol
|
|
16
|
+
* - Domain name
|
|
17
|
+
* - At least one dot (.)
|
|
18
|
+
* - TLD with at least 1 character after the dot
|
|
19
|
+
*
|
|
20
|
+
* Does NOT allow:
|
|
21
|
+
* - Consecutive dots (..)
|
|
22
|
+
* - Leading/trailing dots
|
|
23
|
+
* - @ at start
|
|
24
|
+
* - Spaces
|
|
25
|
+
*/
|
|
26
|
+
const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
|
27
|
+
/**
|
|
28
|
+
* Validate email format
|
|
29
|
+
*
|
|
30
|
+
* @param email - Email address to validate
|
|
31
|
+
* @returns True if valid, false otherwise
|
|
32
|
+
*/
|
|
33
|
+
export function isValidEmail(email) {
|
|
34
|
+
if (!email || email.length > 254) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Check for spaces (not allowed in email addresses)
|
|
38
|
+
if (email.includes(' ')) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const trimmedEmail = email.toLowerCase().trim();
|
|
42
|
+
// Check for consecutive dots
|
|
43
|
+
if (trimmedEmail.includes('..')) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
// Check for leading/trailing dots
|
|
47
|
+
const localPart = trimmedEmail.split('@')[0];
|
|
48
|
+
if (localPart?.startsWith('.') || localPart?.endsWith('.')) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return EMAIL_REGEX.test(trimmedEmail);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate password strength
|
|
55
|
+
*
|
|
56
|
+
* @param password - Password to validate
|
|
57
|
+
* @param minLength - Minimum password length (default: 8)
|
|
58
|
+
* @returns Error message if invalid, null if valid
|
|
59
|
+
*/
|
|
60
|
+
export function validatePassword(password, minLength = 8) {
|
|
61
|
+
if (!password || password.length === 0) {
|
|
62
|
+
return `Password must be at least ${minLength} characters long`;
|
|
63
|
+
}
|
|
64
|
+
if (password.length < minLength) {
|
|
65
|
+
return `Password must be at least ${minLength} characters long`;
|
|
66
|
+
}
|
|
67
|
+
// Optional: Add more password strength requirements
|
|
68
|
+
// - At least one uppercase letter
|
|
69
|
+
// - At least one lowercase letter
|
|
70
|
+
// - At least one number
|
|
71
|
+
// - At least one special character
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generate a secure random token
|
|
76
|
+
*
|
|
77
|
+
* Uses cryptographically secure random values from Web Crypto API.
|
|
78
|
+
*
|
|
79
|
+
* @param entropySize - Number of bytes of entropy (default: 32)
|
|
80
|
+
* @returns Base64url-encoded token
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const token = generateSecureToken(32) // 256 bits of entropy
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function generateSecureToken(entropySize = 32) {
|
|
88
|
+
const bytes = new Uint8Array(entropySize);
|
|
89
|
+
crypto.getRandomValues(bytes);
|
|
90
|
+
// Use base64url encoder without padding
|
|
91
|
+
return base64url.encode(bytes).replace(/=/g, '');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Normalize email address
|
|
95
|
+
*
|
|
96
|
+
* Converts email to lowercase for consistent storage and comparison.
|
|
97
|
+
*
|
|
98
|
+
* @param email - Email address to normalize
|
|
99
|
+
* @returns Normalized email address
|
|
100
|
+
*/
|
|
101
|
+
export function normalizeEmail(email) {
|
|
102
|
+
return email.toLowerCase().trim();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Authentication error types
|
|
106
|
+
*/
|
|
107
|
+
export class AuthError extends Error {
|
|
108
|
+
constructor(message, code, statusCode = 400) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.code = code;
|
|
111
|
+
this.statusCode = statusCode;
|
|
112
|
+
this.name = 'AuthError';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create a standardized auth error
|
|
117
|
+
*/
|
|
118
|
+
export function createAuthError(message, code, statusCode = 400) {
|
|
119
|
+
return new AuthError(message, code, statusCode);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/auth/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,GAAG,sIAAsI,CAAA;AAE1J;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACjC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,oDAAoD;IACpD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;IAE/C,6BAA6B;IAC7B,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAA;IACd,CAAC;IAED,kCAAkC;IAClC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5C,IAAI,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,YAAoB,CAAC;IACtE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,6BAA6B,SAAS,kBAAkB,CAAA;IACjE,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAChC,OAAO,6BAA6B,SAAS,kBAAkB,CAAA;IACjE,CAAC;IAED,oDAAoD;IACpD,kCAAkC;IAClC,kCAAkC;IAClC,wBAAwB;IACxB,mCAAmC;IAEnC,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,cAAsB,EAAE;IAC1D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA;IAC7B,wCAAwC;IACxC,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,YACE,OAAe,EACR,IAAY,EACZ,aAAqB,GAAG;QAE/B,KAAK,CAAC,OAAO,CAAC,CAAA;QAHP,SAAI,GAAJ,IAAI,CAAQ;QACZ,eAAU,GAAV,UAAU,CAAc;QAG/B,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;IACzB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,IAAY,EACZ,aAAqB,GAAG;IAExB,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,CAAA;AACjD,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Verification
|
|
3
|
+
*
|
|
4
|
+
* Handles email verification flow:
|
|
5
|
+
* - Token validation and expiration checking
|
|
6
|
+
* - Marking user's email as verified
|
|
7
|
+
* - Token cleanup after use
|
|
8
|
+
* - Resending verification emails
|
|
9
|
+
*/
|
|
10
|
+
import type { Kysely } from 'kysely';
|
|
11
|
+
import type { Database } from '../database/schema.js';
|
|
12
|
+
/**
|
|
13
|
+
* Verify user's email address using a verification token
|
|
14
|
+
*
|
|
15
|
+
* Validates the token, checks expiration, and marks the user's email as verified.
|
|
16
|
+
* The token is deleted after successful verification.
|
|
17
|
+
*
|
|
18
|
+
* @param db - Kysely database instance
|
|
19
|
+
* @param token - Email verification token
|
|
20
|
+
* @returns Verification result with user ID if successful
|
|
21
|
+
* @throws {AuthError} If token is invalid or expired
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* const result = await verifyEmail(db, 'abc123...')
|
|
26
|
+
* if (result.success) {
|
|
27
|
+
* console.log('Email verified for user:', result.userId)
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyEmail(db: Kysely<Database>, token: string): Promise<{
|
|
32
|
+
success: boolean;
|
|
33
|
+
userId?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Resend verification email
|
|
37
|
+
*
|
|
38
|
+
* Generates a new verification token for the user with the given email.
|
|
39
|
+
* Deletes any existing tokens for that user before creating a new one.
|
|
40
|
+
*
|
|
41
|
+
* @param db - Kysely database instance
|
|
42
|
+
* @param email - User's email address
|
|
43
|
+
* @returns New verification token
|
|
44
|
+
* @throws {AuthError} If user not found or email already verified
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const { token } = await resendVerificationEmail(db, 'user@example.com')
|
|
49
|
+
* // Send token to user via email
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function resendVerificationEmail(db: Kysely<Database>, email: string): Promise<{
|
|
53
|
+
token: string;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Clean up expired verification tokens
|
|
57
|
+
*
|
|
58
|
+
* Removes all expired email verification tokens from the database.
|
|
59
|
+
* This should be run periodically as a background job.
|
|
60
|
+
*
|
|
61
|
+
* @param db - Kysely database instance
|
|
62
|
+
* @returns Number of tokens deleted
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const deleted = await cleanupExpiredVerificationTokens(db)
|
|
67
|
+
* console.log(`Cleaned up ${deleted} expired verification tokens`)
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function cleanupExpiredVerificationTokens(db: Kysely<Database>): Promise<number>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Verification
|
|
3
|
+
*
|
|
4
|
+
* Handles email verification flow:
|
|
5
|
+
* - Token validation and expiration checking
|
|
6
|
+
* - Marking user's email as verified
|
|
7
|
+
* - Token cleanup after use
|
|
8
|
+
* - Resending verification emails
|
|
9
|
+
*/
|
|
10
|
+
import { generateSecureToken, createAuthError, normalizeEmail } from './utils.js';
|
|
11
|
+
import { isValidEmailVerificationToken } from '../database/schema.js';
|
|
12
|
+
/**
|
|
13
|
+
* Email verification token expiration (24 hours)
|
|
14
|
+
*/
|
|
15
|
+
const VERIFICATION_TOKEN_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
16
|
+
/**
|
|
17
|
+
* Verify user's email address using a verification token
|
|
18
|
+
*
|
|
19
|
+
* Validates the token, checks expiration, and marks the user's email as verified.
|
|
20
|
+
* The token is deleted after successful verification.
|
|
21
|
+
*
|
|
22
|
+
* @param db - Kysely database instance
|
|
23
|
+
* @param token - Email verification token
|
|
24
|
+
* @returns Verification result with user ID if successful
|
|
25
|
+
* @throws {AuthError} If token is invalid or expired
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const result = await verifyEmail(db, 'abc123...')
|
|
30
|
+
* if (result.success) {
|
|
31
|
+
* console.log('Email verified for user:', result.userId)
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export async function verifyEmail(db, token) {
|
|
36
|
+
if (!token || token.trim() === '') {
|
|
37
|
+
throw createAuthError('Verification token is required', 'INVALID_TOKEN', 400);
|
|
38
|
+
}
|
|
39
|
+
// Look up token in database
|
|
40
|
+
const tokenRecord = await db
|
|
41
|
+
.selectFrom('email_verification_tokens')
|
|
42
|
+
.selectAll()
|
|
43
|
+
.where('token', '=', token)
|
|
44
|
+
.executeTakeFirst();
|
|
45
|
+
if (!tokenRecord) {
|
|
46
|
+
throw createAuthError('Invalid verification token', 'INVALID_TOKEN', 400);
|
|
47
|
+
}
|
|
48
|
+
// Check if token is expired
|
|
49
|
+
if (!isValidEmailVerificationToken(tokenRecord)) {
|
|
50
|
+
// Delete expired token
|
|
51
|
+
await db.deleteFrom('email_verification_tokens').where('token', '=', token).execute();
|
|
52
|
+
throw createAuthError('Verification token has expired', 'TOKEN_EXPIRED', 400);
|
|
53
|
+
}
|
|
54
|
+
// Mark user's email as verified
|
|
55
|
+
await db
|
|
56
|
+
.updateTable('users')
|
|
57
|
+
.set({ email_verified: true })
|
|
58
|
+
.where('id', '=', tokenRecord.user_id)
|
|
59
|
+
.execute();
|
|
60
|
+
// Delete used verification token
|
|
61
|
+
await db.deleteFrom('email_verification_tokens').where('token', '=', token).execute();
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
userId: tokenRecord.user_id,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resend verification email
|
|
69
|
+
*
|
|
70
|
+
* Generates a new verification token for the user with the given email.
|
|
71
|
+
* Deletes any existing tokens for that user before creating a new one.
|
|
72
|
+
*
|
|
73
|
+
* @param db - Kysely database instance
|
|
74
|
+
* @param email - User's email address
|
|
75
|
+
* @returns New verification token
|
|
76
|
+
* @throws {AuthError} If user not found or email already verified
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const { token } = await resendVerificationEmail(db, 'user@example.com')
|
|
81
|
+
* // Send token to user via email
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export async function resendVerificationEmail(db, email) {
|
|
85
|
+
const normalizedEmail = normalizeEmail(email);
|
|
86
|
+
// Look up user by email
|
|
87
|
+
const user = await db
|
|
88
|
+
.selectFrom('users')
|
|
89
|
+
.select(['id', 'email', 'email_verified'])
|
|
90
|
+
.where('email', '=', normalizedEmail)
|
|
91
|
+
.executeTakeFirst();
|
|
92
|
+
if (!user) {
|
|
93
|
+
// Don't reveal whether email exists (security)
|
|
94
|
+
throw createAuthError('If this email exists, a verification link will be sent', 'EMAIL_SENT', 200);
|
|
95
|
+
}
|
|
96
|
+
if (user.email_verified) {
|
|
97
|
+
throw createAuthError('Email is already verified', 'ALREADY_VERIFIED', 400);
|
|
98
|
+
}
|
|
99
|
+
// Delete any existing verification tokens for this user
|
|
100
|
+
await db.deleteFrom('email_verification_tokens').where('user_id', '=', user.id).execute();
|
|
101
|
+
// Generate new verification token
|
|
102
|
+
const verificationToken = generateSecureToken(32); // 256 bits of entropy
|
|
103
|
+
const expiresAt = new Date(Date.now() + VERIFICATION_TOKEN_EXPIRY);
|
|
104
|
+
const newToken = {
|
|
105
|
+
token: verificationToken,
|
|
106
|
+
user_id: user.id,
|
|
107
|
+
email: normalizedEmail,
|
|
108
|
+
expires_at: expiresAt,
|
|
109
|
+
};
|
|
110
|
+
await db.insertInto('email_verification_tokens').values(newToken).execute();
|
|
111
|
+
return {
|
|
112
|
+
token: verificationToken,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Clean up expired verification tokens
|
|
117
|
+
*
|
|
118
|
+
* Removes all expired email verification tokens from the database.
|
|
119
|
+
* This should be run periodically as a background job.
|
|
120
|
+
*
|
|
121
|
+
* @param db - Kysely database instance
|
|
122
|
+
* @returns Number of tokens deleted
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* const deleted = await cleanupExpiredVerificationTokens(db)
|
|
127
|
+
* console.log(`Cleaned up ${deleted} expired verification tokens`)
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export async function cleanupExpiredVerificationTokens(db) {
|
|
131
|
+
const result = await db
|
|
132
|
+
.deleteFrom('email_verification_tokens')
|
|
133
|
+
.where('expires_at', '<=', new Date())
|
|
134
|
+
.executeTakeFirst();
|
|
135
|
+
return Number(result.numDeletedRows ?? 0);
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=verify-email.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-email.js","sourceRoot":"","sources":["../../src/auth/verify-email.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory function to create a complete ClearAuth configuration
|
|
3
|
+
*
|
|
4
|
+
* This module provides a convenient way to create a fully configured ClearAuthConfig
|
|
5
|
+
* with sensible defaults for session management, cookies, and security settings.
|
|
6
|
+
*
|
|
7
|
+
* @module createClearAuth
|
|
8
|
+
*/
|
|
9
|
+
import { type MechKyselyConfig } from "./mech-kysely.js";
|
|
10
|
+
import type { ClearAuthConfig } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default session configuration
|
|
13
|
+
* - 7 day expiration
|
|
14
|
+
* - Secure cookies in production
|
|
15
|
+
* - SameSite: lax
|
|
16
|
+
*/
|
|
17
|
+
export declare const defaultSessionConfig: {
|
|
18
|
+
readonly expiresIn: number;
|
|
19
|
+
readonly cookie: {
|
|
20
|
+
readonly name: "session";
|
|
21
|
+
readonly httpOnly: true;
|
|
22
|
+
readonly sameSite: "lax";
|
|
23
|
+
readonly path: "/";
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Short-lived session configuration (for high-security apps)
|
|
28
|
+
* - 1 hour expiration
|
|
29
|
+
* - Strict cookies
|
|
30
|
+
*/
|
|
31
|
+
export declare const shortSessionConfig: {
|
|
32
|
+
readonly expiresIn: number;
|
|
33
|
+
readonly cookie: {
|
|
34
|
+
readonly name: "session";
|
|
35
|
+
readonly httpOnly: true;
|
|
36
|
+
readonly sameSite: "strict";
|
|
37
|
+
readonly path: "/";
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Long-lived session configuration (for consumer apps)
|
|
42
|
+
* - 30 day expiration
|
|
43
|
+
* - Lax cookies for better UX
|
|
44
|
+
*/
|
|
45
|
+
export declare const longSessionConfig: {
|
|
46
|
+
readonly expiresIn: number;
|
|
47
|
+
readonly cookie: {
|
|
48
|
+
readonly name: "session";
|
|
49
|
+
readonly httpOnly: true;
|
|
50
|
+
readonly sameSite: "lax";
|
|
51
|
+
readonly path: "/";
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Simplified database configuration
|
|
56
|
+
* Only appId and apiKey are required - everything else has smart defaults
|
|
57
|
+
*/
|
|
58
|
+
export type SimpleDatabaseConfig = {
|
|
59
|
+
/** Mech App ID (required) */
|
|
60
|
+
appId: string;
|
|
61
|
+
/** Mech API Key (required) */
|
|
62
|
+
apiKey: string;
|
|
63
|
+
/** Base URL for Mech Storage. Defaults to "https://storage.mechdna.net" */
|
|
64
|
+
baseUrl?: string;
|
|
65
|
+
/** App Schema ID. Defaults to appId */
|
|
66
|
+
appSchemaId?: string;
|
|
67
|
+
/** Request timeout in ms. Defaults to 30000 */
|
|
68
|
+
timeout?: number;
|
|
69
|
+
/** Max retry attempts. Defaults to 2 */
|
|
70
|
+
maxRetries?: number;
|
|
71
|
+
};
|
|
72
|
+
export type CreateClearAuthOptions = {
|
|
73
|
+
/** Secret key for session signing (required) */
|
|
74
|
+
secret: string;
|
|
75
|
+
/**
|
|
76
|
+
* Database configuration. Can be:
|
|
77
|
+
* - Simplified: { appId, apiKey } (recommended)
|
|
78
|
+
* - Full config: { config: MechKyselyConfig }
|
|
79
|
+
*/
|
|
80
|
+
database: SimpleDatabaseConfig | {
|
|
81
|
+
config: MechKyselyConfig;
|
|
82
|
+
};
|
|
83
|
+
/** Base URL for your application (required for OAuth redirects) */
|
|
84
|
+
baseUrl: string;
|
|
85
|
+
/** Set to true if running in production */
|
|
86
|
+
isProduction?: boolean;
|
|
87
|
+
/** Session configuration (optional, uses defaults if not provided) */
|
|
88
|
+
session?: ClearAuthConfig['session'];
|
|
89
|
+
/** OAuth provider configuration (optional) */
|
|
90
|
+
oauth?: ClearAuthConfig['oauth'];
|
|
91
|
+
/** Password validation configuration (optional) */
|
|
92
|
+
password?: ClearAuthConfig['password'];
|
|
93
|
+
/** Password hashing implementation (optional) */
|
|
94
|
+
passwordHasher?: ClearAuthConfig['passwordHasher'];
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Create a complete ClearAuth configuration
|
|
98
|
+
*
|
|
99
|
+
* All configuration must be provided explicitly - no environment variables are read.
|
|
100
|
+
*
|
|
101
|
+
* Configuration formats:
|
|
102
|
+
*
|
|
103
|
+
* 1. **Simplified config (recommended)**:
|
|
104
|
+
* ```ts
|
|
105
|
+
* const config = createClearAuth({
|
|
106
|
+
* secret: "your-secret-key",
|
|
107
|
+
* baseUrl: "https://yourdomain.com",
|
|
108
|
+
* database: { appId: "...", apiKey: "..." },
|
|
109
|
+
* isProduction: true,
|
|
110
|
+
* oauth: {
|
|
111
|
+
* github: {
|
|
112
|
+
* clientId: env.GITHUB_CLIENT_ID,
|
|
113
|
+
* clientSecret: env.GITHUB_CLIENT_SECRET,
|
|
114
|
+
* redirectUri: 'https://yourdomain.com/auth/callback/github',
|
|
115
|
+
* },
|
|
116
|
+
* },
|
|
117
|
+
* })
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* 2. **With session presets**:
|
|
121
|
+
* ```ts
|
|
122
|
+
* const config = createClearAuth({
|
|
123
|
+
* secret: "your-secret-key",
|
|
124
|
+
* baseUrl: "https://yourdomain.com",
|
|
125
|
+
* database: { appId: "...", apiKey: "..." },
|
|
126
|
+
* session: longSessionConfig, // Use 30-day sessions
|
|
127
|
+
* })
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @param options - Configuration options
|
|
131
|
+
* @returns A configured ClearAuthConfig object
|
|
132
|
+
* @throws ClearAuthConfigError if required config is missing or invalid
|
|
133
|
+
*
|
|
134
|
+
* @example Cloudflare Workers setup
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { createClearAuth, defaultSessionConfig } from 'clearauth'
|
|
137
|
+
*
|
|
138
|
+
* const config = createClearAuth({
|
|
139
|
+
* secret: env.AUTH_SECRET,
|
|
140
|
+
* baseUrl: 'https://yourdomain.com',
|
|
141
|
+
* database: {
|
|
142
|
+
* appId: env.MECH_APP_ID,
|
|
143
|
+
* apiKey: env.MECH_API_KEY,
|
|
144
|
+
* },
|
|
145
|
+
* isProduction: true,
|
|
146
|
+
* session: defaultSessionConfig,
|
|
147
|
+
* oauth: {
|
|
148
|
+
* github: {
|
|
149
|
+
* clientId: env.GITHUB_CLIENT_ID,
|
|
150
|
+
* clientSecret: env.GITHUB_CLIENT_SECRET,
|
|
151
|
+
* redirectUri: 'https://yourdomain.com/auth/callback/github',
|
|
152
|
+
* },
|
|
153
|
+
* },
|
|
154
|
+
* })
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @example Next.js setup
|
|
158
|
+
* ```ts
|
|
159
|
+
* import { createClearAuth } from 'clearauth'
|
|
160
|
+
*
|
|
161
|
+
* export const authConfig = createClearAuth({
|
|
162
|
+
* secret: process.env.AUTH_SECRET!,
|
|
163
|
+
* baseUrl: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
164
|
+
* database: {
|
|
165
|
+
* appId: process.env.MECH_APP_ID!,
|
|
166
|
+
* apiKey: process.env.MECH_API_KEY!,
|
|
167
|
+
* },
|
|
168
|
+
* oauth: {
|
|
169
|
+
* github: {
|
|
170
|
+
* clientId: process.env.GITHUB_CLIENT_ID!,
|
|
171
|
+
* clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
172
|
+
* redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/auth/callback/github`,
|
|
173
|
+
* },
|
|
174
|
+
* },
|
|
175
|
+
* })
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export declare function createClearAuth(options: CreateClearAuthOptions): ClearAuthConfig;
|