@syncello/auth 2.5.1
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/README.md +187 -0
- package/dist/chunk-OZ26T3ZA.js +263 -0
- package/dist/chunk-OZ26T3ZA.js.map +1 -0
- package/dist/cli/index.js +1100 -0
- package/dist/index.cjs +5751 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1339 -0
- package/dist/index.d.ts +1339 -0
- package/dist/index.js +5359 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/index.cjs +292 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.cts +671 -0
- package/dist/schema/index.d.ts +671 -0
- package/dist/schema/index.js +13 -0
- package/dist/schema/index.js.map +1 -0
- package/package.json +82 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
export { ColumnDefinition, ColumnType, EnumDefinition, EnumName, IndexDefinition, SCHEMA_VERSION, TableDefinition, TableName, enums, tableNames, tables } from './schema/index.js';
|
|
2
|
+
import { PgTableWithColumns, PgTable } from 'drizzle-orm/pg-core';
|
|
3
|
+
import * as hono from 'hono';
|
|
4
|
+
import { Context } from 'hono';
|
|
5
|
+
import { InferInsertModel, InferSelectModel } from 'drizzle-orm';
|
|
6
|
+
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
7
|
+
import * as hono_utils_types from 'hono/utils/types';
|
|
8
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hash a password using pepper-hardened bcrypt
|
|
12
|
+
* Flow: Normalize (NFKC) → HMAC-SHA256 (pepper) → Base64 → bcrypt
|
|
13
|
+
* @param password - Plain text password
|
|
14
|
+
* @param pepper - Secret pepper key (from env.PASSWORD_PEPPER_V1)
|
|
15
|
+
* @returns Hashed password
|
|
16
|
+
*/
|
|
17
|
+
declare function hashPassword(password: string, pepper: string): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Verify a password against its hash using pepper-hardened bcrypt
|
|
20
|
+
* @param password - Plain text password to verify
|
|
21
|
+
* @param hash - Stored bcrypt hash
|
|
22
|
+
* @param pepper - Secret pepper key (from env.PASSWORD_PEPPER_V1 or V2)
|
|
23
|
+
* @returns True if password matches
|
|
24
|
+
*/
|
|
25
|
+
declare function verifyPassword(password: string, hash: string, pepper: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Verify password with pepper rotation support
|
|
28
|
+
* Tries current pepper first, then falls back to previous pepper if provided
|
|
29
|
+
* @param password - Plain text password to verify
|
|
30
|
+
* @param hash - Stored bcrypt hash
|
|
31
|
+
* @param currentPepper - Current pepper (v1)
|
|
32
|
+
* @param previousPepper - Previous pepper (v2) for rotation support (optional)
|
|
33
|
+
* @returns Object with verification result and which pepper succeeded
|
|
34
|
+
*/
|
|
35
|
+
declare function verifyPasswordWithRotation(password: string, hash: string, currentPepper: string, previousPepper?: string): Promise<{
|
|
36
|
+
verified: boolean;
|
|
37
|
+
usedPreviousPepper: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Validate password meets security requirements
|
|
41
|
+
* OWASP 2024: Focus on length over complexity
|
|
42
|
+
* @param password - Password to validate
|
|
43
|
+
* @returns Validation result
|
|
44
|
+
*/
|
|
45
|
+
declare function validatePassword(password: string): {
|
|
46
|
+
valid: boolean;
|
|
47
|
+
error?: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Check if password has been compromised using Have I Been Pwned API
|
|
51
|
+
* Uses k-anonymity model - only first 5 chars of SHA-1 hash are sent
|
|
52
|
+
* @param password - Password to check
|
|
53
|
+
* @returns True if password found in breach database
|
|
54
|
+
*/
|
|
55
|
+
declare function checkBreachedPassword(password: string): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Validate password with optional breach check
|
|
58
|
+
* @param password - Password to validate
|
|
59
|
+
* @param checkBreaches - Whether to check against HIBP database (default: true)
|
|
60
|
+
* @returns Validation result with optional breach warning
|
|
61
|
+
*/
|
|
62
|
+
declare function validatePasswordWithBreachCheck(password: string, checkBreaches?: boolean): Promise<{
|
|
63
|
+
valid: boolean;
|
|
64
|
+
error?: string;
|
|
65
|
+
warning?: string;
|
|
66
|
+
}>;
|
|
67
|
+
|
|
68
|
+
type DrizzleDB$2 = any;
|
|
69
|
+
|
|
70
|
+
type SessionsTable = PgTableWithColumns<{
|
|
71
|
+
name: string;
|
|
72
|
+
schema: undefined;
|
|
73
|
+
columns: {
|
|
74
|
+
id: any;
|
|
75
|
+
userId: any;
|
|
76
|
+
expiresAt: any;
|
|
77
|
+
createdAt: any;
|
|
78
|
+
lastActiveAt: any;
|
|
79
|
+
fingerprint: any;
|
|
80
|
+
ipAddress: any;
|
|
81
|
+
};
|
|
82
|
+
dialect: 'pg';
|
|
83
|
+
}>;
|
|
84
|
+
type UsersTable$4 = PgTableWithColumns<{
|
|
85
|
+
name: string;
|
|
86
|
+
schema: undefined;
|
|
87
|
+
columns: {
|
|
88
|
+
id: any;
|
|
89
|
+
email: any;
|
|
90
|
+
emailVerified: any;
|
|
91
|
+
sessionVersion: any;
|
|
92
|
+
createdAt: any;
|
|
93
|
+
updatedAt: any;
|
|
94
|
+
};
|
|
95
|
+
dialect: 'pg';
|
|
96
|
+
}>;
|
|
97
|
+
interface SessionTables {
|
|
98
|
+
sessions: SessionsTable;
|
|
99
|
+
users: UsersTable$4;
|
|
100
|
+
}
|
|
101
|
+
interface SessionData {
|
|
102
|
+
user: {
|
|
103
|
+
id: string;
|
|
104
|
+
email: string;
|
|
105
|
+
emailVerified: boolean;
|
|
106
|
+
sessionVersion: number;
|
|
107
|
+
createdAt: Date;
|
|
108
|
+
updatedAt: Date;
|
|
109
|
+
};
|
|
110
|
+
session: {
|
|
111
|
+
id: string;
|
|
112
|
+
userId: string;
|
|
113
|
+
expiresAt: Date;
|
|
114
|
+
createdAt: Date;
|
|
115
|
+
lastActiveAt: Date | null;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Create a new session for a user
|
|
120
|
+
* @param db - Drizzle database instance
|
|
121
|
+
* @param tables - Session tables (sessions)
|
|
122
|
+
* @param userId - User ID
|
|
123
|
+
* @param fingerprint - Browser fingerprint (optional)
|
|
124
|
+
* @param ipAddress - Client IP address (optional)
|
|
125
|
+
* @param sessionTtlMs - Session TTL in milliseconds (optional)
|
|
126
|
+
* @returns Session ID
|
|
127
|
+
*/
|
|
128
|
+
declare function createSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, userId: string, fingerprint?: string, ipAddress?: string, sessionTtlMs?: number): Promise<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Validate a session and return user data
|
|
131
|
+
* @param db - Drizzle database instance
|
|
132
|
+
* @param tables - Session tables (sessions, users)
|
|
133
|
+
* @param sessionId - Session ID from cookie
|
|
134
|
+
* @param currentFingerprint - Current browser fingerprint (optional)
|
|
135
|
+
* @returns Session data or null if invalid
|
|
136
|
+
*/
|
|
137
|
+
declare function validateSession(db: DrizzleDB$2, tables: SessionTables, sessionId: string, currentFingerprint?: string): Promise<SessionData | null>;
|
|
138
|
+
/**
|
|
139
|
+
* Refresh session expiration (sliding window)
|
|
140
|
+
* @param db - Drizzle database instance
|
|
141
|
+
* @param tables - Session tables (sessions)
|
|
142
|
+
* @param sessionId - Session ID
|
|
143
|
+
* @param sessionTtlMs - Session TTL in milliseconds (optional)
|
|
144
|
+
*/
|
|
145
|
+
declare function refreshSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, sessionId: string, sessionTtlMs?: number): Promise<void>;
|
|
146
|
+
/**
|
|
147
|
+
* Delete a session (logout)
|
|
148
|
+
* @param db - Drizzle database instance
|
|
149
|
+
* @param tables - Session tables (sessions)
|
|
150
|
+
* @param sessionId - Session ID
|
|
151
|
+
*/
|
|
152
|
+
declare function deleteSession(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, sessionId: string): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Delete all sessions for a user (logout all devices)
|
|
155
|
+
* @param db - Drizzle database instance
|
|
156
|
+
* @param tables - Session tables (sessions)
|
|
157
|
+
* @param userId - User ID
|
|
158
|
+
*/
|
|
159
|
+
declare function deleteAllUserSessions(db: DrizzleDB$2, tables: Pick<SessionTables, 'sessions'>, userId: string): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Invalidate all sessions after password change
|
|
162
|
+
* @param db - Drizzle database instance
|
|
163
|
+
* @param tables - Session tables (sessions, users)
|
|
164
|
+
* @param userId - User ID
|
|
165
|
+
*/
|
|
166
|
+
declare function invalidateAllUserSessions(db: DrizzleDB$2, tables: SessionTables, userId: string): Promise<void>;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Generate a cryptographically secure random token
|
|
170
|
+
* @param bytes - Number of bytes (default: 64 = 128 hex chars)
|
|
171
|
+
* @returns Hex-encoded token
|
|
172
|
+
*/
|
|
173
|
+
declare function generateSecureToken(bytes?: number): string;
|
|
174
|
+
/**
|
|
175
|
+
* Hash a token using SHA-256 for storage
|
|
176
|
+
* NEVER store tokens in plain text - always hash first
|
|
177
|
+
* @param token - Token to hash
|
|
178
|
+
* @returns SHA-256 hash as hex string
|
|
179
|
+
*/
|
|
180
|
+
declare function hashToken(token: string): Promise<string>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Centralized auth configuration
|
|
184
|
+
* All hardcoded auth values should be defined here with env var overrides
|
|
185
|
+
*/
|
|
186
|
+
declare const AUTH_DEFAULTS: {
|
|
187
|
+
readonly SESSION_TTL_DAYS: 7;
|
|
188
|
+
readonly LOCKOUT_DURATION_MINUTES: 30;
|
|
189
|
+
readonly LOCKOUT_MAX_ATTEMPTS: 5;
|
|
190
|
+
readonly PASSWORD_RESET_TTL_MINUTES: 15;
|
|
191
|
+
readonly TWO_FACTOR_CHALLENGE_TTL_SECONDS: 300;
|
|
192
|
+
readonly APP_NAME: "App";
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Base type for auth tables - any Postgres table
|
|
197
|
+
*/
|
|
198
|
+
type AuthTable = PgTable;
|
|
199
|
+
/**
|
|
200
|
+
* Schema interface that consumers must provide.
|
|
201
|
+
* Each property must be a Drizzle PgTable with the expected columns.
|
|
202
|
+
* The CLI generates these tables in the consumer's project.
|
|
203
|
+
*/
|
|
204
|
+
interface AuthSchema {
|
|
205
|
+
users: AuthTable;
|
|
206
|
+
sessions: AuthTable;
|
|
207
|
+
user2faMethods: AuthTable;
|
|
208
|
+
userBackupCodes: AuthTable;
|
|
209
|
+
userTrustedDevices: AuthTable;
|
|
210
|
+
emailVerificationTokens: AuthTable;
|
|
211
|
+
passwordResetTokens: AuthTable;
|
|
212
|
+
emailChangeTokens: AuthTable;
|
|
213
|
+
emailEvents: AuthTable;
|
|
214
|
+
failedLoginAttempts: AuthTable;
|
|
215
|
+
securityAuditLog: AuthTable;
|
|
216
|
+
oauthAccounts: AuthTable;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Infer model types from a consumer's schema.
|
|
220
|
+
* These utilities let consumers extract types from their specific tables.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* import { createAuth, type InferUser } from '@syncello/auth';
|
|
225
|
+
*
|
|
226
|
+
* const auth = createAuth({ db, schema, getEnv });
|
|
227
|
+
* type User = InferUser<typeof auth.schema>;
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
type InferUser<TSchema extends AuthSchema> = InferSelectModel<TSchema['users']>;
|
|
231
|
+
type InferNewUser<TSchema extends AuthSchema> = InferInsertModel<TSchema['users']>;
|
|
232
|
+
type InferSession<TSchema extends AuthSchema> = InferSelectModel<TSchema['sessions']>;
|
|
233
|
+
type InferNewSession<TSchema extends AuthSchema> = InferInsertModel<TSchema['sessions']>;
|
|
234
|
+
/**
|
|
235
|
+
* Database interface - accepts any Drizzle Postgres database.
|
|
236
|
+
* Uses duck typing for flexibility across different Drizzle configurations.
|
|
237
|
+
*/
|
|
238
|
+
interface AuthDatabase {
|
|
239
|
+
select: <T extends PgTable>(from?: T) => unknown;
|
|
240
|
+
insert: <T extends PgTable>(into: T) => unknown;
|
|
241
|
+
update: <T extends PgTable>(table: T) => unknown;
|
|
242
|
+
delete: <T extends PgTable>(from: T) => unknown;
|
|
243
|
+
query: Record<string, unknown>;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Configuration for createAuth
|
|
247
|
+
*/
|
|
248
|
+
interface AuthConfig<TEnv = Record<string, unknown>> {
|
|
249
|
+
/** Drizzle database instance */
|
|
250
|
+
db: AuthDatabase;
|
|
251
|
+
/** Schema tables from generated auth.ts */
|
|
252
|
+
schema: AuthSchema;
|
|
253
|
+
/** Environment variables accessor */
|
|
254
|
+
getEnv: (c: Context) => TEnv;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Auth context available in all auth operations.
|
|
258
|
+
* Preserves the TEnv generic for type-safe environment access.
|
|
259
|
+
*/
|
|
260
|
+
interface AuthContext<TEnv = Record<string, unknown>> {
|
|
261
|
+
db: AuthDatabase;
|
|
262
|
+
schema: AuthSchema;
|
|
263
|
+
env: TEnv;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Creates an auth instance with injected database and schema.
|
|
267
|
+
* This follows the adapter pattern - consumers pass their db/schema TO the library.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```typescript
|
|
271
|
+
* import { createAuth } from '@syncello/auth';
|
|
272
|
+
* import { db } from './db';
|
|
273
|
+
* import * as schema from './db/schema';
|
|
274
|
+
*
|
|
275
|
+
* const auth = createAuth({
|
|
276
|
+
* db,
|
|
277
|
+
* schema: {
|
|
278
|
+
* users: schema.users,
|
|
279
|
+
* sessions: schema.sessions,
|
|
280
|
+
* user2faMethods: schema.user2faMethods,
|
|
281
|
+
* userBackupCodes: schema.userBackupCodes,
|
|
282
|
+
* userTrustedDevices: schema.userTrustedDevices,
|
|
283
|
+
* emailVerificationTokens: schema.emailVerificationTokens,
|
|
284
|
+
* passwordResetTokens: schema.passwordResetTokens,
|
|
285
|
+
* emailChangeTokens: schema.emailChangeTokens,
|
|
286
|
+
* emailEvents: schema.emailEvents,
|
|
287
|
+
* failedLoginAttempts: schema.failedLoginAttempts,
|
|
288
|
+
* securityAuditLog: schema.securityAuditLog,
|
|
289
|
+
* oauthAccounts: schema.oauthAccounts,
|
|
290
|
+
* },
|
|
291
|
+
* getEnv: (c) => c.env,
|
|
292
|
+
* });
|
|
293
|
+
*
|
|
294
|
+
* // Use context in middleware/handlers
|
|
295
|
+
* const ctx = auth.getContext(c);
|
|
296
|
+
* const user = await ctx.db.query.users.findFirst(...);
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
declare function createAuth<TEnv = Record<string, unknown>>(config: AuthConfig<TEnv>): {
|
|
300
|
+
db: AuthDatabase;
|
|
301
|
+
schema: AuthSchema;
|
|
302
|
+
getContext: (c: Context) => AuthContext<TEnv>;
|
|
303
|
+
};
|
|
304
|
+
type Auth<TEnv = Record<string, unknown>> = ReturnType<typeof createAuth<TEnv>>;
|
|
305
|
+
/**
|
|
306
|
+
* Creates a middleware that injects AuthContext into the Hono context.
|
|
307
|
+
* Apply this middleware before mounting auth routes.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* import { createAuth, createAuthMiddleware } from '@syncello/auth';
|
|
312
|
+
*
|
|
313
|
+
* const auth = createAuth({ db, schema, getEnv: (c) => c.env });
|
|
314
|
+
* const authMiddleware = createAuthMiddleware(auth);
|
|
315
|
+
*
|
|
316
|
+
* app.use('/api/auth/*', authMiddleware);
|
|
317
|
+
* app.route('/api/auth', authRoutes);
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
declare function createAuthMiddleware<TEnv = Record<string, unknown>>(auth: Auth<TEnv>): (c: Context, next: () => Promise<void>) => Promise<void>;
|
|
321
|
+
/**
|
|
322
|
+
* Helper to get AuthContext from Hono context.
|
|
323
|
+
* Use this in route handlers to access db, schema, and env.
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```typescript
|
|
327
|
+
* import { getAuthContext } from '@syncello/auth';
|
|
328
|
+
*
|
|
329
|
+
* const handler = async (c) => {
|
|
330
|
+
* const { db, schema, env } = getAuthContext(c);
|
|
331
|
+
* const user = await db.select().from(schema.users).where(...);
|
|
332
|
+
* };
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
declare function getAuthContext<TEnv = Record<string, unknown>>(c: Context): AuthContext<TEnv>;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Environment variables available in Cloudflare Workers
|
|
339
|
+
*/
|
|
340
|
+
type Env = {
|
|
341
|
+
APP_URL: string;
|
|
342
|
+
APP_NAME?: string;
|
|
343
|
+
SESSION_SECRET: string;
|
|
344
|
+
PASSWORD_PEPPER_V1: string;
|
|
345
|
+
PASSWORD_PEPPER_V2?: string;
|
|
346
|
+
RESEND_API_KEY?: string;
|
|
347
|
+
RESEND_WEBHOOK_SECRET?: string;
|
|
348
|
+
DB: {
|
|
349
|
+
connectionString: string;
|
|
350
|
+
};
|
|
351
|
+
ENVIRONMENT?: 'development' | 'staging' | 'production';
|
|
352
|
+
CORS_ORIGINS?: string;
|
|
353
|
+
CORS_PAGES_PATTERN?: string;
|
|
354
|
+
COOKIE_DOMAIN?: string;
|
|
355
|
+
SESSION_TTL_DAYS?: string;
|
|
356
|
+
LOCKOUT_DURATION_MINUTES?: string;
|
|
357
|
+
LOCKOUT_MAX_ATTEMPTS?: string;
|
|
358
|
+
PASSWORD_RESET_TTL_MINUTES?: string;
|
|
359
|
+
TWO_FACTOR_CHALLENGE_TTL_SECONDS?: string;
|
|
360
|
+
SENTRY_DSN?: string;
|
|
361
|
+
CF_VERSION_METADATA?: {
|
|
362
|
+
id: string;
|
|
363
|
+
tag: string;
|
|
364
|
+
timestamp: string;
|
|
365
|
+
};
|
|
366
|
+
ENCRYPTION_KEY_V1: string;
|
|
367
|
+
ENCRYPTION_KEY_V2?: string;
|
|
368
|
+
ENCRYPTION_ACTIVE_KEY_VERSION: string;
|
|
369
|
+
TOTP_ENCRYPTION_KEY: string;
|
|
370
|
+
GOOGLE_CLIENT_ID?: string;
|
|
371
|
+
GOOGLE_CLIENT_SECRET?: string;
|
|
372
|
+
MICROSOFT_CLIENT_ID?: string;
|
|
373
|
+
MICROSOFT_CLIENT_SECRET?: string;
|
|
374
|
+
STRIPE_SECRET_KEY?: string;
|
|
375
|
+
STRIPE_WEBHOOK_SECRET?: string;
|
|
376
|
+
STRIPE_PRICE_BASIC?: string;
|
|
377
|
+
STRIPE_PRICE_PRO?: string;
|
|
378
|
+
RATE_LIMIT_KV: KVNamespace;
|
|
379
|
+
OAUTH_STATES: KVNamespace;
|
|
380
|
+
AVATARS: R2Bucket;
|
|
381
|
+
AVATARS_PUBLIC_URL?: string;
|
|
382
|
+
CACHE_KV?: KVNamespace;
|
|
383
|
+
TURNSTILE_SECRET_KEY: string;
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* Variables set by middleware and available in route handlers
|
|
387
|
+
*/
|
|
388
|
+
type Variables = {
|
|
389
|
+
db: AuthDatabase;
|
|
390
|
+
userId?: string;
|
|
391
|
+
requestId: string;
|
|
392
|
+
traceId: string;
|
|
393
|
+
spanId: string;
|
|
394
|
+
authContext?: AuthContext;
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
declare function getSessionCookieName(env?: {
|
|
398
|
+
ENVIRONMENT?: string;
|
|
399
|
+
}): string;
|
|
400
|
+
declare function setSessionCookie(sessionId: string, env?: {
|
|
401
|
+
ENVIRONMENT?: string;
|
|
402
|
+
}): string;
|
|
403
|
+
declare function clearSessionCookie(env?: {
|
|
404
|
+
ENVIRONMENT?: string;
|
|
405
|
+
}): string;
|
|
406
|
+
declare function getTrustedDeviceCookieName(env: Env): string;
|
|
407
|
+
declare function setTrustedDeviceCookie(c: Context, env: Env, token: string): void;
|
|
408
|
+
declare function clearTrustedDeviceCookie(c: Context, env: Env): void;
|
|
409
|
+
declare function getChallengeCookieName(env: Env): string;
|
|
410
|
+
declare function setChallengeCookie(c: Context, env: Env, token: string): void;
|
|
411
|
+
declare function clearChallengeCookie(c: Context, env: Env): void;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate a browser fingerprint from request headers
|
|
415
|
+
* Uses User-Agent and Accept-Language to create a stable identifier
|
|
416
|
+
* @param request - The incoming request
|
|
417
|
+
* @returns SHA-256 hash (first 32 characters)
|
|
418
|
+
*/
|
|
419
|
+
declare function generateFingerprint(request: Request): Promise<string>;
|
|
420
|
+
/**
|
|
421
|
+
* Get client IP address from request headers
|
|
422
|
+
* Prioritizes Cloudflare's cf-connecting-ip, falls back to x-forwarded-for
|
|
423
|
+
* @param request - The incoming request
|
|
424
|
+
* @returns Client IP address or 'unknown'
|
|
425
|
+
*/
|
|
426
|
+
declare function getClientIp(request: Request): string;
|
|
427
|
+
|
|
428
|
+
type DrizzleDB$1 = any;
|
|
429
|
+
type UsersTable$3 = PgTableWithColumns<{
|
|
430
|
+
name: string;
|
|
431
|
+
schema: undefined;
|
|
432
|
+
columns: {
|
|
433
|
+
id: any;
|
|
434
|
+
lockedUntil: any;
|
|
435
|
+
failedLoginCount: any;
|
|
436
|
+
};
|
|
437
|
+
dialect: 'pg';
|
|
438
|
+
}>;
|
|
439
|
+
interface AccountLockoutTables {
|
|
440
|
+
users: UsersTable$3;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Check if an account is currently locked
|
|
444
|
+
* @param db - Drizzle database instance
|
|
445
|
+
* @param tables - Account lockout tables (users)
|
|
446
|
+
* @param userId - User ID to check
|
|
447
|
+
* @returns Lock status and unlock time if locked
|
|
448
|
+
*/
|
|
449
|
+
declare function checkAccountLocked(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string): Promise<{
|
|
450
|
+
isLocked: boolean;
|
|
451
|
+
unlockAt?: number;
|
|
452
|
+
}>;
|
|
453
|
+
/**
|
|
454
|
+
* Increment failed login attempts for a user
|
|
455
|
+
* Locks account after maxAttempts
|
|
456
|
+
* @param db - Drizzle database instance
|
|
457
|
+
* @param tables - Account lockout tables (users)
|
|
458
|
+
* @param userId - User ID
|
|
459
|
+
* @param maxAttempts - Maximum failed attempts before lockout
|
|
460
|
+
* @param lockoutDurationMs - Duration of lockout in milliseconds
|
|
461
|
+
*/
|
|
462
|
+
declare function incrementFailedAttempts(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string, maxAttempts?: number, lockoutDurationMs?: number): Promise<void>;
|
|
463
|
+
/**
|
|
464
|
+
* Clear account lockout and failed attempt counter
|
|
465
|
+
* Called after successful login
|
|
466
|
+
* @param db - Drizzle database instance
|
|
467
|
+
* @param tables - Account lockout tables (users)
|
|
468
|
+
* @param userId - User ID
|
|
469
|
+
*/
|
|
470
|
+
declare function clearAccountLockout(db: DrizzleDB$1, tables: AccountLockoutTables, userId: string): Promise<void>;
|
|
471
|
+
/**
|
|
472
|
+
* Get minutes remaining until account unlock
|
|
473
|
+
* @param unlockAt - Unlock timestamp
|
|
474
|
+
* @returns Minutes remaining (rounded up)
|
|
475
|
+
*/
|
|
476
|
+
declare function getMinutesUntilUnlock(unlockAt: number): number;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Verifies a Cloudflare Turnstile token server-side.
|
|
480
|
+
* Returns true if the token is valid, false otherwise.
|
|
481
|
+
*
|
|
482
|
+
* In staging/test environments, uses Cloudflare's test secret key that always passes.
|
|
483
|
+
* @see https://developers.cloudflare.com/turnstile/tutorials/excluding-turnstile-from-e2e-tests/
|
|
484
|
+
*/
|
|
485
|
+
declare function verifyTurnstileToken(token: string, secretKey: string, remoteIp?: string, environment?: 'development' | 'staging' | 'production'): Promise<boolean>;
|
|
486
|
+
|
|
487
|
+
type DrizzleDB = any;
|
|
488
|
+
type UsersTable$2 = PgTableWithColumns<{
|
|
489
|
+
name: string;
|
|
490
|
+
schema: undefined;
|
|
491
|
+
columns: {
|
|
492
|
+
id: any;
|
|
493
|
+
email: any;
|
|
494
|
+
hashedPassword: any;
|
|
495
|
+
updatedAt: any;
|
|
496
|
+
};
|
|
497
|
+
dialect: 'pg';
|
|
498
|
+
}>;
|
|
499
|
+
type EmailChangeTokensTable = PgTableWithColumns<{
|
|
500
|
+
name: string;
|
|
501
|
+
schema: undefined;
|
|
502
|
+
columns: {
|
|
503
|
+
id: any;
|
|
504
|
+
userId: any;
|
|
505
|
+
newEmail: any;
|
|
506
|
+
tokenHash: any;
|
|
507
|
+
cancelTokenHash: any;
|
|
508
|
+
expiresAt: any;
|
|
509
|
+
createdAt: any;
|
|
510
|
+
};
|
|
511
|
+
dialect: 'pg';
|
|
512
|
+
}>;
|
|
513
|
+
interface EmailChangeTables {
|
|
514
|
+
users: UsersTable$2;
|
|
515
|
+
emailChangeTokens: EmailChangeTokensTable;
|
|
516
|
+
}
|
|
517
|
+
interface RequestEmailChangeParams {
|
|
518
|
+
userId: string;
|
|
519
|
+
password: string;
|
|
520
|
+
newEmail: string;
|
|
521
|
+
pepper: string;
|
|
522
|
+
db: DrizzleDB;
|
|
523
|
+
tables: EmailChangeTables;
|
|
524
|
+
}
|
|
525
|
+
interface RequestEmailChangeResult {
|
|
526
|
+
success: boolean;
|
|
527
|
+
error?: string;
|
|
528
|
+
confirmToken?: string;
|
|
529
|
+
cancelToken?: string;
|
|
530
|
+
oldEmail?: string;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Request an email change - validates password and creates tokens
|
|
534
|
+
*/
|
|
535
|
+
declare function requestEmailChange(params: RequestEmailChangeParams): Promise<RequestEmailChangeResult>;
|
|
536
|
+
interface ConfirmEmailChangeParams {
|
|
537
|
+
token: string;
|
|
538
|
+
db: DrizzleDB;
|
|
539
|
+
tables: EmailChangeTables;
|
|
540
|
+
}
|
|
541
|
+
interface ConfirmEmailChangeResult {
|
|
542
|
+
success: boolean;
|
|
543
|
+
error?: string;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Confirm email change - validates token and updates email
|
|
547
|
+
*/
|
|
548
|
+
declare function confirmEmailChange(params: ConfirmEmailChangeParams): Promise<ConfirmEmailChangeResult>;
|
|
549
|
+
interface CancelEmailChangeParams {
|
|
550
|
+
token: string;
|
|
551
|
+
db: DrizzleDB;
|
|
552
|
+
tables: Pick<EmailChangeTables, 'emailChangeTokens'>;
|
|
553
|
+
}
|
|
554
|
+
interface CancelEmailChangeResult {
|
|
555
|
+
success: boolean;
|
|
556
|
+
error?: string;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Cancel email change - validates cancel token and deletes pending change
|
|
560
|
+
*/
|
|
561
|
+
declare function cancelEmailChange(params: CancelEmailChangeParams): Promise<CancelEmailChangeResult>;
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Unified API Key utilities
|
|
565
|
+
*
|
|
566
|
+
* Keys use `syn_` prefix + 32 hex chars (36 total).
|
|
567
|
+
* Keys are hashed with SHA-256 for storage — never stored in retrievable form.
|
|
568
|
+
*/
|
|
569
|
+
/**
|
|
570
|
+
* Generate a new API key: syn_ + 32 random hex characters
|
|
571
|
+
*/
|
|
572
|
+
declare function generateApiKey(): string;
|
|
573
|
+
/**
|
|
574
|
+
* Hash an API key using SHA-256 for secure storage/lookup
|
|
575
|
+
*/
|
|
576
|
+
declare function hashApiKey(key: string): Promise<string>;
|
|
577
|
+
/**
|
|
578
|
+
* Extract display prefix from a key (first 12 chars: syn_ + 8 hex)
|
|
579
|
+
*/
|
|
580
|
+
declare function getKeyPrefix(key: string): string;
|
|
581
|
+
/**
|
|
582
|
+
* Validate API key format: syn_ + 32 hex chars
|
|
583
|
+
*/
|
|
584
|
+
declare function isValidApiKeyFormat(key: string): boolean;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Validate Origin header for state-changing requests
|
|
588
|
+
* Prevents CSRF attacks by ensuring requests come from same origin
|
|
589
|
+
*
|
|
590
|
+
* @param request - The incoming request
|
|
591
|
+
* @param allowedOrigins - List of allowed origins (e.g., ['https://app.example.com'])
|
|
592
|
+
* @returns True if valid, false if suspicious
|
|
593
|
+
*/
|
|
594
|
+
declare function validateOrigin(request: Request, allowedOrigins: string[], pagesPattern?: string): boolean;
|
|
595
|
+
/**
|
|
596
|
+
* Get allowed origins from environment
|
|
597
|
+
*/
|
|
598
|
+
declare function getAllowedOrigins(appUrl: string): string[];
|
|
599
|
+
/**
|
|
600
|
+
* Check if origin is a Cloudflare Pages preview URL for this project.
|
|
601
|
+
* Uses CORS_PAGES_PATTERN env var (e.g., "your-app.pages.dev") to match preview deployments.
|
|
602
|
+
* If no pattern is configured, returns false (no preview URLs allowed by default).
|
|
603
|
+
*/
|
|
604
|
+
declare function isCloudflarePreviewUrl(origin: string, pagesPattern?: string): boolean;
|
|
605
|
+
/**
|
|
606
|
+
* Check if origin is a Capacitor mobile app
|
|
607
|
+
* Allows capacitor:// and ionic:// protocols used by iOS/Android apps
|
|
608
|
+
*/
|
|
609
|
+
declare function isCapacitorApp(origin: string): boolean;
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Mobile authentication utilities
|
|
613
|
+
* Implements PKCE (RFC 7636) for secure OAuth on native apps
|
|
614
|
+
*/
|
|
615
|
+
/**
|
|
616
|
+
* Generate a cryptographically random code verifier for PKCE
|
|
617
|
+
* @returns Code verifier string (43-128 chars, URL-safe)
|
|
618
|
+
*/
|
|
619
|
+
declare function generateCodeVerifier(): string;
|
|
620
|
+
/**
|
|
621
|
+
* Generate S256 code challenge from verifier
|
|
622
|
+
* @param verifier - Code verifier string
|
|
623
|
+
* @returns Base64URL-encoded SHA256 hash
|
|
624
|
+
*/
|
|
625
|
+
declare function generateCodeChallenge(verifier: string): Promise<string>;
|
|
626
|
+
/**
|
|
627
|
+
* Verify that a code verifier matches a code challenge
|
|
628
|
+
* @param verifier - The code verifier from the token request
|
|
629
|
+
* @param challenge - The code challenge from the authorization request
|
|
630
|
+
* @returns True if verifier hashes to challenge
|
|
631
|
+
*/
|
|
632
|
+
declare function verifyCodeChallenge(verifier: string, challenge: string): Promise<boolean>;
|
|
633
|
+
/**
|
|
634
|
+
* Check if a URI is a deep link (custom scheme)
|
|
635
|
+
* Deep links are used by native mobile apps for OAuth callbacks
|
|
636
|
+
* @param uri - The redirect URI to check
|
|
637
|
+
* @returns True if URI uses a custom scheme (not http/https)
|
|
638
|
+
*/
|
|
639
|
+
declare function isDeepLinkUri(uri: string): boolean;
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* TOTP (Time-based One-Time Password) utilities
|
|
643
|
+
* RFC 6238 compliant with replay prevention
|
|
644
|
+
*/
|
|
645
|
+
/**
|
|
646
|
+
* Get current TOTP counter (time step)
|
|
647
|
+
*/
|
|
648
|
+
declare function getTotpCounter(): number;
|
|
649
|
+
/**
|
|
650
|
+
* Generate a cryptographically secure TOTP secret
|
|
651
|
+
* @returns Base32-encoded 20-byte secret (32 characters)
|
|
652
|
+
*/
|
|
653
|
+
declare function generateTotpSecret(): string;
|
|
654
|
+
/**
|
|
655
|
+
* Generate the otpauth:// URI for QR code scanning
|
|
656
|
+
* @param secret - Base32-encoded TOTP secret
|
|
657
|
+
* @param email - User's email address
|
|
658
|
+
* @param issuer - Application name shown in authenticator apps (default: AUTH_DEFAULTS.APP_NAME)
|
|
659
|
+
*/
|
|
660
|
+
declare function generateQrCodeUri(secret: string, email: string, issuer?: string): string;
|
|
661
|
+
type TotpVerifyResult = {
|
|
662
|
+
valid: true;
|
|
663
|
+
counter: number;
|
|
664
|
+
} | {
|
|
665
|
+
valid: false;
|
|
666
|
+
counter?: undefined;
|
|
667
|
+
};
|
|
668
|
+
/**
|
|
669
|
+
* Verify a TOTP code with replay prevention
|
|
670
|
+
* @param secret - Base32-encoded TOTP secret
|
|
671
|
+
* @param code - 6-digit TOTP code from user
|
|
672
|
+
* @param lastCounter - Last accepted counter (null if first verification)
|
|
673
|
+
* @returns Verification result with counter if valid
|
|
674
|
+
*/
|
|
675
|
+
declare function verifyTotpCode(secret: string, code: string, lastCounter: number | null): TotpVerifyResult;
|
|
676
|
+
/**
|
|
677
|
+
* Encrypt a TOTP secret using AES-256-GCM
|
|
678
|
+
*/
|
|
679
|
+
declare function encryptTotpSecret(secret: string, keyHex: string): Promise<string>;
|
|
680
|
+
/**
|
|
681
|
+
* Decrypt a TOTP secret using AES-256-GCM
|
|
682
|
+
*/
|
|
683
|
+
declare function decryptTotpSecret(encrypted: string, keyHex: string): Promise<string>;
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Backup codes for 2FA recovery
|
|
687
|
+
* Uses bcrypt for NIST SP 800-63B compliance (secrets < 112 bits)
|
|
688
|
+
*/
|
|
689
|
+
/**
|
|
690
|
+
* Generate backup codes
|
|
691
|
+
* @param count - Number of codes to generate (default: 10)
|
|
692
|
+
* @returns Array of 8-character backup codes
|
|
693
|
+
*/
|
|
694
|
+
declare function generateBackupCodes(count?: number): string[];
|
|
695
|
+
/**
|
|
696
|
+
* Format a backup code for display (XXXX-XXXX)
|
|
697
|
+
*/
|
|
698
|
+
declare function formatBackupCode(code: string): string;
|
|
699
|
+
/**
|
|
700
|
+
* Normalize user input for comparison
|
|
701
|
+
*/
|
|
702
|
+
declare function normalizeBackupCode(input: string): string;
|
|
703
|
+
/**
|
|
704
|
+
* Hash a backup code using bcrypt (NIST compliant)
|
|
705
|
+
*/
|
|
706
|
+
declare function hashBackupCode(code: string): Promise<string>;
|
|
707
|
+
/**
|
|
708
|
+
* Verify a backup code against a stored bcrypt hash
|
|
709
|
+
*/
|
|
710
|
+
declare function verifyBackupCode(code: string, storedHash: string): Promise<boolean>;
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Trusted device management for 2FA "Remember this device" feature
|
|
714
|
+
*/
|
|
715
|
+
/** Trusted device cookie TTL: 30 days in milliseconds */
|
|
716
|
+
declare const TRUSTED_DEVICE_TTL_MS: number;
|
|
717
|
+
/** Trusted device cookie base name */
|
|
718
|
+
declare const TRUSTED_DEVICE_COOKIE_BASE = "trusted_device";
|
|
719
|
+
type DeviceType = 'desktop' | 'mobile' | 'tablet';
|
|
720
|
+
/**
|
|
721
|
+
* Parse User-Agent to get a human-readable device name
|
|
722
|
+
*/
|
|
723
|
+
declare function parseDeviceName(userAgent: string): string;
|
|
724
|
+
/**
|
|
725
|
+
* Parse User-Agent to determine device type (desktop, mobile, or tablet)
|
|
726
|
+
*/
|
|
727
|
+
declare function parseDeviceType(userAgent: string): DeviceType;
|
|
728
|
+
/**
|
|
729
|
+
* Create a new trusted device token
|
|
730
|
+
*/
|
|
731
|
+
declare function createDeviceToken(): {
|
|
732
|
+
token: string;
|
|
733
|
+
expiresAt: number;
|
|
734
|
+
};
|
|
735
|
+
/**
|
|
736
|
+
* Validate a trusted device token for a user
|
|
737
|
+
* Returns true if the token is valid and not expired
|
|
738
|
+
*/
|
|
739
|
+
declare function validateTrustedDevice(db: any, userTrustedDevicesTable: any, userId: string, tokenHash: string): Promise<boolean>;
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* 2FA Challenge token management with KV helpers
|
|
743
|
+
*/
|
|
744
|
+
/** Challenge token TTL: 5 minutes */
|
|
745
|
+
declare const CHALLENGE_TTL_MS: number;
|
|
746
|
+
/** Max attempts before challenge is invalidated */
|
|
747
|
+
declare const MAX_CHALLENGE_ATTEMPTS = 5;
|
|
748
|
+
interface ChallengePayload {
|
|
749
|
+
userId: string;
|
|
750
|
+
methods: ('totp' | 'email')[];
|
|
751
|
+
invitationToken: string | null;
|
|
752
|
+
expiresAt: number;
|
|
753
|
+
attempts: number;
|
|
754
|
+
createdAt: number;
|
|
755
|
+
}
|
|
756
|
+
type ValidationResult = {
|
|
757
|
+
valid: true;
|
|
758
|
+
} | {
|
|
759
|
+
valid: false;
|
|
760
|
+
reason: 'expired' | 'max_attempts' | 'invalid';
|
|
761
|
+
};
|
|
762
|
+
/**
|
|
763
|
+
* Create a new challenge token with full context
|
|
764
|
+
*/
|
|
765
|
+
declare function createChallengeToken(userId: string, methods: ('totp' | 'email')[], invitationToken: string | null): {
|
|
766
|
+
token: string;
|
|
767
|
+
payload: ChallengePayload;
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Validate a challenge payload
|
|
771
|
+
*/
|
|
772
|
+
declare function validateChallengePayload(payload: ChallengePayload): ValidationResult;
|
|
773
|
+
/**
|
|
774
|
+
* Store a challenge token in KV
|
|
775
|
+
*/
|
|
776
|
+
declare function storeChallengeToken(kv: KVNamespace, token: string, payload: ChallengePayload): Promise<void>;
|
|
777
|
+
/**
|
|
778
|
+
* Retrieve a challenge token from KV
|
|
779
|
+
*/
|
|
780
|
+
declare function retrieveChallengeToken(kv: KVNamespace, token: string): Promise<ChallengePayload | null>;
|
|
781
|
+
/**
|
|
782
|
+
* Update challenge attempts in KV
|
|
783
|
+
*/
|
|
784
|
+
declare function updateChallengeAttempts(kv: KVNamespace, token: string, payload: ChallengePayload): Promise<void>;
|
|
785
|
+
/**
|
|
786
|
+
* Delete a challenge token from KV
|
|
787
|
+
*/
|
|
788
|
+
declare function deleteChallengeToken(kv: KVNamespace, token: string): Promise<void>;
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Auth middleware - ensures user is authenticated
|
|
792
|
+
* Sets userId in context if session is valid
|
|
793
|
+
*/
|
|
794
|
+
declare const requireAuth: hono.MiddlewareHandler<{
|
|
795
|
+
Bindings: Env;
|
|
796
|
+
Variables: Variables;
|
|
797
|
+
}, string, {}, Response>;
|
|
798
|
+
/**
|
|
799
|
+
* Optional auth middleware - sets userId if session exists, continues either way
|
|
800
|
+
*/
|
|
801
|
+
declare const optionalAuth: hono.MiddlewareHandler<{
|
|
802
|
+
Bindings: Env;
|
|
803
|
+
Variables: Variables;
|
|
804
|
+
}, string, {}, Response>;
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* CSRF middleware - validates Origin header for state-changing requests
|
|
808
|
+
* Prevents CSRF attacks by ensuring requests come from allowed origins
|
|
809
|
+
*/
|
|
810
|
+
declare const csrf: hono.MiddlewareHandler<{
|
|
811
|
+
Bindings: Env;
|
|
812
|
+
Variables: Variables;
|
|
813
|
+
}, string, {}, Response>;
|
|
814
|
+
|
|
815
|
+
type RateLimitConfig = {
|
|
816
|
+
identifier: (c: Context<{
|
|
817
|
+
Bindings: Env;
|
|
818
|
+
Variables: Variables;
|
|
819
|
+
}>) => Promise<string> | string;
|
|
820
|
+
action: string;
|
|
821
|
+
maxAttempts: number;
|
|
822
|
+
windowMs: number;
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* Rate limit middleware factory
|
|
826
|
+
* Uses KV-backed rate limiting (check + increment) to avoid database hits.
|
|
827
|
+
*
|
|
828
|
+
* Sets standard rate limit headers on all responses:
|
|
829
|
+
* - X-RateLimit-Limit: Maximum requests allowed in window
|
|
830
|
+
* - X-RateLimit-Remaining: Requests remaining in current window
|
|
831
|
+
* - X-RateLimit-Reset: Unix timestamp when the window resets
|
|
832
|
+
*
|
|
833
|
+
* On 429 responses, also sets:
|
|
834
|
+
* - Retry-After: Seconds until the rate limit resets
|
|
835
|
+
*
|
|
836
|
+
* @param config - Rate limit configuration
|
|
837
|
+
* @returns Middleware that enforces rate limits
|
|
838
|
+
*/
|
|
839
|
+
declare const rateLimit: (config: RateLimitConfig) => hono.MiddlewareHandler<{
|
|
840
|
+
Bindings: Env;
|
|
841
|
+
Variables: Variables;
|
|
842
|
+
}, string, {}, Response>;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Middleware that requires the authenticated user to have a verified email.
|
|
846
|
+
* Must be used AFTER requireAuth middleware.
|
|
847
|
+
*/
|
|
848
|
+
declare const requireVerifiedEmail: hono.MiddlewareHandler<{
|
|
849
|
+
Bindings: Env;
|
|
850
|
+
Variables: Variables;
|
|
851
|
+
}, string, {}, Response>;
|
|
852
|
+
|
|
853
|
+
declare const auth: OpenAPIHono<{
|
|
854
|
+
Bindings: Env;
|
|
855
|
+
Variables: Variables;
|
|
856
|
+
}, {}, "/">;
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* RFC 9457 Problem Details interface
|
|
860
|
+
* All fields are optional per spec, but we require type/title/status for consistency
|
|
861
|
+
*/
|
|
862
|
+
interface ProblemDetails {
|
|
863
|
+
/** URI identifying the problem type (defaults to about:blank) */
|
|
864
|
+
type: string;
|
|
865
|
+
/** Short, human-readable summary of the problem type */
|
|
866
|
+
title: string;
|
|
867
|
+
/** HTTP status code */
|
|
868
|
+
status: number;
|
|
869
|
+
/** Human-readable explanation specific to this occurrence */
|
|
870
|
+
detail?: string;
|
|
871
|
+
/** URI identifying the specific occurrence of the problem */
|
|
872
|
+
instance?: string;
|
|
873
|
+
/** OpenTelemetry trace ID (32-char hex) for distributed tracing */
|
|
874
|
+
traceId?: string;
|
|
875
|
+
/** Request ID from X-Request-ID header for correlation */
|
|
876
|
+
requestId?: string;
|
|
877
|
+
/** Additional extension members (validation errors, etc.) */
|
|
878
|
+
[key: string]: unknown;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Common problem types for the API
|
|
882
|
+
* Using relative URIs - update these to point to your documentation
|
|
883
|
+
*/
|
|
884
|
+
declare const ProblemTypes: {
|
|
885
|
+
readonly BAD_REQUEST: "/errors/bad-request";
|
|
886
|
+
readonly UNAUTHORIZED: "/errors/unauthorized";
|
|
887
|
+
readonly FORBIDDEN: "/errors/forbidden";
|
|
888
|
+
readonly NOT_FOUND: "/errors/not-found";
|
|
889
|
+
readonly CONFLICT: "/errors/conflict";
|
|
890
|
+
readonly GONE: "/errors/gone";
|
|
891
|
+
readonly VALIDATION_ERROR: "/errors/validation-error";
|
|
892
|
+
readonly RATE_LIMIT_EXCEEDED: "/errors/rate-limit-exceeded";
|
|
893
|
+
readonly INTERNAL_ERROR: "/errors/internal-error";
|
|
894
|
+
readonly SERVICE_UNAVAILABLE: "/errors/service-unavailable";
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* Generate a trace ID in OpenTelemetry format (32-character hex string)
|
|
898
|
+
* Uses Web Crypto API for cryptographically secure randomness
|
|
899
|
+
*/
|
|
900
|
+
declare function generateTraceId(): string;
|
|
901
|
+
/**
|
|
902
|
+
* Create a Problem Details object with automatic traceId and requestId
|
|
903
|
+
*/
|
|
904
|
+
declare function createProblemDetails(type: string, title: string, status: number, options?: {
|
|
905
|
+
detail?: string;
|
|
906
|
+
instance?: string;
|
|
907
|
+
traceId?: string;
|
|
908
|
+
requestId?: string;
|
|
909
|
+
extensions?: Record<string, unknown>;
|
|
910
|
+
}): ProblemDetails;
|
|
911
|
+
/**
|
|
912
|
+
* Return a Problem Details JSON response from a Hono context
|
|
913
|
+
* Sets the correct Content-Type header (application/problem+json)
|
|
914
|
+
*/
|
|
915
|
+
declare function problemJson(c: Context, problem: ProblemDetails): Response & hono.TypedResponse<{
|
|
916
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
917
|
+
type: string;
|
|
918
|
+
title: string;
|
|
919
|
+
status: number;
|
|
920
|
+
detail?: string | undefined;
|
|
921
|
+
instance?: string | undefined;
|
|
922
|
+
traceId?: string | undefined;
|
|
923
|
+
requestId?: string | undefined;
|
|
924
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
925
|
+
/**
|
|
926
|
+
* Helper function to return common problem types
|
|
927
|
+
*/
|
|
928
|
+
declare const problems: {
|
|
929
|
+
/**
|
|
930
|
+
* 400 Bad Request - Generic client error
|
|
931
|
+
*/
|
|
932
|
+
badRequest(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
933
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
934
|
+
type: string;
|
|
935
|
+
title: string;
|
|
936
|
+
status: number;
|
|
937
|
+
detail?: string | undefined;
|
|
938
|
+
instance?: string | undefined;
|
|
939
|
+
traceId?: string | undefined;
|
|
940
|
+
requestId?: string | undefined;
|
|
941
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
942
|
+
/**
|
|
943
|
+
* 400 Bad Request - Validation errors with field-specific details
|
|
944
|
+
*/
|
|
945
|
+
validationError(c: Context, detail: string, errors?: Array<{
|
|
946
|
+
field: string;
|
|
947
|
+
message: string;
|
|
948
|
+
}>): Response & hono.TypedResponse<{
|
|
949
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
950
|
+
type: string;
|
|
951
|
+
title: string;
|
|
952
|
+
status: number;
|
|
953
|
+
detail?: string | undefined;
|
|
954
|
+
instance?: string | undefined;
|
|
955
|
+
traceId?: string | undefined;
|
|
956
|
+
requestId?: string | undefined;
|
|
957
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
958
|
+
/**
|
|
959
|
+
* 401 Unauthorized - Authentication required
|
|
960
|
+
*/
|
|
961
|
+
unauthorized(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
962
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
963
|
+
type: string;
|
|
964
|
+
title: string;
|
|
965
|
+
status: number;
|
|
966
|
+
detail?: string | undefined;
|
|
967
|
+
instance?: string | undefined;
|
|
968
|
+
traceId?: string | undefined;
|
|
969
|
+
requestId?: string | undefined;
|
|
970
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
971
|
+
/**
|
|
972
|
+
* 403 Forbidden - Insufficient permissions
|
|
973
|
+
*/
|
|
974
|
+
forbidden(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
975
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
976
|
+
type: string;
|
|
977
|
+
title: string;
|
|
978
|
+
status: number;
|
|
979
|
+
detail?: string | undefined;
|
|
980
|
+
instance?: string | undefined;
|
|
981
|
+
traceId?: string | undefined;
|
|
982
|
+
requestId?: string | undefined;
|
|
983
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
984
|
+
/**
|
|
985
|
+
* 404 Not Found
|
|
986
|
+
*/
|
|
987
|
+
notFound(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
988
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
989
|
+
type: string;
|
|
990
|
+
title: string;
|
|
991
|
+
status: number;
|
|
992
|
+
detail?: string | undefined;
|
|
993
|
+
instance?: string | undefined;
|
|
994
|
+
traceId?: string | undefined;
|
|
995
|
+
requestId?: string | undefined;
|
|
996
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
997
|
+
/**
|
|
998
|
+
* 409 Conflict - Resource conflict (e.g., duplicate email)
|
|
999
|
+
*/
|
|
1000
|
+
conflict(c: Context, detail: string): Response & hono.TypedResponse<{
|
|
1001
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
1002
|
+
type: string;
|
|
1003
|
+
title: string;
|
|
1004
|
+
status: number;
|
|
1005
|
+
detail?: string | undefined;
|
|
1006
|
+
instance?: string | undefined;
|
|
1007
|
+
traceId?: string | undefined;
|
|
1008
|
+
requestId?: string | undefined;
|
|
1009
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
1010
|
+
/**
|
|
1011
|
+
* 410 Gone - Resource is no longer available
|
|
1012
|
+
*/
|
|
1013
|
+
gone(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
1014
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
1015
|
+
type: string;
|
|
1016
|
+
title: string;
|
|
1017
|
+
status: number;
|
|
1018
|
+
detail?: string | undefined;
|
|
1019
|
+
instance?: string | undefined;
|
|
1020
|
+
traceId?: string | undefined;
|
|
1021
|
+
requestId?: string | undefined;
|
|
1022
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
1023
|
+
/**
|
|
1024
|
+
* 429 Too Many Requests - Rate limit exceeded
|
|
1025
|
+
*/
|
|
1026
|
+
rateLimitExceeded(c: Context, retryAfter?: number): Response & hono.TypedResponse<{
|
|
1027
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
1028
|
+
type: string;
|
|
1029
|
+
title: string;
|
|
1030
|
+
status: number;
|
|
1031
|
+
detail?: string | undefined;
|
|
1032
|
+
instance?: string | undefined;
|
|
1033
|
+
traceId?: string | undefined;
|
|
1034
|
+
requestId?: string | undefined;
|
|
1035
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
1036
|
+
/**
|
|
1037
|
+
* 500 Internal Server Error - Generic server error
|
|
1038
|
+
* SECURITY: Never include error details or stack traces
|
|
1039
|
+
*/
|
|
1040
|
+
internalError(c: Context, error?: Error): Response & hono.TypedResponse<{
|
|
1041
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
1042
|
+
type: string;
|
|
1043
|
+
title: string;
|
|
1044
|
+
status: number;
|
|
1045
|
+
detail?: string | undefined;
|
|
1046
|
+
instance?: string | undefined;
|
|
1047
|
+
traceId?: string | undefined;
|
|
1048
|
+
requestId?: string | undefined;
|
|
1049
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
1050
|
+
/**
|
|
1051
|
+
* 503 Service Unavailable - Service is temporarily unavailable
|
|
1052
|
+
*/
|
|
1053
|
+
serviceUnavailable(c: Context, detail?: string): Response & hono.TypedResponse<{
|
|
1054
|
+
[x: string]: hono_utils_types.JSONValue;
|
|
1055
|
+
type: string;
|
|
1056
|
+
title: string;
|
|
1057
|
+
status: number;
|
|
1058
|
+
detail?: string | undefined;
|
|
1059
|
+
instance?: string | undefined;
|
|
1060
|
+
traceId?: string | undefined;
|
|
1061
|
+
requestId?: string | undefined;
|
|
1062
|
+
}, 400 | 401 | 403 | 404 | 409 | 410 | 429 | 500 | 503, "json">;
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Lightweight structured logger for Cloudflare Workers
|
|
1067
|
+
*
|
|
1068
|
+
* Passes objects directly to console methods for proper field indexing.
|
|
1069
|
+
* Workers Logs automatically extracts and indexes object properties.
|
|
1070
|
+
*
|
|
1071
|
+
* @see https://developers.cloudflare.com/workers/observability/logs/
|
|
1072
|
+
*/
|
|
1073
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
1074
|
+
interface LogMetadata {
|
|
1075
|
+
[key: string]: unknown;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Base logger that outputs structured JSON
|
|
1079
|
+
*/
|
|
1080
|
+
declare class Logger {
|
|
1081
|
+
private minLevel;
|
|
1082
|
+
private isSilent;
|
|
1083
|
+
constructor(minLevel?: LogLevel);
|
|
1084
|
+
private shouldLog;
|
|
1085
|
+
private log;
|
|
1086
|
+
debug(message: string, meta?: LogMetadata): void;
|
|
1087
|
+
info(message: string, meta?: LogMetadata): void;
|
|
1088
|
+
warn(message: string, meta?: LogMetadata): void;
|
|
1089
|
+
error(message: string, meta?: LogMetadata): void;
|
|
1090
|
+
/**
|
|
1091
|
+
* Set minimum log level
|
|
1092
|
+
*/
|
|
1093
|
+
setLevel(level: LogLevel): void;
|
|
1094
|
+
/**
|
|
1095
|
+
* Enable/disable silent mode
|
|
1096
|
+
* Silent mode suppresses all log output (useful for tests)
|
|
1097
|
+
*/
|
|
1098
|
+
setSilent(silent: boolean): void;
|
|
1099
|
+
}
|
|
1100
|
+
declare const logger: Logger;
|
|
1101
|
+
/**
|
|
1102
|
+
* Log error with full context
|
|
1103
|
+
* Fields are automatically indexed in Workers Logs
|
|
1104
|
+
*/
|
|
1105
|
+
declare function logError(error: Error | unknown, context?: LogMetadata): void;
|
|
1106
|
+
/**
|
|
1107
|
+
* Log security event
|
|
1108
|
+
*/
|
|
1109
|
+
declare function logSecurityEvent(event: string, severity: 'low' | 'medium' | 'high' | 'critical', metadata?: LogMetadata): void;
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Email service types for Resend email provider integration
|
|
1113
|
+
*/
|
|
1114
|
+
interface EmailTemplate {
|
|
1115
|
+
to: string;
|
|
1116
|
+
subject: string;
|
|
1117
|
+
html: string;
|
|
1118
|
+
text?: string;
|
|
1119
|
+
tags?: Record<string, string>;
|
|
1120
|
+
}
|
|
1121
|
+
interface VerificationEmailData {
|
|
1122
|
+
email: string;
|
|
1123
|
+
token: string;
|
|
1124
|
+
verificationUrl: string;
|
|
1125
|
+
firstName?: string;
|
|
1126
|
+
}
|
|
1127
|
+
interface PasswordResetEmailData {
|
|
1128
|
+
email: string;
|
|
1129
|
+
token: string;
|
|
1130
|
+
resetUrl: string;
|
|
1131
|
+
}
|
|
1132
|
+
interface EmailChangeConfirmationData {
|
|
1133
|
+
newEmail: string;
|
|
1134
|
+
confirmUrl: string;
|
|
1135
|
+
}
|
|
1136
|
+
interface EmailChangeNotificationData {
|
|
1137
|
+
oldEmail: string;
|
|
1138
|
+
newEmail: string;
|
|
1139
|
+
cancelUrl: string;
|
|
1140
|
+
}
|
|
1141
|
+
interface TwoFactorCodeEmailData {
|
|
1142
|
+
email: string;
|
|
1143
|
+
firstName: string | null;
|
|
1144
|
+
code: string;
|
|
1145
|
+
}
|
|
1146
|
+
interface TwoFactorEnabledEmailData {
|
|
1147
|
+
email: string;
|
|
1148
|
+
firstName: string | null;
|
|
1149
|
+
method: 'totp' | 'email';
|
|
1150
|
+
}
|
|
1151
|
+
interface TwoFactorDisabledEmailData {
|
|
1152
|
+
email: string;
|
|
1153
|
+
firstName: string | null;
|
|
1154
|
+
}
|
|
1155
|
+
type EmailType = 'verification' | 'password_reset' | 'email_change_confirmation' | 'email_change_notification' | '2fa_code' | '2fa_enabled' | '2fa_disabled';
|
|
1156
|
+
interface SendEmailResult {
|
|
1157
|
+
success: boolean;
|
|
1158
|
+
emailId?: string;
|
|
1159
|
+
error?: string;
|
|
1160
|
+
}
|
|
1161
|
+
type ResendEventType = 'email.sent' | 'email.delivered' | 'email.delivery_delayed' | 'email.complained' | 'email.bounced' | 'email.opened' | 'email.clicked' | 'email.failed';
|
|
1162
|
+
interface ResendWebhookPayload {
|
|
1163
|
+
type: ResendEventType;
|
|
1164
|
+
created_at: string;
|
|
1165
|
+
data: {
|
|
1166
|
+
email_id: string;
|
|
1167
|
+
from: string;
|
|
1168
|
+
to: string[];
|
|
1169
|
+
subject: string;
|
|
1170
|
+
created_at: string;
|
|
1171
|
+
tags?: Record<string, string>;
|
|
1172
|
+
bounce?: {
|
|
1173
|
+
message: string;
|
|
1174
|
+
subType: string;
|
|
1175
|
+
type: 'Permanent' | 'Transient';
|
|
1176
|
+
};
|
|
1177
|
+
failed?: {
|
|
1178
|
+
reason: string;
|
|
1179
|
+
};
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Email adapter interface for provider abstraction
|
|
1185
|
+
* Allows for provider abstraction (currently Resend)
|
|
1186
|
+
*/
|
|
1187
|
+
interface EmailSendOptions {
|
|
1188
|
+
from: string;
|
|
1189
|
+
to: string;
|
|
1190
|
+
subject: string;
|
|
1191
|
+
html: string;
|
|
1192
|
+
text?: string;
|
|
1193
|
+
tags?: Record<string, string>;
|
|
1194
|
+
}
|
|
1195
|
+
interface EmailSendResult {
|
|
1196
|
+
success: boolean;
|
|
1197
|
+
emailId?: string;
|
|
1198
|
+
error?: string;
|
|
1199
|
+
}
|
|
1200
|
+
interface EmailAdapter {
|
|
1201
|
+
/**
|
|
1202
|
+
* Send an email through the provider
|
|
1203
|
+
*/
|
|
1204
|
+
send(options: EmailSendOptions): Promise<EmailSendResult>;
|
|
1205
|
+
/**
|
|
1206
|
+
* Provider name for logging
|
|
1207
|
+
*/
|
|
1208
|
+
readonly providerName: string;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Create the Resend email adapter
|
|
1213
|
+
*
|
|
1214
|
+
* @param env - Environment variables
|
|
1215
|
+
* @returns ResendAdapter instance
|
|
1216
|
+
* @throws Error if RESEND_API_KEY is missing
|
|
1217
|
+
*/
|
|
1218
|
+
declare function createEmailAdapter(env: Env): EmailAdapter;
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Resend email adapter
|
|
1222
|
+
* Wraps the Resend SDK for sending transactional emails
|
|
1223
|
+
*/
|
|
1224
|
+
declare class ResendAdapter implements EmailAdapter {
|
|
1225
|
+
private client;
|
|
1226
|
+
readonly providerName = "resend";
|
|
1227
|
+
constructor(apiKey: string);
|
|
1228
|
+
send(options: EmailSendOptions): Promise<EmailSendResult>;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
type UsersTable$1 = PgTableWithColumns<any>;
|
|
1232
|
+
declare class EmailService {
|
|
1233
|
+
private adapter;
|
|
1234
|
+
private env;
|
|
1235
|
+
private get fromAddress();
|
|
1236
|
+
constructor(env: Env, adapter?: EmailAdapter);
|
|
1237
|
+
/**
|
|
1238
|
+
* Check if email address has bounced or complained
|
|
1239
|
+
*/
|
|
1240
|
+
private shouldBlockEmail;
|
|
1241
|
+
/**
|
|
1242
|
+
* Get recipient email based on environment
|
|
1243
|
+
* - Production & Staging: Use real email (verified domain on Resend)
|
|
1244
|
+
* - Local/Dev/Test: Use Resend test address to avoid sending real emails
|
|
1245
|
+
*/
|
|
1246
|
+
getRecipientEmail(userEmail: string, emailType: EmailType): string;
|
|
1247
|
+
/**
|
|
1248
|
+
* Send verification email
|
|
1249
|
+
*/
|
|
1250
|
+
sendVerificationEmail(data: VerificationEmailData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1251
|
+
/**
|
|
1252
|
+
* Send password reset email
|
|
1253
|
+
*/
|
|
1254
|
+
sendPasswordResetEmail(data: PasswordResetEmailData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1255
|
+
/**
|
|
1256
|
+
* Send email change confirmation email (to new email address)
|
|
1257
|
+
*/
|
|
1258
|
+
sendEmailChangeConfirmation(data: EmailChangeConfirmationData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1259
|
+
/**
|
|
1260
|
+
* Send email change notification email (to old email address)
|
|
1261
|
+
*/
|
|
1262
|
+
sendEmailChangeNotification(data: EmailChangeNotificationData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1263
|
+
/**
|
|
1264
|
+
* Send 2FA verification code email
|
|
1265
|
+
*/
|
|
1266
|
+
send2faCodeEmail(data: TwoFactorCodeEmailData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1267
|
+
/**
|
|
1268
|
+
* Send 2FA enabled confirmation email
|
|
1269
|
+
*/
|
|
1270
|
+
send2faEnabledEmail(data: TwoFactorEnabledEmailData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1271
|
+
/**
|
|
1272
|
+
* Send 2FA disabled notification email
|
|
1273
|
+
*/
|
|
1274
|
+
send2faDisabledEmail(data: TwoFactorDisabledEmailData, db: PostgresJsDatabase<Record<string, unknown>>, usersTable: UsersTable$1): Promise<SendEmailResult>;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Extract base URL from a full URL (e.g., https://flow.example.com/verify?token=xxx → https://flow.example.com)
|
|
1279
|
+
*/
|
|
1280
|
+
declare function extractAppUrl(url: string): string;
|
|
1281
|
+
/**
|
|
1282
|
+
* Generate email verification email
|
|
1283
|
+
*/
|
|
1284
|
+
declare function generateVerificationEmail(data: VerificationEmailData, appName?: string, configuredAppUrl?: string): EmailTemplate;
|
|
1285
|
+
/**
|
|
1286
|
+
* Generate password reset email
|
|
1287
|
+
*/
|
|
1288
|
+
declare function generatePasswordResetEmail(data: PasswordResetEmailData, appName?: string, configuredAppUrl?: string): EmailTemplate;
|
|
1289
|
+
/**
|
|
1290
|
+
* Generate email change confirmation email (sent to NEW email)
|
|
1291
|
+
*/
|
|
1292
|
+
declare function generateEmailChangeConfirmation(data: EmailChangeConfirmationData, appName?: string, configuredAppUrl?: string): EmailTemplate;
|
|
1293
|
+
/**
|
|
1294
|
+
* Generate email change notification email (sent to OLD email)
|
|
1295
|
+
*/
|
|
1296
|
+
declare function generateEmailChangeNotification(data: EmailChangeNotificationData, appName?: string, configuredAppUrl?: string): EmailTemplate;
|
|
1297
|
+
/**
|
|
1298
|
+
* Generate 2FA verification code email
|
|
1299
|
+
*/
|
|
1300
|
+
declare function generate2faCodeEmail(data: TwoFactorCodeEmailData, appName?: string, appUrl?: string): EmailTemplate;
|
|
1301
|
+
/**
|
|
1302
|
+
* Generate 2FA enabled confirmation email
|
|
1303
|
+
*/
|
|
1304
|
+
declare function generate2faEnabledEmail(data: TwoFactorEnabledEmailData, appName?: string, appUrl?: string): EmailTemplate;
|
|
1305
|
+
/**
|
|
1306
|
+
* Generate 2FA disabled notification email
|
|
1307
|
+
*/
|
|
1308
|
+
declare function generate2faDisabledEmail(data: TwoFactorDisabledEmailData, appName?: string, appUrl?: string): EmailTemplate;
|
|
1309
|
+
|
|
1310
|
+
type EmailEventsTable$1 = PgTableWithColumns<any>;
|
|
1311
|
+
type UsersTable = PgTableWithColumns<any>;
|
|
1312
|
+
/**
|
|
1313
|
+
* Handle Resend webhook event
|
|
1314
|
+
* Processes email delivery events and updates database accordingly
|
|
1315
|
+
*/
|
|
1316
|
+
declare function handleWebhookEvent(payload: ResendWebhookPayload, db: PostgresJsDatabase<Record<string, unknown>>, tables: {
|
|
1317
|
+
emailEvents: EmailEventsTable$1;
|
|
1318
|
+
users: UsersTable;
|
|
1319
|
+
}): Promise<void>;
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Verify Resend webhook signature using svix
|
|
1323
|
+
* Resend uses svix for webhook signing
|
|
1324
|
+
*/
|
|
1325
|
+
declare function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
|
|
1326
|
+
|
|
1327
|
+
type EmailEventsTable = PgTableWithColumns<any>;
|
|
1328
|
+
/**
|
|
1329
|
+
* Calculate bounce rate for monitoring
|
|
1330
|
+
* Returns percentage of emails that bounced out of total sent
|
|
1331
|
+
*/
|
|
1332
|
+
declare function calculateBounceRate(db: PostgresJsDatabase<Record<string, unknown>>, emailEventsTable: EmailEventsTable, hoursAgo?: number): Promise<number>;
|
|
1333
|
+
/**
|
|
1334
|
+
* Calculate complaint rate for monitoring
|
|
1335
|
+
* Returns percentage of emails marked as spam out of total sent
|
|
1336
|
+
*/
|
|
1337
|
+
declare function calculateComplaintRate(db: PostgresJsDatabase<Record<string, unknown>>, emailEventsTable: EmailEventsTable, hoursAgo?: number): Promise<number>;
|
|
1338
|
+
|
|
1339
|
+
export { AUTH_DEFAULTS, type AccountLockoutTables, type Auth, type AuthConfig, type AuthContext, type AuthDatabase, type AuthSchema, CHALLENGE_TTL_MS, type ChallengePayload, type DeviceType, type EmailAdapter, type EmailChangeConfirmationData, type EmailChangeNotificationData, type EmailChangeTables, type EmailSendOptions, type EmailSendResult, EmailService, type EmailTemplate, type EmailType, type Env, type InferNewSession, type InferNewUser, type InferSession, type InferUser, MAX_CHALLENGE_ATTEMPTS, type PasswordResetEmailData, type ProblemDetails, ProblemTypes, ResendAdapter, type ResendEventType, type ResendWebhookPayload, type SendEmailResult, type SessionData, type SessionTables, TRUSTED_DEVICE_COOKIE_BASE, TRUSTED_DEVICE_TTL_MS, type TotpVerifyResult, type TwoFactorCodeEmailData, type TwoFactorDisabledEmailData, type TwoFactorEnabledEmailData, type ValidationResult, type Variables, type VerificationEmailData, auth as authRoutes, calculateBounceRate, calculateComplaintRate, cancelEmailChange, checkAccountLocked, checkBreachedPassword, clearAccountLockout, clearChallengeCookie, clearSessionCookie, clearTrustedDeviceCookie, confirmEmailChange, createAuth, createAuthMiddleware, createChallengeToken, createDeviceToken, createEmailAdapter, createProblemDetails, createSession, csrf, decryptTotpSecret, deleteAllUserSessions, deleteChallengeToken, deleteSession, encryptTotpSecret, extractAppUrl, formatBackupCode, generate2faCodeEmail, generate2faDisabledEmail, generate2faEnabledEmail, generateApiKey, generateBackupCodes, generateCodeChallenge, generateCodeVerifier, generateEmailChangeConfirmation, generateEmailChangeNotification, generateFingerprint, generatePasswordResetEmail, generateQrCodeUri, generateSecureToken, generateTotpSecret, generateTraceId, generateVerificationEmail, getAllowedOrigins, getAuthContext, getChallengeCookieName, getClientIp, getKeyPrefix, getMinutesUntilUnlock, getSessionCookieName, getTotpCounter, getTrustedDeviceCookieName, handleWebhookEvent, hashApiKey, hashBackupCode, hashPassword, hashToken, incrementFailedAttempts, invalidateAllUserSessions, isCapacitorApp, isCloudflarePreviewUrl, isDeepLinkUri, isValidApiKeyFormat, logError, logSecurityEvent, logger, normalizeBackupCode, optionalAuth, parseDeviceName, parseDeviceType, problemJson, problems, rateLimit, refreshSession, requestEmailChange, requireAuth, requireVerifiedEmail, retrieveChallengeToken, setChallengeCookie, setSessionCookie, setTrustedDeviceCookie, storeChallengeToken, updateChallengeAttempts, validateChallengePayload, validateOrigin, validatePassword, validatePasswordWithBreachCheck, validateSession, validateTrustedDevice, verifyBackupCode, verifyCodeChallenge, verifyPassword, verifyPasswordWithRotation, verifyTotpCode, verifyTurnstileToken, verifyWebhookSignature };
|