@revealui/auth 0.2.1 → 0.3.2

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.
Files changed (75) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/react/index.d.ts +4 -0
  3. package/dist/react/index.d.ts.map +1 -1
  4. package/dist/react/index.js +2 -0
  5. package/dist/react/useMFA.d.ts +83 -0
  6. package/dist/react/useMFA.d.ts.map +1 -0
  7. package/dist/react/useMFA.js +182 -0
  8. package/dist/react/usePasskey.d.ts +88 -0
  9. package/dist/react/usePasskey.d.ts.map +1 -0
  10. package/dist/react/usePasskey.js +203 -0
  11. package/dist/react/useSession.d.ts.map +1 -1
  12. package/dist/react/useSession.js +16 -5
  13. package/dist/react/useSignIn.d.ts +9 -3
  14. package/dist/react/useSignIn.d.ts.map +1 -1
  15. package/dist/react/useSignIn.js +32 -10
  16. package/dist/react/useSignOut.d.ts.map +1 -1
  17. package/dist/react/useSignUp.d.ts.map +1 -1
  18. package/dist/react/useSignUp.js +25 -9
  19. package/dist/server/auth.d.ts.map +1 -1
  20. package/dist/server/auth.js +85 -8
  21. package/dist/server/brute-force.d.ts +10 -1
  22. package/dist/server/brute-force.d.ts.map +1 -1
  23. package/dist/server/brute-force.js +17 -3
  24. package/dist/server/errors.d.ts.map +1 -1
  25. package/dist/server/index.d.ts +16 -6
  26. package/dist/server/index.d.ts.map +1 -1
  27. package/dist/server/index.js +11 -5
  28. package/dist/server/magic-link.d.ts +52 -0
  29. package/dist/server/magic-link.d.ts.map +1 -0
  30. package/dist/server/magic-link.js +111 -0
  31. package/dist/server/mfa.d.ts +87 -0
  32. package/dist/server/mfa.d.ts.map +1 -0
  33. package/dist/server/mfa.js +263 -0
  34. package/dist/server/oauth.d.ts +52 -4
  35. package/dist/server/oauth.d.ts.map +1 -1
  36. package/dist/server/oauth.js +165 -18
  37. package/dist/server/passkey.d.ts +132 -0
  38. package/dist/server/passkey.d.ts.map +1 -0
  39. package/dist/server/passkey.js +257 -0
  40. package/dist/server/password-reset.d.ts +15 -0
  41. package/dist/server/password-reset.d.ts.map +1 -1
  42. package/dist/server/password-reset.js +44 -1
  43. package/dist/server/password-validation.d.ts.map +1 -1
  44. package/dist/server/providers/github.d.ts.map +1 -1
  45. package/dist/server/providers/github.js +18 -5
  46. package/dist/server/providers/google.d.ts.map +1 -1
  47. package/dist/server/providers/google.js +18 -3
  48. package/dist/server/providers/vercel.d.ts.map +1 -1
  49. package/dist/server/providers/vercel.js +18 -3
  50. package/dist/server/rate-limit.d.ts +10 -1
  51. package/dist/server/rate-limit.d.ts.map +1 -1
  52. package/dist/server/rate-limit.js +61 -43
  53. package/dist/server/session.d.ts +65 -1
  54. package/dist/server/session.d.ts.map +1 -1
  55. package/dist/server/session.js +175 -7
  56. package/dist/server/signed-cookie.d.ts +32 -0
  57. package/dist/server/signed-cookie.d.ts.map +1 -0
  58. package/dist/server/signed-cookie.js +67 -0
  59. package/dist/server/storage/database.d.ts +1 -1
  60. package/dist/server/storage/database.d.ts.map +1 -1
  61. package/dist/server/storage/database.js +15 -7
  62. package/dist/server/storage/in-memory.d.ts.map +1 -1
  63. package/dist/server/storage/in-memory.js +7 -7
  64. package/dist/server/storage/index.d.ts +11 -3
  65. package/dist/server/storage/index.d.ts.map +1 -1
  66. package/dist/server/storage/index.js +18 -4
  67. package/dist/server/storage/interface.d.ts +1 -1
  68. package/dist/server/storage/interface.d.ts.map +1 -1
  69. package/dist/server/storage/interface.js +1 -1
  70. package/dist/types.d.ts +20 -8
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js +2 -2
  73. package/dist/utils/database.d.ts.map +1 -1
  74. package/dist/utils/database.js +9 -2
  75. package/package.json +31 -13
@@ -7,16 +7,78 @@
7
7
  import { logger } from '@revealui/core/observability/logger';
8
8
  import { getClient } from '@revealui/db/client';
9
9
  import { sessions, users } from '@revealui/db/schema';
10
- import { and, eq, gt } from 'drizzle-orm';
10
+ import { and, eq, gt, isNull, ne } from 'drizzle-orm';
11
11
  import { hashToken } from '../utils/token.js';
12
12
  import { DatabaseError, TokenError } from './errors.js';
13
+ const DEFAULT_SESSION_BINDING = {
14
+ enforceUserAgent: true,
15
+ enforceIp: false,
16
+ warnOnIpChange: true,
17
+ };
18
+ let sessionBindingConfig = { ...DEFAULT_SESSION_BINDING };
19
+ /** Override session binding behaviour (useful for tests or strict deployments). */
20
+ export function configureSessionBinding(overrides) {
21
+ sessionBindingConfig = { ...DEFAULT_SESSION_BINDING, ...overrides };
22
+ }
23
+ /** Reset to defaults (for tests). */
24
+ export function resetSessionBindingConfig() {
25
+ sessionBindingConfig = { ...DEFAULT_SESSION_BINDING };
26
+ }
27
+ /**
28
+ * Validate that the current request context matches the session's stored
29
+ * binding values (IP address, user-agent).
30
+ *
31
+ * @returns `null` when the session is valid, or a reason string when it should
32
+ * be invalidated.
33
+ */
34
+ export function validateSessionBinding(session, ctx) {
35
+ // User-agent enforcement
36
+ if (sessionBindingConfig.enforceUserAgent &&
37
+ ctx.userAgent &&
38
+ session.userAgent &&
39
+ ctx.userAgent !== session.userAgent) {
40
+ return 'user-agent mismatch';
41
+ }
42
+ // IP enforcement / warning
43
+ if (ctx.ipAddress && session.ipAddress && ctx.ipAddress !== session.ipAddress) {
44
+ if (sessionBindingConfig.enforceIp) {
45
+ return 'ip-address mismatch';
46
+ }
47
+ if (sessionBindingConfig.warnOnIpChange) {
48
+ logger.warn('Session IP changed', {
49
+ sessionId: session.id,
50
+ storedIp: session.ipAddress,
51
+ currentIp: ctx.ipAddress,
52
+ });
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /**
58
+ * Check if a session is a recovery session (created via magic link recovery).
59
+ *
60
+ * Recovery sessions are restricted — they should only be used for:
61
+ * - Changing the password (`/api/auth/change-password`)
62
+ * - Signing out (`/api/auth/sign-out`)
63
+ * - Viewing current session (`/api/auth/me`, `/api/auth/session`)
64
+ *
65
+ * All other operations (MFA management, passkey management, OAuth linking,
66
+ * admin actions) should reject recovery sessions.
67
+ */
68
+ export function isRecoverySession(sessionData) {
69
+ if (!sessionData)
70
+ return false;
71
+ const metadata = sessionData.session.metadata;
72
+ return metadata?.recovery === true;
73
+ }
13
74
  /**
14
75
  * Get session from request headers (cookie)
15
76
  *
16
77
  * @param headers - Request headers containing cookies
78
+ * @param requestContext - Optional IP / user-agent for session binding validation
17
79
  * @returns Session data with user, or null if invalid/expired
18
80
  */
19
- export async function getSession(headers) {
81
+ export async function getSession(headers, requestContext) {
20
82
  try {
21
83
  const cookieHeader = headers.get('cookie');
22
84
  if (!cookieHeader) {
@@ -50,7 +112,7 @@ export async function getSession(headers) {
50
112
  const result = await db
51
113
  .select()
52
114
  .from(sessions)
53
- .where(and(eq(sessions.tokenHash, tokenHash), gt(sessions.expiresAt, new Date())))
115
+ .where(and(eq(sessions.tokenHash, tokenHash), gt(sessions.expiresAt, new Date()), isNull(sessions.deletedAt)))
54
116
  .limit(1);
55
117
  session = result[0];
56
118
  }
@@ -61,10 +123,32 @@ export async function getSession(headers) {
61
123
  if (!session) {
62
124
  return null;
63
125
  }
126
+ // Session binding validation (IP / user-agent)
127
+ if (requestContext) {
128
+ const bindingError = validateSessionBinding(session, requestContext);
129
+ if (bindingError) {
130
+ logger.warn('Session binding violation — invalidating session', {
131
+ sessionId: session.id,
132
+ reason: bindingError,
133
+ });
134
+ // Delete the compromised session
135
+ try {
136
+ await db.delete(sessions).where(eq(sessions.id, session.id));
137
+ }
138
+ catch {
139
+ logger.error('Failed to delete session after binding violation');
140
+ }
141
+ return null;
142
+ }
143
+ }
64
144
  // Get user data
65
145
  let user;
66
146
  try {
67
- const result = await db.select().from(users).where(eq(users.id, session.userId)).limit(1);
147
+ const result = await db
148
+ .select()
149
+ .from(users)
150
+ .where(and(eq(users.id, session.userId), isNull(users.deletedAt)))
151
+ .limit(1);
68
152
  user = result[0];
69
153
  }
70
154
  catch (error) {
@@ -128,9 +212,11 @@ export async function createSession(userId, options) {
128
212
  logger.error('Error generating session token');
129
213
  throw new TokenError('Failed to generate session token');
130
214
  }
131
- // Calculate expiration (7 days for persistent, 1 day for regular)
132
- const expiresAt = new Date();
133
- expiresAt.setDate(expiresAt.getDate() + (options?.persistent ? 7 : 1));
215
+ // Calculate expiration (custom override, 7 days for persistent, 1 day for regular)
216
+ const expiresAt = options?.expiresAt ?? new Date();
217
+ if (!options?.expiresAt) {
218
+ expiresAt.setDate(expiresAt.getDate() + (options?.persistent ? 7 : 1));
219
+ }
134
220
  // Create session in database
135
221
  let session;
136
222
  try {
@@ -145,6 +231,7 @@ export async function createSession(userId, options) {
145
231
  userAgent: options?.userAgent,
146
232
  ipAddress: options?.ipAddress,
147
233
  lastActivityAt: new Date(),
234
+ metadata: options?.metadata ?? null,
148
235
  })
149
236
  .returning();
150
237
  session = result[0];
@@ -171,6 +258,55 @@ export async function createSession(userId, options) {
171
258
  throw new DatabaseError('Unexpected error creating session', err);
172
259
  }
173
260
  }
261
+ /**
262
+ * Rotate a user's session to prevent session fixation attacks.
263
+ *
264
+ * Deletes the old session (by token hash) or all sessions for the user,
265
+ * then creates a fresh session with a new token.
266
+ *
267
+ * @param userId - User ID to rotate sessions for
268
+ * @param options - Rotation options
269
+ * @returns New session token and session data
270
+ */
271
+ export async function rotateSession(userId, options) {
272
+ try {
273
+ let db;
274
+ try {
275
+ db = getClient();
276
+ }
277
+ catch (error) {
278
+ logger.error('Error getting database client in rotateSession');
279
+ throw new DatabaseError('Database connection failed', error);
280
+ }
281
+ // Invalidate old session(s)
282
+ try {
283
+ if (options?.oldSessionToken) {
284
+ const oldTokenHash = hashToken(options.oldSessionToken);
285
+ await db.delete(sessions).where(eq(sessions.tokenHash, oldTokenHash));
286
+ }
287
+ else {
288
+ await db.delete(sessions).where(eq(sessions.userId, userId));
289
+ }
290
+ }
291
+ catch (error) {
292
+ logger.error('Error deleting old session(s) during rotation');
293
+ throw new DatabaseError('Failed to delete old session(s)', error);
294
+ }
295
+ // Create a fresh session
296
+ return await createSession(userId, {
297
+ persistent: options?.persistent,
298
+ userAgent: options?.userAgent,
299
+ ipAddress: options?.ipAddress,
300
+ });
301
+ }
302
+ catch (err) {
303
+ if (err instanceof DatabaseError || err instanceof TokenError) {
304
+ throw err;
305
+ }
306
+ logger.error('Unexpected error in rotateSession');
307
+ throw new DatabaseError('Unexpected error rotating session', err);
308
+ }
309
+ }
174
310
  /**
175
311
  * Delete a session (sign out)
176
312
  *
@@ -198,6 +334,38 @@ export async function deleteSession(headers) {
198
334
  *
199
335
  * @param userId - User ID
200
336
  */
337
+ /**
338
+ * Delete all sessions for a user EXCEPT the specified session.
339
+ * Used for "sign out all other devices" functionality.
340
+ */
341
+ export async function deleteOtherUserSessions(userId, exceptSessionId) {
342
+ try {
343
+ let db;
344
+ try {
345
+ db = getClient();
346
+ }
347
+ catch (error) {
348
+ logger.error('Error getting database client');
349
+ throw new DatabaseError('Database connection failed', error);
350
+ }
351
+ try {
352
+ await db
353
+ .delete(sessions)
354
+ .where(and(eq(sessions.userId, userId), ne(sessions.id, exceptSessionId)));
355
+ }
356
+ catch (error) {
357
+ logger.error('Error deleting other user sessions');
358
+ throw new DatabaseError('Failed to delete other user sessions', error);
359
+ }
360
+ }
361
+ catch (err) {
362
+ if (err instanceof DatabaseError) {
363
+ throw err;
364
+ }
365
+ logger.error('Unexpected error in deleteOtherUserSessions');
366
+ throw new DatabaseError('Unexpected error deleting other user sessions', err);
367
+ }
368
+ }
201
369
  export async function deleteAllUserSessions(userId) {
202
370
  try {
203
371
  let db;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Signed Cookie Utility
3
+ *
4
+ * Signs and verifies JSON payloads for stateless storage in httpOnly cookies.
5
+ * Used by MFA pre-auth flow and WebAuthn challenge flow.
6
+ *
7
+ * Format: base64url(JSON payload) + '.' + base64url(HMAC-SHA256 signature)
8
+ */
9
+ /**
10
+ * Sign a payload for storage in an httpOnly cookie.
11
+ * Format: base64url(JSON payload) + '.' + base64url(HMAC-SHA256 signature)
12
+ *
13
+ * @param payload - Must include `expiresAt` (Unix ms timestamp)
14
+ * @param secret - HMAC signing key (e.g., REVEALUI_SECRET)
15
+ * @returns Signed string safe for cookie value
16
+ */
17
+ export declare function signCookiePayload<T extends {
18
+ expiresAt: number;
19
+ }>(payload: T, secret: string): string;
20
+ /**
21
+ * Verify and decode a signed cookie payload.
22
+ * Returns null if: signature invalid, expired, or malformed.
23
+ * Uses timing-safe comparison to prevent timing attacks.
24
+ *
25
+ * @param signed - Signed cookie string from `signCookiePayload`
26
+ * @param secret - HMAC signing key (must match the one used to sign)
27
+ * @returns Decoded payload or null if invalid
28
+ */
29
+ export declare function verifyCookiePayload<T extends {
30
+ expiresAt: number;
31
+ }>(signed: string, secret: string): T | null;
32
+ //# sourceMappingURL=signed-cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signed-cookie.d.ts","sourceRoot":"","sources":["../../src/server/signed-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC/D,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,MAAM,GACb,MAAM,CAQR;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EACjE,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,CAAC,GAAG,IAAI,CAyCV"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Signed Cookie Utility
3
+ *
4
+ * Signs and verifies JSON payloads for stateless storage in httpOnly cookies.
5
+ * Used by MFA pre-auth flow and WebAuthn challenge flow.
6
+ *
7
+ * Format: base64url(JSON payload) + '.' + base64url(HMAC-SHA256 signature)
8
+ */
9
+ import crypto from 'node:crypto';
10
+ /**
11
+ * Sign a payload for storage in an httpOnly cookie.
12
+ * Format: base64url(JSON payload) + '.' + base64url(HMAC-SHA256 signature)
13
+ *
14
+ * @param payload - Must include `expiresAt` (Unix ms timestamp)
15
+ * @param secret - HMAC signing key (e.g., REVEALUI_SECRET)
16
+ * @returns Signed string safe for cookie value
17
+ */
18
+ export function signCookiePayload(payload, secret) {
19
+ const payloadJson = JSON.stringify(payload);
20
+ const payloadB64 = Buffer.from(payloadJson).toString('base64url');
21
+ const signature = crypto.createHmac('sha256', secret).update(payloadB64).digest();
22
+ const signatureB64 = signature.toString('base64url');
23
+ return `${payloadB64}.${signatureB64}`;
24
+ }
25
+ /**
26
+ * Verify and decode a signed cookie payload.
27
+ * Returns null if: signature invalid, expired, or malformed.
28
+ * Uses timing-safe comparison to prevent timing attacks.
29
+ *
30
+ * @param signed - Signed cookie string from `signCookiePayload`
31
+ * @param secret - HMAC signing key (must match the one used to sign)
32
+ * @returns Decoded payload or null if invalid
33
+ */
34
+ export function verifyCookiePayload(signed, secret) {
35
+ try {
36
+ if (!signed) {
37
+ return null;
38
+ }
39
+ const parts = signed.split('.');
40
+ if (parts.length !== 2) {
41
+ return null;
42
+ }
43
+ const [payloadB64, signatureB64] = parts;
44
+ // Recompute the expected signature
45
+ const expectedSignature = crypto.createHmac('sha256', secret).update(payloadB64).digest();
46
+ const actualSignature = Buffer.from(signatureB64, 'base64url');
47
+ // Timing-safe comparison — buffers must be same length
48
+ if (expectedSignature.length !== actualSignature.length) {
49
+ return null;
50
+ }
51
+ if (!crypto.timingSafeEqual(expectedSignature, actualSignature)) {
52
+ return null;
53
+ }
54
+ // Signature valid — decode and parse the payload
55
+ const payloadJson = Buffer.from(payloadB64, 'base64url').toString('utf8');
56
+ const payload = JSON.parse(payloadJson);
57
+ // Check expiry
58
+ if (typeof payload.expiresAt !== 'number' || payload.expiresAt <= Date.now()) {
59
+ return null;
60
+ }
61
+ return payload;
62
+ }
63
+ catch {
64
+ // Malformed input (bad base64, bad JSON, etc.) → null
65
+ return null;
66
+ }
67
+ }
@@ -2,7 +2,7 @@
2
2
  * Database Storage
3
3
  *
4
4
  * Database backend using Drizzle ORM
5
- * Slower than Redis but uses existing database infrastructure
5
+ * Uses existing database infrastructure with no external cache dependency
6
6
  */
7
7
  import type { Storage } from './interface.js';
8
8
  export declare class DatabaseStorage implements Storage {
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/server/storage/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE7C,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,EAAE,CAAU;gBAER,gBAAgB,CAAC,EAAE,MAAM;IAkB/B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAcxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C;;;;OAIG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC;CAwBjB"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../../src/server/storage/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAE9C,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,EAAE,CAAW;gBAET,gBAAgB,CAAC,EAAE,MAAM;IAkB/B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAcxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3C;;;;OAIG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC;CA6BjB"}
@@ -2,13 +2,14 @@
2
2
  * Database Storage
3
3
  *
4
4
  * Database backend using Drizzle ORM
5
- * Slower than Redis but uses existing database infrastructure
5
+ * Uses existing database infrastructure with no external cache dependency
6
6
  */
7
7
  // Import config module (ESM)
8
8
  // Config uses proxy for lazy loading, so import is safe - validation only happens on property access
9
9
  import configModule from '@revealui/config';
10
10
  import { createClient } from '@revealui/db/client';
11
- import { and, eq, gte, rateLimits } from '@revealui/db/schema';
11
+ import { rateLimits } from '@revealui/db/schema';
12
+ import { and, eq, gte } from 'drizzle-orm';
12
13
  export class DatabaseStorage {
13
14
  db;
14
15
  constructor(connectionString) {
@@ -60,9 +61,11 @@ export class DatabaseStorage {
60
61
  await this.db.delete(rateLimits).where(eq(rateLimits.key, key));
61
62
  }
62
63
  async incr(key) {
63
- const current = await this.get(key);
64
- const newValue = current ? parseInt(current, 10) + 1 : 1;
65
- await this.set(key, String(newValue));
64
+ let newValue = 1;
65
+ await this.atomicUpdate(key, (existing) => {
66
+ newValue = existing ? parseInt(existing, 10) + 1 : 1;
67
+ return { value: String(newValue), ttlSeconds: 24 * 60 * 60 };
68
+ });
66
69
  return newValue;
67
70
  }
68
71
  async exists(key) {
@@ -92,8 +95,13 @@ export class DatabaseStorage {
92
95
  });
93
96
  });
94
97
  }
95
- catch {
96
- // Transaction not supported (e.g., Neon HTTP serverless) — fall back to non-atomic
98
+ catch (error) {
99
+ // Only fall back for transaction-not-supported errors (e.g., Neon HTTP serverless).
100
+ // Re-throw real DB errors (connection failures, constraint violations, deadlocks).
101
+ const msg = error instanceof Error ? error.message : String(error);
102
+ if (!(msg.includes('transaction') || msg.includes('Transaction'))) {
103
+ throw error;
104
+ }
97
105
  const existing = await this.get(key);
98
106
  const { value, ttlSeconds } = updater(existing);
99
107
  await this.set(key, value, ttlSeconds);
@@ -1 +1 @@
1
- {"version":3,"file":"in-memory.d.ts","sourceRoot":"","sources":["../../../src/server/storage/in-memory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAO7C,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,KAAK,CAAuC;IAG9C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiBxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBrC,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC;IAUhB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"in-memory.d.ts","sourceRoot":"","sources":["../../../src/server/storage/in-memory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAO9C,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,KAAK,CAAwC;IAE/C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAgBxC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAYlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBrC,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC;IAUhB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
@@ -6,7 +6,6 @@
6
6
  */
7
7
  export class InMemoryStorage {
8
8
  store = new Map();
9
- // eslint-disable-next-line @typescript-eslint/require-await
10
9
  async get(key) {
11
10
  const entry = this.store.get(key);
12
11
  if (!entry) {
@@ -19,7 +18,6 @@ export class InMemoryStorage {
19
18
  }
20
19
  return entry.value;
21
20
  }
22
- // eslint-disable-next-line @typescript-eslint/require-await
23
21
  async set(key, value, ttlSeconds) {
24
22
  const entry = {
25
23
  value,
@@ -27,17 +25,20 @@ export class InMemoryStorage {
27
25
  };
28
26
  this.store.set(key, entry);
29
27
  }
30
- // eslint-disable-next-line @typescript-eslint/require-await
31
28
  async del(key) {
32
29
  this.store.delete(key);
33
30
  }
34
31
  async incr(key) {
35
- const current = await this.get(key);
32
+ // Read and write synchronously on the Map to avoid yielding to the event loop
33
+ // between read and write (same approach as atomicUpdate).
34
+ const now = Date.now();
35
+ const entry = this.store.get(key);
36
+ const current = entry && (!entry.expiresAt || entry.expiresAt >= now) ? entry.value : null;
36
37
  const newValue = current ? parseInt(current, 10) + 1 : 1;
37
- await this.set(key, String(newValue));
38
+ // Preserve the original TTL if the entry exists
39
+ this.store.set(key, { value: String(newValue), expiresAt: entry?.expiresAt });
38
40
  return newValue;
39
41
  }
40
- // eslint-disable-next-line @typescript-eslint/require-await
41
42
  async exists(key) {
42
43
  const entry = this.store.get(key);
43
44
  if (!entry) {
@@ -50,7 +51,6 @@ export class InMemoryStorage {
50
51
  }
51
52
  return true;
52
53
  }
53
- // eslint-disable-next-line @typescript-eslint/require-await
54
54
  async atomicUpdate(key, updater) {
55
55
  // Read synchronously from the Map to avoid yielding to the event loop between
56
56
  // read and write (JavaScript is single-threaded; no I/O = no interleaving).
@@ -1,11 +1,19 @@
1
1
  /**
2
2
  * Storage Factory
3
3
  *
4
- * Selects storage backend based on configuration
4
+ * Selects storage backend based on configuration.
5
5
  * Priority: Database > In-Memory
6
6
  *
7
- * Note: Uses database storage for distributed rate limiting (works with ElectricSQL sync).
8
- * ElectricSQL handles client-side sync, database handles server-side storage.
7
+ * Architecture Decision (2026-03-11):
8
+ * Production deployments use DatabaseStorage backed by NeonDB (PostgreSQL).
9
+ * Neon's serverless driver uses HTTP (not persistent connections), so each
10
+ * rate limit check is a single HTTP round-trip (~30-50ms). State persists
11
+ * across Vercel cold starts because it lives in PostgreSQL, not process memory.
12
+ * This is acceptable for current scale. If sub-10ms latency becomes critical,
13
+ * add an ElectricSQL/PGlite adapter implementing the Storage interface.
14
+ *
15
+ * In-memory storage is ONLY used in development (throws in production if
16
+ * DATABASE_URL is missing).
9
17
  */
10
18
  import type { Storage } from './interface.js';
11
19
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/storage/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAI7C;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAkCpC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAevC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/storage/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAI9C;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CA6CpC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAevC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC"}
@@ -1,11 +1,19 @@
1
1
  /**
2
2
  * Storage Factory
3
3
  *
4
- * Selects storage backend based on configuration
4
+ * Selects storage backend based on configuration.
5
5
  * Priority: Database > In-Memory
6
6
  *
7
- * Note: Uses database storage for distributed rate limiting (works with ElectricSQL sync).
8
- * ElectricSQL handles client-side sync, database handles server-side storage.
7
+ * Architecture Decision (2026-03-11):
8
+ * Production deployments use DatabaseStorage backed by NeonDB (PostgreSQL).
9
+ * Neon's serverless driver uses HTTP (not persistent connections), so each
10
+ * rate limit check is a single HTTP round-trip (~30-50ms). State persists
11
+ * across Vercel cold starts because it lives in PostgreSQL, not process memory.
12
+ * This is acceptable for current scale. If sub-10ms latency becomes critical,
13
+ * add an ElectricSQL/PGlite adapter implementing the Storage interface.
14
+ *
15
+ * In-memory storage is ONLY used in development (throws in production if
16
+ * DATABASE_URL is missing).
9
17
  */
10
18
  import config from '@revealui/config';
11
19
  import { logger } from '@revealui/core/observability/logger';
@@ -40,12 +48,18 @@ export function getStorage() {
40
48
  return globalStorage;
41
49
  }
42
50
  catch (error) {
51
+ if (process.env.NODE_ENV === 'production') {
52
+ throw new Error(`Rate limiting requires database storage in production. DatabaseStorage failed: ${error instanceof Error ? error.message : String(error)}`);
53
+ }
43
54
  logger.warn('Failed to create DatabaseStorage, falling back to InMemoryStorage', {
44
55
  error: error instanceof Error ? error.message : String(error),
45
56
  });
46
57
  }
47
58
  }
48
- // Fallback to in-memory (development without DATABASE_URL)
59
+ if (process.env.NODE_ENV === 'production') {
60
+ throw new Error('Rate limiting requires DATABASE_URL or POSTGRES_URL in production. In-memory storage is not safe for distributed deployments.');
61
+ }
62
+ // Fallback to in-memory (development only)
49
63
  globalStorage = new InMemoryStorage();
50
64
  return globalStorage;
51
65
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Storage Interface
3
3
  *
4
- * Abstract interface for storage backends (in-memory, Redis, database)
4
+ * Abstract interface for storage backends (in-memory, database)
5
5
  */
6
6
  export interface Storage {
7
7
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/server/storage/interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAExC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEnE;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAErC;;OAEG;IACH,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAA;IAEjD;;OAEG;IACH,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzE;;;;;OAKG;IACH,YAAY,CAAC,CACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB"}
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/server/storage/interface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,OAAO;IACtB;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnC;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtC;;OAEG;IACH,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAElD;;OAEG;IACH,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1E;;;;;OAKG;IACH,YAAY,CAAC,CACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB"}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Storage Interface
3
3
  *
4
- * Abstract interface for storage backends (in-memory, Redis, database)
4
+ * Abstract interface for storage backends (in-memory, database)
5
5
  */
6
6
  export {};
package/dist/types.d.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Auth Types
3
3
  *
4
4
  * Type definitions for authentication system.
5
- * Uses concrete interfaces instead of z.infer<> aliases to ensure
6
- * ESLint type-checked rules can resolve all types.
5
+ * Uses concrete interfaces instead of z.infer<> aliases for
6
+ * clear type definitions and better IDE support.
7
7
  */
8
8
  /**
9
9
  * User row type matching the users table schema.
@@ -25,6 +25,8 @@ export interface User {
25
25
  emailVerified: boolean;
26
26
  emailVerificationToken: string | null;
27
27
  emailVerifiedAt: Date | null;
28
+ mfaEnabled: boolean;
29
+ mfaVerifiedAt: Date | null;
28
30
  preferences: unknown;
29
31
  createdAt: Date;
30
32
  updatedAt: Date;
@@ -46,17 +48,27 @@ export interface Session {
46
48
  lastActivityAt: Date;
47
49
  createdAt: Date;
48
50
  expiresAt: Date;
51
+ metadata: Record<string, unknown> | null;
49
52
  }
50
53
  export interface AuthSession {
51
54
  session: Session;
52
55
  user: User;
53
56
  }
54
- export interface SignInResult {
55
- success: boolean;
56
- user?: User;
57
- sessionToken?: string;
58
- error?: string;
59
- }
57
+ /** Discriminated union for sign-in outcomes. Check `success` first, then `reason` for failure details. */
58
+ export type SignInResult = {
59
+ success: true;
60
+ requiresMfa?: false;
61
+ user: User;
62
+ sessionToken: string;
63
+ } | {
64
+ success: true;
65
+ requiresMfa: true;
66
+ mfaUserId: string;
67
+ } | {
68
+ success: false;
69
+ reason: 'invalid_credentials' | 'account_locked' | 'rate_limited' | 'database_error' | 'session_error' | 'email_not_verified' | 'unexpected_error';
70
+ error: string;
71
+ };
60
72
  export interface SignUpResult {
61
73
  success: boolean;
62
74
  user?: User;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,aAAa,EAAE,MAAM,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IAClC,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,EAAE,OAAO,CAAA;IACtB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAA;IAC5B,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,YAAY,EAAE,IAAI,GAAG,IAAI,CAAA;IAEzB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IAC1B,cAAc,EAAE,IAAI,CAAA;IACpB,SAAS,EAAE,IAAI,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,IAAI,CAAA;CACX;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,IAAI,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,0GAA0G;AAC1G,MAAM,MAAM,YAAY,GACpB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EACF,qBAAqB,GACrB,gBAAgB,GAChB,cAAc,GACd,gBAAgB,GAChB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/dist/types.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Auth Types
3
3
  *
4
4
  * Type definitions for authentication system.
5
- * Uses concrete interfaces instead of z.infer<> aliases to ensure
6
- * ESLint type-checked rules can resolve all types.
5
+ * Uses concrete interfaces instead of z.infer<> aliases for
6
+ * clear type definitions and better IDE support.
7
7
  */
8
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAEhD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAU3C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,2CAMvC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B7E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAKxE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAStF;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAI3C"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/utils/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAU3C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,2CAMvC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBtE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B7E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAC3B,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAStF;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,aAAa,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,EAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAI3C"}