@factiii/auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1947 @@
1
+ import {
2
+ biometricVerifySchema,
3
+ changePasswordSchema,
4
+ checkPasswordResetSchema,
5
+ createSchemas,
6
+ deregisterPushTokenSchema,
7
+ disableTwofaSchema,
8
+ endAllSessionsSchema,
9
+ getTwofaSecretSchema,
10
+ loginSchema,
11
+ logoutSchema,
12
+ oAuthLoginSchema,
13
+ otpLoginRequestSchema,
14
+ otpLoginVerifySchema,
15
+ registerPushTokenSchema,
16
+ requestPasswordResetSchema,
17
+ resetPasswordSchema,
18
+ signupSchema,
19
+ twoFaResetSchema,
20
+ twoFaResetVerifySchema,
21
+ twoFaSetupSchema,
22
+ twoFaVerifySchema,
23
+ verifyEmailSchema
24
+ } from "./chunk-CLHDX2R2.mjs";
25
+
26
+ // src/middleware/authGuard.ts
27
+ import { TRPCError } from "@trpc/server";
28
+
29
+ // src/adapters/email.ts
30
+ function createNoopEmailAdapter() {
31
+ return {
32
+ async sendVerificationEmail(email, code) {
33
+ console.log(
34
+ `[NoopEmailAdapter] Would send verification email to ${email} with code ${code}`
35
+ );
36
+ },
37
+ async sendPasswordResetEmail(email, token) {
38
+ console.log(
39
+ `[NoopEmailAdapter] Would send password reset email to ${email} with token ${token}`
40
+ );
41
+ },
42
+ async sendOTPEmail(email, otp) {
43
+ console.log(
44
+ `[NoopEmailAdapter] Would send OTP email to ${email} with code ${otp}`
45
+ );
46
+ },
47
+ async sendLoginNotification(email, browserName, ip) {
48
+ console.log(
49
+ `[NoopEmailAdapter] Would send login notification to ${email} from ${browserName} (${ip})`
50
+ );
51
+ }
52
+ };
53
+ }
54
+ function createConsoleEmailAdapter() {
55
+ return {
56
+ async sendVerificationEmail(email, code) {
57
+ console.log("\n=== EMAIL: Verification ===");
58
+ console.log(`To: ${email}`);
59
+ console.log(`Code: ${code}`);
60
+ console.log("===========================\n");
61
+ },
62
+ async sendPasswordResetEmail(email, token) {
63
+ console.log("\n=== EMAIL: Password Reset ===");
64
+ console.log(`To: ${email}`);
65
+ console.log(`Token: ${token}`);
66
+ console.log("=============================\n");
67
+ },
68
+ async sendOTPEmail(email, otp) {
69
+ console.log("\n=== EMAIL: OTP Login ===");
70
+ console.log(`To: ${email}`);
71
+ console.log(`OTP: ${otp}`);
72
+ console.log("========================\n");
73
+ },
74
+ async sendLoginNotification(email, browserName, ip) {
75
+ console.log("\n=== EMAIL: Login Notification ===");
76
+ console.log(`To: ${email}`);
77
+ console.log(`Browser: ${browserName}`);
78
+ console.log(`IP: ${ip || "Unknown"}`);
79
+ console.log("=================================\n");
80
+ }
81
+ };
82
+ }
83
+
84
+ // src/utilities/config.ts
85
+ var defaultTokenSettings = {
86
+ accessTokenExpiry: "5m",
87
+ passwordResetExpiryMs: 60 * 60 * 1e3,
88
+ // 1 hour
89
+ otpValidityMs: 15 * 60 * 1e3
90
+ // 15 minutes
91
+ };
92
+ var defaultCookieSettings = {
93
+ secure: true,
94
+ sameSite: "Strict",
95
+ httpOnly: true,
96
+ accessTokenPath: "/",
97
+ refreshTokenPath: "/api/trpc/auth.refresh",
98
+ maxAge: 365 * 24 * 60 * 60
99
+ // 1 year in seconds
100
+ };
101
+ var defaultStorageKeys = {
102
+ accessToken: "auth-at",
103
+ refreshToken: "auth-rt"
104
+ };
105
+ var defaultFeatures = {
106
+ twoFa: true,
107
+ oauth: { google: true, apple: true },
108
+ biometric: false,
109
+ emailVerification: true,
110
+ passwordReset: true,
111
+ otpLogin: true
112
+ };
113
+ function createAuthConfig(config) {
114
+ const emailService = config.emailService ?? createNoopEmailAdapter();
115
+ return {
116
+ ...config,
117
+ features: { ...defaultFeatures, ...config.features },
118
+ tokenSettings: { ...defaultTokenSettings, ...config.tokenSettings },
119
+ cookieSettings: { ...defaultCookieSettings, ...config.cookieSettings },
120
+ storageKeys: { ...defaultStorageKeys, ...config.storageKeys },
121
+ generateUsername: config.generateUsername ?? (() => `user_${Date.now()}`),
122
+ emailService
123
+ };
124
+ }
125
+ var defaultAuthConfig = {
126
+ features: defaultFeatures,
127
+ tokenSettings: defaultTokenSettings,
128
+ cookieSettings: defaultCookieSettings,
129
+ storageKeys: defaultStorageKeys
130
+ };
131
+
132
+ // src/utilities/cookies.ts
133
+ var DEFAULT_STORAGE_KEYS = {
134
+ ACCESS_TOKEN: "auth-at",
135
+ REFRESH_TOKEN: "auth-rt"
136
+ };
137
+ function parseAuthCookies(cookieHeader, storageKeys = {
138
+ accessToken: DEFAULT_STORAGE_KEYS.ACCESS_TOKEN,
139
+ refreshToken: DEFAULT_STORAGE_KEYS.REFRESH_TOKEN
140
+ }) {
141
+ if (!cookieHeader) {
142
+ return {};
143
+ }
144
+ const accessToken = cookieHeader.split(`${storageKeys.accessToken}=`)[1]?.split(";")[0];
145
+ const refreshToken = cookieHeader.split(`${storageKeys.refreshToken}=`)[1]?.split(";")[0];
146
+ return {
147
+ accessToken: accessToken || void 0,
148
+ refreshToken: refreshToken || void 0
149
+ };
150
+ }
151
+ function extractDomain(req) {
152
+ const origin = req.headers.origin;
153
+ if (origin) {
154
+ try {
155
+ return new URL(origin).hostname;
156
+ } catch {
157
+ }
158
+ }
159
+ const referer = req.headers.referer;
160
+ if (referer) {
161
+ try {
162
+ return new URL(referer).hostname;
163
+ } catch {
164
+ }
165
+ }
166
+ const host = req.headers.host;
167
+ if (host) {
168
+ return host.split(":")[0];
169
+ }
170
+ return void 0;
171
+ }
172
+ function setAuthCookies(res, credentials, settings, storageKeys = {
173
+ accessToken: DEFAULT_STORAGE_KEYS.ACCESS_TOKEN,
174
+ refreshToken: DEFAULT_STORAGE_KEYS.REFRESH_TOKEN
175
+ }) {
176
+ const cookies = [];
177
+ const domain = settings.domain ?? extractDomain(res.req);
178
+ const expiresDate = settings.maxAge ? new Date(Date.now() + settings.maxAge * 1e3).toUTCString() : void 0;
179
+ if (credentials.refreshToken) {
180
+ const refreshCookie = [
181
+ `${storageKeys.refreshToken}=${credentials.refreshToken}`,
182
+ "HttpOnly",
183
+ settings.secure ? "Secure=true" : "",
184
+ `SameSite=${settings.sameSite}`,
185
+ `Path=${settings.refreshTokenPath}`,
186
+ domain ? `Domain=${domain}` : "",
187
+ `Expires=${expiresDate}`
188
+ ].filter(Boolean).join("; ");
189
+ cookies.push(refreshCookie);
190
+ }
191
+ if (credentials.accessToken) {
192
+ const accessCookie = [
193
+ `${storageKeys.accessToken}=${credentials.accessToken}`,
194
+ settings.secure ? "Secure=true" : "",
195
+ `SameSite=${settings.sameSite}`,
196
+ `Path=${settings.accessTokenPath}`,
197
+ domain ? `Domain=${domain}` : "",
198
+ `Expires=${expiresDate}`
199
+ ].filter(Boolean).join("; ");
200
+ cookies.push(accessCookie);
201
+ }
202
+ if (cookies.length > 0) {
203
+ res.setHeader("Set-Cookie", cookies);
204
+ }
205
+ }
206
+ function clearAuthCookies(res, settings, storageKeys = {
207
+ accessToken: DEFAULT_STORAGE_KEYS.ACCESS_TOKEN,
208
+ refreshToken: DEFAULT_STORAGE_KEYS.REFRESH_TOKEN
209
+ }) {
210
+ const domain = extractDomain(res.req);
211
+ const expiredDate = (/* @__PURE__ */ new Date(0)).toUTCString();
212
+ const cookies = [
213
+ [
214
+ `${storageKeys.refreshToken}=destroy`,
215
+ "HttpOnly",
216
+ settings.secure ? "Secure=true" : "",
217
+ `SameSite=${settings.sameSite}`,
218
+ `Path=${settings.refreshTokenPath}`,
219
+ domain ? `Domain=${domain}` : "",
220
+ `Expires=${expiredDate}`
221
+ ].filter(Boolean).join("; "),
222
+ [
223
+ `${storageKeys.accessToken}=destroy`,
224
+ settings.secure ? "Secure=true" : "",
225
+ `SameSite=${settings.sameSite}`,
226
+ `Path=${settings.accessTokenPath}`,
227
+ domain ? `Domain=${domain}` : "",
228
+ `Expires=${expiredDate}`
229
+ ].filter(Boolean).join("; ")
230
+ ];
231
+ res.setHeader("Set-Cookie", cookies);
232
+ }
233
+
234
+ // src/utilities/jwt.ts
235
+ import jwt from "jsonwebtoken";
236
+ function createAccessToken(payload, options) {
237
+ return jwt.sign(payload, options.secret, {
238
+ expiresIn: options.expiresIn
239
+ });
240
+ }
241
+ function verifyAccessToken(token, options) {
242
+ return jwt.verify(token, options.secret, {
243
+ ignoreExpiration: options.ignoreExpiration ?? false
244
+ });
245
+ }
246
+ function decodeToken(token) {
247
+ try {
248
+ return jwt.decode(token);
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+ function isJwtError(error) {
254
+ return error instanceof Error && ["TokenExpiredError", "JsonWebTokenError", "NotBeforeError"].includes(
255
+ error.name
256
+ );
257
+ }
258
+ function isTokenExpiredError(error) {
259
+ return isJwtError(error) && error.name === "TokenExpiredError";
260
+ }
261
+ function isTokenInvalidError(error) {
262
+ return isJwtError(error) && error.name === "JsonWebTokenError";
263
+ }
264
+
265
+ // src/middleware/authGuard.ts
266
+ function createAuthGuard(config, t) {
267
+ const storageKeys = config.storageKeys ?? defaultStorageKeys;
268
+ const cookieSettings = { ...defaultCookieSettings, ...config.cookieSettings };
269
+ const revokeSession = async (ctx, sessionId, description, errorStack, path) => {
270
+ clearAuthCookies(ctx.res, cookieSettings, storageKeys);
271
+ if (config.hooks?.logError) {
272
+ try {
273
+ const contextInfo = {
274
+ reason: description,
275
+ sessionId,
276
+ userId: ctx.userId,
277
+ ip: ctx.ip,
278
+ userAgent: ctx.headers["user-agent"],
279
+ ...path ? { path } : {},
280
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
281
+ };
282
+ const combinedStack = [
283
+ errorStack ? `Error Stack:
284
+ ${errorStack}` : null,
285
+ "Context:",
286
+ JSON.stringify(contextInfo, null, 2)
287
+ ].filter(Boolean).join("\n\n");
288
+ await config.hooks.logError({
289
+ type: "SECURITY",
290
+ description: `Session revoked: ${description}`,
291
+ stack: combinedStack,
292
+ ip: ctx.ip,
293
+ userId: ctx.userId ?? null
294
+ });
295
+ } catch {
296
+ }
297
+ }
298
+ if (sessionId) {
299
+ try {
300
+ await config.prisma.session.update({
301
+ where: { id: sessionId },
302
+ data: { revokedAt: /* @__PURE__ */ new Date() }
303
+ });
304
+ if (config.hooks?.onSessionRevoked) {
305
+ const session = await config.prisma.session.findUnique({
306
+ where: { id: sessionId },
307
+ select: { id: true, userId: true, socketId: true }
308
+ });
309
+ if (session) {
310
+ await config.hooks.onSessionRevoked(
311
+ session.userId,
312
+ session.socketId,
313
+ description
314
+ );
315
+ }
316
+ }
317
+ } catch {
318
+ }
319
+ }
320
+ };
321
+ const authGuard = t.middleware(async ({ ctx, meta, next, path }) => {
322
+ const cookies = parseAuthCookies(ctx.headers.cookie, storageKeys);
323
+ const authToken = cookies.accessToken;
324
+ const refreshToken = cookies.refreshToken;
325
+ const userAgent = ctx.headers["user-agent"];
326
+ if (!userAgent) {
327
+ throw new TRPCError({
328
+ code: "BAD_REQUEST",
329
+ message: "User agent is required"
330
+ });
331
+ }
332
+ if (authToken) {
333
+ try {
334
+ const decodedToken = verifyAccessToken(authToken, {
335
+ secret: config.secrets.jwt,
336
+ ignoreExpiration: meta?.ignoreExpiration ?? false
337
+ });
338
+ if (path === "auth.refresh" && !refreshToken) {
339
+ await revokeSession(
340
+ ctx,
341
+ decodedToken.id,
342
+ "Session revoked: No refresh token",
343
+ void 0,
344
+ path
345
+ );
346
+ throw new TRPCError({
347
+ message: "Unauthorized",
348
+ code: "UNAUTHORIZED"
349
+ });
350
+ }
351
+ const session = await config.prisma.session.findUnique({
352
+ where: {
353
+ id: decodedToken.id,
354
+ ...path === "auth.refresh" ? { refreshToken } : {}
355
+ },
356
+ select: {
357
+ userId: true,
358
+ user: {
359
+ select: {
360
+ status: true,
361
+ verifiedHumanAt: true
362
+ }
363
+ },
364
+ revokedAt: true,
365
+ socketId: true,
366
+ id: true
367
+ }
368
+ });
369
+ if (!session) {
370
+ await revokeSession(
371
+ ctx,
372
+ decodedToken.id,
373
+ "Session revoked: Session not found",
374
+ void 0,
375
+ path
376
+ );
377
+ throw new TRPCError({
378
+ message: "Unauthorized",
379
+ code: "UNAUTHORIZED"
380
+ });
381
+ }
382
+ if (session.user.status === "BANNED") {
383
+ await revokeSession(
384
+ ctx,
385
+ session.id,
386
+ "Session revoked: User banned",
387
+ void 0,
388
+ path
389
+ );
390
+ throw new TRPCError({
391
+ message: "Unauthorized",
392
+ code: "UNAUTHORIZED"
393
+ });
394
+ }
395
+ if (config.features?.biometric && config.hooks?.getBiometricTimeout) {
396
+ const timeoutMs = await config.hooks.getBiometricTimeout();
397
+ if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(
398
+ path
399
+ )) {
400
+ if (!session.user.verifiedHumanAt) {
401
+ throw new TRPCError({
402
+ message: "Biometric verification not completed. Please verify again.",
403
+ code: "FORBIDDEN"
404
+ });
405
+ }
406
+ const now = /* @__PURE__ */ new Date();
407
+ const verificationExpiry = new Date(
408
+ session.user.verifiedHumanAt.getTime() + timeoutMs
409
+ );
410
+ if (now > verificationExpiry) {
411
+ throw new TRPCError({
412
+ message: "Biometric verification expired. Please verify again.",
413
+ code: "FORBIDDEN"
414
+ });
415
+ }
416
+ }
417
+ }
418
+ if (session.revokedAt) {
419
+ await revokeSession(
420
+ ctx,
421
+ session.id,
422
+ "Session revoked: Session already revoked",
423
+ void 0,
424
+ path
425
+ );
426
+ throw new TRPCError({
427
+ message: "Unauthorized",
428
+ code: "UNAUTHORIZED"
429
+ });
430
+ }
431
+ if (meta?.adminRequired) {
432
+ const admin = await config.prisma.admin.findFirst({
433
+ where: { userId: session.userId },
434
+ select: { ip: true }
435
+ });
436
+ if (!admin || admin.ip !== ctx.ip) {
437
+ await revokeSession(
438
+ ctx,
439
+ session.id,
440
+ "Session revoked: Admin not found or IP mismatch",
441
+ void 0,
442
+ path
443
+ );
444
+ throw new TRPCError({
445
+ message: "Unauthorized",
446
+ code: "UNAUTHORIZED"
447
+ });
448
+ }
449
+ }
450
+ return next({
451
+ ctx: {
452
+ ...ctx,
453
+ userId: session.userId,
454
+ socketId: session.socketId,
455
+ sessionId: session.id,
456
+ refreshToken
457
+ }
458
+ });
459
+ } catch (err) {
460
+ if (err instanceof TRPCError && err.code === "FORBIDDEN") {
461
+ throw err;
462
+ }
463
+ if (!meta?.authRequired) {
464
+ return next({ ctx: { ...ctx, userId: 0 } });
465
+ }
466
+ const errorStack = err instanceof Error ? err.stack : void 0;
467
+ if (isTokenExpiredError(err) || isTokenInvalidError(err)) {
468
+ await revokeSession(
469
+ ctx,
470
+ null,
471
+ isTokenInvalidError(err) ? "Session revoked: Token invalid" : "Session revoked: Token expired",
472
+ errorStack,
473
+ path
474
+ );
475
+ throw new TRPCError({
476
+ message: isTokenInvalidError(err) ? "Token invalid" : "Token expired",
477
+ code: "UNAUTHORIZED"
478
+ });
479
+ }
480
+ if (err instanceof TRPCError && err.code === "UNAUTHORIZED") {
481
+ await revokeSession(
482
+ ctx,
483
+ null,
484
+ "Session revoked: Unauthorized",
485
+ errorStack,
486
+ path
487
+ );
488
+ throw new TRPCError({
489
+ message: "Unauthorized",
490
+ code: "UNAUTHORIZED"
491
+ });
492
+ }
493
+ throw err;
494
+ }
495
+ } else {
496
+ if (!meta?.authRequired) {
497
+ return next({ ctx: { ...ctx, userId: 0 } });
498
+ }
499
+ await revokeSession(
500
+ ctx,
501
+ null,
502
+ "Session revoked: No token sent",
503
+ void 0,
504
+ path
505
+ );
506
+ throw new TRPCError({ message: "Unauthorized", code: "UNAUTHORIZED" });
507
+ }
508
+ });
509
+ return authGuard;
510
+ }
511
+
512
+ // src/procedures/base.ts
513
+ import { randomUUID } from "crypto";
514
+ import { TRPCError as TRPCError2 } from "@trpc/server";
515
+
516
+ // src/utilities/browser.ts
517
+ function detectBrowser(userAgent) {
518
+ if (/cfnetwork|darwin/i.test(userAgent)) return "iOS App";
519
+ if (/iphone|ipad|ipod/i.test(userAgent) && /safari/i.test(userAgent) && !/crios|fxios|edg\//i.test(userAgent)) {
520
+ return "iOS Browser (Safari)";
521
+ }
522
+ if (/iphone|ipad|ipod/i.test(userAgent) && /crios/i.test(userAgent))
523
+ return "iOS Browser (Chrome)";
524
+ if (/iphone|ipad|ipod/i.test(userAgent) && /fxios/i.test(userAgent))
525
+ return "iOS Browser (Firefox)";
526
+ if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent))
527
+ return "iOS Browser (Edge)";
528
+ if (/android/i.test(userAgent) && !/chrome|firefox|samsungbrowser|opr\/|edg\//i.test(userAgent)) {
529
+ return "Android App";
530
+ }
531
+ if (/android/i.test(userAgent) && /chrome/i.test(userAgent))
532
+ return "Android Browser (Chrome)";
533
+ if (/android/i.test(userAgent) && /firefox/i.test(userAgent))
534
+ return "Android Browser (Firefox)";
535
+ if (/android/i.test(userAgent) && /samsungbrowser/i.test(userAgent))
536
+ return "Android Browser (Samsung)";
537
+ if (/android/i.test(userAgent) && /opr\//i.test(userAgent))
538
+ return "Android Browser (Opera)";
539
+ if (/android/i.test(userAgent) && /edg\//i.test(userAgent))
540
+ return "Android Browser (Edge)";
541
+ if (/chrome|chromium/i.test(userAgent)) return "Chrome";
542
+ if (/firefox/i.test(userAgent)) return "Firefox";
543
+ if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent))
544
+ return "Safari";
545
+ if (/opr\//i.test(userAgent)) return "Opera";
546
+ if (/edg\//i.test(userAgent)) return "Edge";
547
+ return "Unknown";
548
+ }
549
+ function isMobileDevice(userAgent) {
550
+ return /android|iphone|ipad|ipod|mobile/i.test(userAgent);
551
+ }
552
+ function isNativeApp(userAgent) {
553
+ const browser = detectBrowser(userAgent);
554
+ return browser === "iOS App" || browser === "Android App";
555
+ }
556
+
557
+ // src/utilities/oauth.ts
558
+ import appleSignin from "apple-signin-auth";
559
+ import { OAuth2Client } from "google-auth-library";
560
+ var OAuthVerificationError = class extends Error {
561
+ constructor(message, statusCode = 401) {
562
+ super(message);
563
+ this.statusCode = statusCode;
564
+ this.name = "OAuthVerificationError";
565
+ }
566
+ };
567
+ function createOAuthVerifier(keys) {
568
+ let googleClient = null;
569
+ if (keys.google?.clientId) {
570
+ googleClient = new OAuth2Client({
571
+ clientId: keys.google.clientId,
572
+ clientSecret: keys.google.clientSecret
573
+ });
574
+ }
575
+ return async function verifyOAuthToken(provider, token, extra) {
576
+ if (provider === "GOOGLE") {
577
+ if (!keys.google?.clientId) {
578
+ throw new OAuthVerificationError(
579
+ "Google OAuth configuration missing",
580
+ 500
581
+ );
582
+ }
583
+ if (!googleClient) {
584
+ throw new OAuthVerificationError(
585
+ "Google OAuth client not initialized",
586
+ 500
587
+ );
588
+ }
589
+ const audience = [keys.google.clientId];
590
+ if (keys.google.iosClientId) {
591
+ audience.push(keys.google.iosClientId);
592
+ }
593
+ const ticket = await googleClient.verifyIdToken({
594
+ idToken: token,
595
+ audience
596
+ });
597
+ const payload = ticket.getPayload();
598
+ if (!payload?.sub || !payload.email) {
599
+ throw new OAuthVerificationError("Invalid Google token", 401);
600
+ }
601
+ return {
602
+ oauthId: payload.sub,
603
+ email: payload.email
604
+ };
605
+ }
606
+ if (provider === "APPLE") {
607
+ if (!keys.apple?.clientId) {
608
+ throw new OAuthVerificationError(
609
+ "Apple OAuth configuration missing",
610
+ 500
611
+ );
612
+ }
613
+ const audience = [keys.apple.clientId];
614
+ if (keys.apple.iosClientId) {
615
+ audience.push(keys.apple.iosClientId);
616
+ }
617
+ const { sub, email } = await appleSignin.verifyIdToken(token, {
618
+ audience,
619
+ ignoreExpiration: false
620
+ });
621
+ const finalEmail = email || extra?.email;
622
+ if (!finalEmail || !sub) {
623
+ throw new OAuthVerificationError("Invalid Apple token", 401);
624
+ }
625
+ return {
626
+ oauthId: sub,
627
+ email: finalEmail
628
+ };
629
+ }
630
+ throw new OAuthVerificationError("Unsupported OAuth provider", 400);
631
+ };
632
+ }
633
+
634
+ // src/utilities/password.ts
635
+ import bcrypt from "bcryptjs";
636
+ var DEFAULT_SALT_ROUNDS = 10;
637
+ async function hashPassword(password, saltRounds = DEFAULT_SALT_ROUNDS) {
638
+ const salt = await bcrypt.genSalt(saltRounds);
639
+ return bcrypt.hash(password, salt);
640
+ }
641
+ async function comparePassword(password, hashedPassword) {
642
+ return bcrypt.compare(password, hashedPassword);
643
+ }
644
+ function validatePasswordStrength(password, minLength = 6) {
645
+ if (password.length < minLength) {
646
+ return {
647
+ valid: false,
648
+ error: `Password must be at least ${minLength} characters`
649
+ };
650
+ }
651
+ return { valid: true };
652
+ }
653
+
654
+ // src/utilities/totp.ts
655
+ import crypto from "crypto";
656
+ import { TOTP } from "totp-generator";
657
+ var BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
658
+ function generateTotpSecret(length = 16) {
659
+ let secret = "";
660
+ for (let i = 0; i < length; i++) {
661
+ const randIndex = Math.floor(Math.random() * BASE32_CHARS.length);
662
+ secret += BASE32_CHARS[randIndex];
663
+ }
664
+ return secret;
665
+ }
666
+ function cleanBase32String(input) {
667
+ return input.replace(/[^A-Z2-7]/gi, "").toUpperCase();
668
+ }
669
+ async function generateTotpCode(secret) {
670
+ const cleanSecret = cleanBase32String(secret);
671
+ const { otp } = await TOTP.generate(cleanSecret);
672
+ return otp;
673
+ }
674
+ async function verifyTotp(code, secret) {
675
+ const cleanSecret = cleanBase32String(secret);
676
+ const normalizedCode = code.replace(/\s/g, "");
677
+ const { otp } = await TOTP.generate(cleanSecret);
678
+ return otp === normalizedCode;
679
+ }
680
+ function generateOtp(min = 1e5, max = 999999) {
681
+ return Math.floor(crypto.randomInt(min, max + 1));
682
+ }
683
+
684
+ // src/procedures/base.ts
685
+ var BaseProcedureFactory = class {
686
+ constructor(config, procedure, authProcedure) {
687
+ this.config = config;
688
+ this.procedure = procedure;
689
+ this.authProcedure = authProcedure;
690
+ }
691
+ /** Returns all base auth procedures to be merged into the router. */
692
+ createBaseProcedures(schemas) {
693
+ return {
694
+ register: this.register(schemas.signup),
695
+ login: this.login(schemas.login),
696
+ logout: this.logout(),
697
+ refresh: this.refresh(),
698
+ endAllSessions: this.endAllSessions(),
699
+ changePassword: this.changePassword(),
700
+ sendPasswordResetEmail: this.sendPasswordResetEmail(),
701
+ checkPasswordReset: this.checkPasswordReset(),
702
+ resetPassword: this.resetPassword()
703
+ };
704
+ }
705
+ register(schema) {
706
+ return this.procedure.input(schema).mutation(async ({ ctx, input }) => {
707
+ const typedInput = input;
708
+ const { username, email, password } = typedInput;
709
+ const userAgent = ctx.headers["user-agent"];
710
+ if (!userAgent) {
711
+ throw new TRPCError2({
712
+ code: "BAD_REQUEST",
713
+ message: "User agent not found"
714
+ });
715
+ }
716
+ if (this.config.hooks?.beforeRegister) {
717
+ await this.config.hooks.beforeRegister(typedInput);
718
+ }
719
+ const usernameCheck = await this.config.prisma.user.findFirst({
720
+ where: { username: { equals: username, mode: "insensitive" } }
721
+ });
722
+ if (usernameCheck) {
723
+ throw new TRPCError2({
724
+ code: "CONFLICT",
725
+ message: "An account already exists with that username."
726
+ });
727
+ }
728
+ const emailCheck = await this.config.prisma.user.findFirst({
729
+ where: { email: { equals: email, mode: "insensitive" } },
730
+ select: { id: true }
731
+ });
732
+ if (emailCheck) {
733
+ throw new TRPCError2({
734
+ code: "CONFLICT",
735
+ message: "An account already exists with that email."
736
+ });
737
+ }
738
+ const hashedPassword = await hashPassword(password);
739
+ const user = await this.config.prisma.user.create({
740
+ data: {
741
+ username,
742
+ email,
743
+ password: hashedPassword,
744
+ status: "ACTIVE",
745
+ tag: this.config.features.biometric ? "BOT" : "HUMAN",
746
+ twoFaEnabled: false,
747
+ emailVerificationStatus: "UNVERIFIED",
748
+ verifiedHumanAt: null
749
+ }
750
+ });
751
+ if (this.config.hooks?.onUserCreated) {
752
+ await this.config.hooks.onUserCreated(user.id, typedInput);
753
+ }
754
+ const refreshToken = randomUUID();
755
+ const extraSessionData = this.config.hooks?.getSessionData ? await this.config.hooks.getSessionData(typedInput) : {};
756
+ const session = await this.config.prisma.session.create({
757
+ data: {
758
+ userId: user.id,
759
+ browserName: detectBrowser(userAgent),
760
+ socketId: null,
761
+ refreshToken,
762
+ ...extraSessionData
763
+ },
764
+ select: { id: true, refreshToken: true, userId: true }
765
+ });
766
+ if (this.config.hooks?.onSessionCreated) {
767
+ await this.config.hooks.onSessionCreated(session.id, typedInput);
768
+ }
769
+ const accessToken = createAccessToken(
770
+ { id: session.id, userId: session.userId, verifiedHumanAt: null },
771
+ {
772
+ secret: this.config.secrets.jwt,
773
+ expiresIn: this.config.tokenSettings.accessTokenExpiry
774
+ }
775
+ );
776
+ setAuthCookies(
777
+ ctx.res,
778
+ { accessToken, refreshToken: session.refreshToken },
779
+ this.config.cookieSettings,
780
+ this.config.storageKeys
781
+ );
782
+ return {
783
+ success: true,
784
+ user: { id: user.id, email: user.email, username: user.username }
785
+ };
786
+ });
787
+ }
788
+ login(schema) {
789
+ return this.procedure.input(schema).mutation(async ({ ctx, input }) => {
790
+ const typedInput = input;
791
+ const { username, password, code } = typedInput;
792
+ const userAgent = ctx.headers["user-agent"];
793
+ if (!userAgent) {
794
+ throw new TRPCError2({
795
+ code: "BAD_REQUEST",
796
+ message: "User agent not found"
797
+ });
798
+ }
799
+ if (this.config.hooks?.beforeLogin) {
800
+ await this.config.hooks.beforeLogin(typedInput);
801
+ }
802
+ const user = await this.config.prisma.user.findFirst({
803
+ where: {
804
+ OR: [
805
+ { email: { equals: username, mode: "insensitive" } },
806
+ { username: { equals: username, mode: "insensitive" } }
807
+ ]
808
+ },
809
+ select: {
810
+ id: true,
811
+ status: true,
812
+ password: true,
813
+ twoFaEnabled: true,
814
+ email: true,
815
+ username: true,
816
+ oauthProvider: true,
817
+ verifiedHumanAt: true
818
+ }
819
+ });
820
+ if (!user) {
821
+ throw new TRPCError2({
822
+ code: "FORBIDDEN",
823
+ message: "Invalid credentials."
824
+ });
825
+ }
826
+ if (user.status === "DEACTIVATED") {
827
+ throw new TRPCError2({
828
+ code: "FORBIDDEN",
829
+ message: "Your account has been deactivated."
830
+ });
831
+ }
832
+ if (user.status === "BANNED") {
833
+ throw new TRPCError2({
834
+ code: "FORBIDDEN",
835
+ message: "Your account has been banned."
836
+ });
837
+ }
838
+ if (!user.password) {
839
+ throw new TRPCError2({
840
+ code: "FORBIDDEN",
841
+ message: `This account uses ${user.oauthProvider?.toLowerCase() || "social login"}. Please use that method.`
842
+ });
843
+ }
844
+ const isMatch = await comparePassword(password, user.password);
845
+ if (!isMatch) {
846
+ throw new TRPCError2({
847
+ code: "FORBIDDEN",
848
+ message: "Invalid credentials."
849
+ });
850
+ }
851
+ if (user.twoFaEnabled && this.config.features?.twoFa) {
852
+ if (!code) {
853
+ throw new TRPCError2({
854
+ code: "FORBIDDEN",
855
+ message: "2FA code required."
856
+ });
857
+ }
858
+ let validCode = false;
859
+ const secrets = await this.config.prisma.session.findMany({
860
+ where: { userId: user.id, twoFaSecret: { not: null } },
861
+ select: { twoFaSecret: true }
862
+ });
863
+ for (const s of secrets) {
864
+ if (s.twoFaSecret && await verifyTotp(code, cleanBase32String(s.twoFaSecret))) {
865
+ validCode = true;
866
+ break;
867
+ }
868
+ }
869
+ if (!validCode) {
870
+ const checkOTP = await this.config.prisma.oTPBasedLogin.findFirst({
871
+ where: {
872
+ code: Number(code),
873
+ userId: user.id,
874
+ disabled: false,
875
+ createdAt: { gte: new Date(Date.now() - this.config.tokenSettings.otpValidityMs) }
876
+ }
877
+ });
878
+ if (checkOTP) {
879
+ validCode = true;
880
+ await this.config.prisma.oTPBasedLogin.updateMany({
881
+ where: { code: Number(code) },
882
+ data: { disabled: true }
883
+ });
884
+ }
885
+ }
886
+ if (!validCode) {
887
+ throw new TRPCError2({
888
+ code: "FORBIDDEN",
889
+ message: "Invalid 2FA code."
890
+ });
891
+ }
892
+ }
893
+ const refreshToken = randomUUID();
894
+ const extraSessionData = this.config.hooks?.getSessionData ? await this.config.hooks.getSessionData(typedInput) : {};
895
+ const session = await this.config.prisma.session.create({
896
+ data: {
897
+ userId: user.id,
898
+ browserName: detectBrowser(userAgent),
899
+ socketId: null,
900
+ refreshToken,
901
+ ...extraSessionData
902
+ },
903
+ select: {
904
+ id: true,
905
+ refreshToken: true,
906
+ userId: true,
907
+ socketId: true,
908
+ browserName: true,
909
+ issuedAt: true,
910
+ lastUsed: true,
911
+ revokedAt: true,
912
+ deviceId: true,
913
+ twoFaSecret: true
914
+ }
915
+ });
916
+ if (this.config.hooks?.onUserLogin) {
917
+ await this.config.hooks.onUserLogin(user.id, session.id);
918
+ }
919
+ if (this.config.hooks?.onSessionCreated) {
920
+ await this.config.hooks.onSessionCreated(session.id, typedInput);
921
+ }
922
+ const accessToken = createAccessToken(
923
+ { id: session.id, userId: session.userId, verifiedHumanAt: user.verifiedHumanAt },
924
+ {
925
+ secret: this.config.secrets.jwt,
926
+ expiresIn: this.config.tokenSettings.accessTokenExpiry
927
+ }
928
+ );
929
+ setAuthCookies(
930
+ ctx.res,
931
+ { accessToken, refreshToken: session.refreshToken },
932
+ this.config.cookieSettings,
933
+ this.config.storageKeys
934
+ );
935
+ return {
936
+ success: true,
937
+ user: { id: user.id, email: user.email, username: user.username }
938
+ };
939
+ });
940
+ }
941
+ logout() {
942
+ return this.procedure.mutation(async ({ ctx }) => {
943
+ const { userId, sessionId } = ctx;
944
+ if (sessionId) {
945
+ await this.config.prisma.session.update({
946
+ where: { id: sessionId },
947
+ data: { revokedAt: /* @__PURE__ */ new Date() }
948
+ });
949
+ if (userId) {
950
+ await this.config.prisma.user.update({
951
+ where: { id: userId },
952
+ data: { isActive: false }
953
+ });
954
+ }
955
+ if (this.config.hooks?.afterLogout) {
956
+ await this.config.hooks.afterLogout(userId, sessionId, ctx.socketId);
957
+ }
958
+ }
959
+ clearAuthCookies(ctx.res, this.config.cookieSettings, this.config.storageKeys);
960
+ return { success: true };
961
+ });
962
+ }
963
+ refresh() {
964
+ return this.authProcedure.meta({ ignoreExpiration: true }).query(async ({ ctx }) => {
965
+ const session = await this.config.prisma.session.update({
966
+ where: { id: ctx.sessionId },
967
+ data: { refreshToken: randomUUID(), lastUsed: /* @__PURE__ */ new Date() },
968
+ select: {
969
+ id: true,
970
+ refreshToken: true,
971
+ userId: true,
972
+ user: { select: { verifiedHumanAt: true } }
973
+ }
974
+ });
975
+ if (this.config.hooks?.onRefresh) {
976
+ this.config.hooks.onRefresh(session.userId).catch(() => {
977
+ });
978
+ }
979
+ const accessToken = createAccessToken(
980
+ { id: session.id, userId: session.userId, verifiedHumanAt: session.user.verifiedHumanAt },
981
+ {
982
+ secret: this.config.secrets.jwt,
983
+ expiresIn: this.config.tokenSettings.accessTokenExpiry
984
+ }
985
+ );
986
+ setAuthCookies(
987
+ ctx.res,
988
+ { accessToken, refreshToken: session.refreshToken },
989
+ this.config.cookieSettings,
990
+ this.config.storageKeys
991
+ );
992
+ return { success: true };
993
+ });
994
+ }
995
+ endAllSessions() {
996
+ return this.authProcedure.input(endAllSessionsSchema).mutation(async ({ ctx, input }) => {
997
+ const { skipCurrentSession } = input;
998
+ const { userId, sessionId } = ctx;
999
+ const sessionsToRevoke = await this.config.prisma.session.findMany({
1000
+ where: {
1001
+ userId,
1002
+ revokedAt: null,
1003
+ ...skipCurrentSession ? { NOT: { id: sessionId } } : {}
1004
+ },
1005
+ select: { socketId: true, id: true, userId: true }
1006
+ });
1007
+ await this.config.prisma.session.updateMany({
1008
+ where: {
1009
+ userId,
1010
+ revokedAt: null,
1011
+ ...skipCurrentSession ? { NOT: { id: sessionId } } : {}
1012
+ },
1013
+ data: { revokedAt: /* @__PURE__ */ new Date() }
1014
+ });
1015
+ for (const session of sessionsToRevoke) {
1016
+ if (this.config.hooks?.onSessionRevoked) {
1017
+ await this.config.hooks.onSessionRevoked(session.id, session.socketId, "End all sessions");
1018
+ }
1019
+ }
1020
+ if (!skipCurrentSession) {
1021
+ await this.config.prisma.user.update({
1022
+ where: { id: userId },
1023
+ data: { isActive: false }
1024
+ });
1025
+ }
1026
+ return { success: true, revokedCount: sessionsToRevoke.length };
1027
+ });
1028
+ }
1029
+ changePassword() {
1030
+ return this.authProcedure.input(changePasswordSchema).mutation(async ({ ctx, input }) => {
1031
+ const { userId, sessionId } = ctx;
1032
+ const { currentPassword, newPassword } = input;
1033
+ if (currentPassword === newPassword) {
1034
+ throw new TRPCError2({
1035
+ code: "BAD_REQUEST",
1036
+ message: "New password cannot be the same as current password"
1037
+ });
1038
+ }
1039
+ const user = await this.config.prisma.user.findUnique({
1040
+ where: { id: userId },
1041
+ select: { password: true }
1042
+ });
1043
+ if (!user) {
1044
+ throw new TRPCError2({ code: "NOT_FOUND", message: "User not found" });
1045
+ }
1046
+ if (!user.password) {
1047
+ throw new TRPCError2({
1048
+ code: "BAD_REQUEST",
1049
+ message: "This account uses social login and cannot change password."
1050
+ });
1051
+ }
1052
+ const isMatch = await comparePassword(currentPassword, user.password);
1053
+ if (!isMatch) {
1054
+ throw new TRPCError2({
1055
+ code: "FORBIDDEN",
1056
+ message: "Current password is incorrect"
1057
+ });
1058
+ }
1059
+ const hashedPassword = await hashPassword(newPassword);
1060
+ await this.config.prisma.user.update({
1061
+ where: { id: userId },
1062
+ data: { password: hashedPassword }
1063
+ });
1064
+ await this.config.prisma.session.updateMany({
1065
+ where: { userId, revokedAt: null, NOT: { id: sessionId } },
1066
+ data: { revokedAt: /* @__PURE__ */ new Date() }
1067
+ });
1068
+ if (this.config.hooks?.onPasswordChanged) {
1069
+ await this.config.hooks.onPasswordChanged(userId);
1070
+ }
1071
+ return {
1072
+ success: true,
1073
+ message: "Password changed. You will need to re-login on other devices."
1074
+ };
1075
+ });
1076
+ }
1077
+ sendPasswordResetEmail() {
1078
+ return this.procedure.input(requestPasswordResetSchema).mutation(async ({ input }) => {
1079
+ const { email } = input;
1080
+ const user = await this.config.prisma.user.findFirst({
1081
+ where: { email: { equals: email, mode: "insensitive" }, status: "ACTIVE" },
1082
+ select: { id: true, password: true, email: true }
1083
+ });
1084
+ if (!user) {
1085
+ return { message: "If an account exists with that email, a reset link has been sent." };
1086
+ }
1087
+ if (!user.password) {
1088
+ throw new TRPCError2({
1089
+ code: "BAD_REQUEST",
1090
+ message: "This account uses social login. Please use that method."
1091
+ });
1092
+ }
1093
+ await this.config.prisma.passwordReset.deleteMany({ where: { userId: user.id } });
1094
+ const passwordReset = await this.config.prisma.passwordReset.create({
1095
+ data: { userId: user.id }
1096
+ });
1097
+ if (this.config.emailService) {
1098
+ await this.config.emailService.sendPasswordResetEmail(user.email, String(passwordReset.id));
1099
+ }
1100
+ return { message: "Password reset email sent." };
1101
+ });
1102
+ }
1103
+ checkPasswordReset() {
1104
+ return this.procedure.input(checkPasswordResetSchema).query(async ({ input }) => {
1105
+ const { token } = input;
1106
+ const passwordReset = await this.config.prisma.passwordReset.findUnique({
1107
+ where: { id: token },
1108
+ select: { id: true, createdAt: true, userId: true }
1109
+ });
1110
+ if (!passwordReset) {
1111
+ throw new TRPCError2({ code: "NOT_FOUND", message: "Invalid reset token." });
1112
+ }
1113
+ if (passwordReset.createdAt.getTime() + this.config.tokenSettings.passwordResetExpiryMs < Date.now()) {
1114
+ await this.config.prisma.passwordReset.delete({ where: { id: token } });
1115
+ throw new TRPCError2({ code: "FORBIDDEN", message: "Reset token expired." });
1116
+ }
1117
+ return { valid: true };
1118
+ });
1119
+ }
1120
+ resetPassword() {
1121
+ return this.procedure.input(resetPasswordSchema).mutation(async ({ input }) => {
1122
+ const { token, password } = input;
1123
+ const passwordReset = await this.config.prisma.passwordReset.findFirst({
1124
+ where: { id: token },
1125
+ select: { id: true, createdAt: true, userId: true }
1126
+ });
1127
+ if (!passwordReset) {
1128
+ throw new TRPCError2({ code: "NOT_FOUND", message: "Invalid reset token." });
1129
+ }
1130
+ if (passwordReset.createdAt.getTime() + this.config.tokenSettings.passwordResetExpiryMs < Date.now()) {
1131
+ await this.config.prisma.passwordReset.delete({ where: { id: token } });
1132
+ throw new TRPCError2({ code: "FORBIDDEN", message: "Reset token expired." });
1133
+ }
1134
+ const hashedPassword = await hashPassword(password);
1135
+ await this.config.prisma.user.update({
1136
+ where: { id: passwordReset.userId },
1137
+ data: { password: hashedPassword }
1138
+ });
1139
+ await this.config.prisma.passwordReset.delete({ where: { id: token } });
1140
+ await this.config.prisma.session.updateMany({
1141
+ where: { userId: passwordReset.userId },
1142
+ data: { revokedAt: /* @__PURE__ */ new Date() }
1143
+ });
1144
+ return { message: "Password updated. Please log in with your new password." };
1145
+ });
1146
+ }
1147
+ };
1148
+
1149
+ // src/procedures/biometric.ts
1150
+ import { TRPCError as TRPCError3 } from "@trpc/server";
1151
+ var BiometricProcedureFactory = class {
1152
+ constructor(config, authProcedure) {
1153
+ this.config = config;
1154
+ this.authProcedure = authProcedure;
1155
+ }
1156
+ createBiometricProcedures() {
1157
+ return {
1158
+ verifyBiometric: this.verifyBiometric(),
1159
+ getBiometricStatus: this.getBiometricStatus()
1160
+ };
1161
+ }
1162
+ checkConfig() {
1163
+ if (!this.config.features.biometric) {
1164
+ throw new TRPCError3({ code: "NOT_FOUND" });
1165
+ }
1166
+ }
1167
+ verifyBiometric() {
1168
+ return this.authProcedure.input(biometricVerifySchema).mutation(async ({ ctx }) => {
1169
+ this.checkConfig();
1170
+ const { userId } = ctx;
1171
+ await this.config.prisma.user.update({
1172
+ where: { id: userId },
1173
+ data: { verifiedHumanAt: /* @__PURE__ */ new Date(), tag: "HUMAN" }
1174
+ });
1175
+ if (this.config.hooks?.onBiometricVerified) {
1176
+ await this.config.hooks.onBiometricVerified(userId);
1177
+ }
1178
+ return { success: true, verifiedAt: /* @__PURE__ */ new Date() };
1179
+ });
1180
+ }
1181
+ getBiometricStatus() {
1182
+ return this.authProcedure.query(async ({ ctx }) => {
1183
+ this.checkConfig();
1184
+ const { userId } = ctx;
1185
+ const user = await this.config.prisma.user.findUnique({
1186
+ where: { id: userId },
1187
+ select: { verifiedHumanAt: true }
1188
+ });
1189
+ if (!user) {
1190
+ throw new TRPCError3({ code: "NOT_FOUND", message: "User not found" });
1191
+ }
1192
+ let timeoutMs = null;
1193
+ if (this.config.hooks?.getBiometricTimeout) {
1194
+ timeoutMs = await this.config.hooks.getBiometricTimeout();
1195
+ }
1196
+ let isExpired = false;
1197
+ if (user.verifiedHumanAt && timeoutMs !== null) {
1198
+ const expiresAt = new Date(user.verifiedHumanAt.getTime() + timeoutMs);
1199
+ isExpired = /* @__PURE__ */ new Date() > expiresAt;
1200
+ }
1201
+ return {
1202
+ verifiedHumanAt: user.verifiedHumanAt,
1203
+ isVerified: !!user.verifiedHumanAt && !isExpired,
1204
+ isExpired,
1205
+ requiresVerification: timeoutMs !== null
1206
+ };
1207
+ });
1208
+ }
1209
+ };
1210
+
1211
+ // src/procedures/emailVerification.ts
1212
+ import { randomUUID as randomUUID2 } from "crypto";
1213
+ import { TRPCError as TRPCError4 } from "@trpc/server";
1214
+ var EmailVerificationProcedureFactory = class {
1215
+ constructor(config, authProcedure) {
1216
+ this.config = config;
1217
+ this.authProcedure = authProcedure;
1218
+ }
1219
+ createEmailVerificationProcedures() {
1220
+ return {
1221
+ sendVerificationEmail: this.sendVerificationEmail(),
1222
+ verifyEmail: this.verifyEmail(),
1223
+ getVerificationStatus: this.getVerificationStatus()
1224
+ };
1225
+ }
1226
+ checkConfig() {
1227
+ if (!this.config.features.emailVerification) {
1228
+ throw new TRPCError4({ code: "NOT_FOUND" });
1229
+ }
1230
+ }
1231
+ sendVerificationEmail() {
1232
+ return this.authProcedure.mutation(async ({ ctx }) => {
1233
+ this.checkConfig();
1234
+ const { userId } = ctx;
1235
+ const user = await this.config.prisma.user.findUnique({
1236
+ where: { id: userId, status: "ACTIVE" },
1237
+ select: { id: true, email: true, emailVerificationStatus: true }
1238
+ });
1239
+ if (!user) {
1240
+ throw new TRPCError4({ code: "NOT_FOUND", message: "User not found" });
1241
+ }
1242
+ if (user.emailVerificationStatus === "VERIFIED") {
1243
+ return { message: "Email is already verified", emailSent: false };
1244
+ }
1245
+ const otp = randomUUID2();
1246
+ await this.config.prisma.user.update({
1247
+ where: { id: userId },
1248
+ data: { emailVerificationStatus: "PENDING", otpForEmailVerification: otp }
1249
+ });
1250
+ if (this.config.emailService) {
1251
+ try {
1252
+ await this.config.emailService.sendVerificationEmail(user.email, otp);
1253
+ return { message: "Verification email sent", emailSent: true };
1254
+ } catch {
1255
+ return { message: "Failed to send email", emailSent: false };
1256
+ }
1257
+ }
1258
+ return { message: "Email service not configured", emailSent: false };
1259
+ });
1260
+ }
1261
+ verifyEmail() {
1262
+ return this.authProcedure.input(verifyEmailSchema).mutation(async ({ ctx, input }) => {
1263
+ this.checkConfig();
1264
+ const { userId } = ctx;
1265
+ const { code } = input;
1266
+ const user = await this.config.prisma.user.findUnique({
1267
+ where: { id: userId, status: "ACTIVE" },
1268
+ select: { id: true, emailVerificationStatus: true, otpForEmailVerification: true }
1269
+ });
1270
+ if (!user) {
1271
+ throw new TRPCError4({ code: "NOT_FOUND", message: "User not found" });
1272
+ }
1273
+ if (user.emailVerificationStatus === "VERIFIED") {
1274
+ return { success: true, message: "Email is already verified" };
1275
+ }
1276
+ if (code !== user.otpForEmailVerification) {
1277
+ throw new TRPCError4({ code: "BAD_REQUEST", message: "Invalid verification code" });
1278
+ }
1279
+ await this.config.prisma.user.update({
1280
+ where: { id: userId },
1281
+ data: { emailVerificationStatus: "VERIFIED", otpForEmailVerification: null }
1282
+ });
1283
+ if (this.config.hooks?.onEmailVerified) {
1284
+ await this.config.hooks.onEmailVerified(userId);
1285
+ }
1286
+ return { success: true, message: "Email verified" };
1287
+ });
1288
+ }
1289
+ getVerificationStatus() {
1290
+ return this.authProcedure.query(async ({ ctx }) => {
1291
+ this.checkConfig();
1292
+ const { userId } = ctx;
1293
+ const user = await this.config.prisma.user.findUnique({
1294
+ where: { id: userId },
1295
+ select: { emailVerificationStatus: true, email: true }
1296
+ });
1297
+ if (!user) {
1298
+ throw new TRPCError4({ code: "NOT_FOUND", message: "User not found" });
1299
+ }
1300
+ return {
1301
+ email: user.email,
1302
+ status: user.emailVerificationStatus,
1303
+ isVerified: user.emailVerificationStatus === "VERIFIED"
1304
+ };
1305
+ });
1306
+ }
1307
+ };
1308
+
1309
+ // src/procedures/oauth.ts
1310
+ import { randomUUID as randomUUID3 } from "crypto";
1311
+ import { TRPCError as TRPCError5 } from "@trpc/server";
1312
+ var OAuthLoginProcedureFactory = class {
1313
+ constructor(config, procedure) {
1314
+ this.config = config;
1315
+ this.procedure = procedure;
1316
+ this.verifyOAuthToken = null;
1317
+ if (config.oauthKeys) {
1318
+ this.verifyOAuthToken = createOAuthVerifier(config.oauthKeys);
1319
+ }
1320
+ }
1321
+ createOAuthLoginProcedures(schemas) {
1322
+ return { oAuthLogin: this.oAuthLogin(schemas.oauth) };
1323
+ }
1324
+ checkConfig() {
1325
+ if (!this.config.features.oauth?.google && !this.config.features.oauth?.apple) {
1326
+ throw new TRPCError5({ code: "NOT_FOUND" });
1327
+ }
1328
+ }
1329
+ oAuthLogin(schema) {
1330
+ return this.procedure.input(schema).mutation(async ({ ctx, input }) => {
1331
+ this.checkConfig();
1332
+ const typedInput = input;
1333
+ const { idToken, user: appleUser, provider } = typedInput;
1334
+ const userAgent = ctx.headers["user-agent"];
1335
+ if (!userAgent) {
1336
+ throw new TRPCError5({ code: "BAD_REQUEST", message: "User agent not found" });
1337
+ }
1338
+ if (!this.verifyOAuthToken) {
1339
+ throw new TRPCError5({
1340
+ code: "INTERNAL_SERVER_ERROR",
1341
+ message: "OAuth not configured. Provide oauthKeys in config."
1342
+ });
1343
+ }
1344
+ const { email, oauthId } = await this.verifyOAuthToken(provider, idToken, appleUser);
1345
+ if (!email) {
1346
+ throw new TRPCError5({ code: "BAD_REQUEST", message: "Email not provided by OAuth provider" });
1347
+ }
1348
+ let user = await this.config.prisma.user.findFirst({
1349
+ where: {
1350
+ OR: [
1351
+ { email: { equals: email, mode: "insensitive" } },
1352
+ { oauthId: { equals: oauthId } }
1353
+ ]
1354
+ },
1355
+ select: {
1356
+ id: true,
1357
+ status: true,
1358
+ email: true,
1359
+ username: true,
1360
+ password: true,
1361
+ oauthProvider: true,
1362
+ oauthId: true,
1363
+ twoFaEnabled: true,
1364
+ verifiedHumanAt: true,
1365
+ emailVerificationStatus: true
1366
+ }
1367
+ });
1368
+ if (user?.oauthProvider && user.oauthProvider !== provider) {
1369
+ throw new TRPCError5({
1370
+ code: "BAD_REQUEST",
1371
+ message: `This email uses ${user.oauthProvider.toLowerCase()} sign-in.`
1372
+ });
1373
+ }
1374
+ if (user && !user.oauthProvider && user.password) {
1375
+ throw new TRPCError5({
1376
+ code: "BAD_REQUEST",
1377
+ message: "This email uses password login. Please use email/password."
1378
+ });
1379
+ }
1380
+ if (!user) {
1381
+ const generateUsername = this.config.generateUsername ?? (() => `user_${Date.now()}`);
1382
+ user = await this.config.prisma.user.create({
1383
+ data: {
1384
+ username: generateUsername(),
1385
+ email,
1386
+ password: null,
1387
+ emailVerificationStatus: "VERIFIED",
1388
+ oauthProvider: provider,
1389
+ oauthId,
1390
+ status: "ACTIVE",
1391
+ tag: this.config.features.biometric ? "BOT" : "HUMAN",
1392
+ twoFaEnabled: false,
1393
+ verifiedHumanAt: null
1394
+ }
1395
+ });
1396
+ if (this.config.hooks?.onUserCreated) {
1397
+ await this.config.hooks.onUserCreated(user.id, typedInput);
1398
+ }
1399
+ if (this.config.hooks?.onOAuthLinked) {
1400
+ await this.config.hooks.onOAuthLinked(user.id, provider);
1401
+ }
1402
+ }
1403
+ if (user.status === "DEACTIVATED") {
1404
+ throw new TRPCError5({ code: "FORBIDDEN", message: "Your account has been deactivated." });
1405
+ }
1406
+ if (user.status === "BANNED") {
1407
+ throw new TRPCError5({ code: "FORBIDDEN", message: "Your account has been banned." });
1408
+ }
1409
+ const refreshToken = randomUUID3();
1410
+ const extraSessionData = this.config.hooks?.getSessionData ? await this.config.hooks.getSessionData(typedInput) : {};
1411
+ const session = await this.config.prisma.session.create({
1412
+ data: {
1413
+ userId: user.id,
1414
+ browserName: detectBrowser(userAgent),
1415
+ socketId: null,
1416
+ refreshToken,
1417
+ ...extraSessionData
1418
+ },
1419
+ select: {
1420
+ id: true,
1421
+ refreshToken: true,
1422
+ userId: true,
1423
+ socketId: true,
1424
+ browserName: true,
1425
+ issuedAt: true,
1426
+ lastUsed: true,
1427
+ revokedAt: true,
1428
+ deviceId: true,
1429
+ twoFaSecret: true
1430
+ }
1431
+ });
1432
+ if (this.config.hooks?.onUserLogin) {
1433
+ await this.config.hooks.onUserLogin(user.id, session.id);
1434
+ }
1435
+ if (this.config.hooks?.onSessionCreated) {
1436
+ await this.config.hooks.onSessionCreated(session.id, typedInput);
1437
+ }
1438
+ const accessToken = createAccessToken(
1439
+ { id: session.id, userId: session.userId, verifiedHumanAt: user.verifiedHumanAt ?? null },
1440
+ {
1441
+ secret: this.config.secrets.jwt,
1442
+ expiresIn: this.config.tokenSettings.accessTokenExpiry
1443
+ }
1444
+ );
1445
+ setAuthCookies(
1446
+ ctx.res,
1447
+ { accessToken, refreshToken: session.refreshToken },
1448
+ this.config.cookieSettings,
1449
+ this.config.storageKeys
1450
+ );
1451
+ return {
1452
+ success: true,
1453
+ user: { id: user.id, email: user.email, username: user.username }
1454
+ };
1455
+ });
1456
+ }
1457
+ };
1458
+
1459
+ // src/procedures/twoFa.ts
1460
+ import { TRPCError as TRPCError6 } from "@trpc/server";
1461
+ var TwoFaProcedureFactory = class {
1462
+ constructor(config, procedure, authProcedure) {
1463
+ this.config = config;
1464
+ this.procedure = procedure;
1465
+ this.authProcedure = authProcedure;
1466
+ }
1467
+ createTwoFaProcedures() {
1468
+ return {
1469
+ enableTwofa: this.enableTwofa(),
1470
+ disableTwofa: this.disableTwofa(),
1471
+ getTwofaSecret: this.getTwofaSecret(),
1472
+ twoFaReset: this.twoFaReset(),
1473
+ twoFaResetVerify: this.twoFaResetVerify(),
1474
+ registerPushToken: this.registerPushToken(),
1475
+ deregisterPushToken: this.deregisterPushToken()
1476
+ };
1477
+ }
1478
+ checkConfig() {
1479
+ if (!this.config.features.twoFa) {
1480
+ throw new TRPCError6({ code: "NOT_FOUND" });
1481
+ }
1482
+ }
1483
+ enableTwofa() {
1484
+ return this.authProcedure.mutation(async ({ ctx }) => {
1485
+ this.checkConfig();
1486
+ const { userId, sessionId } = ctx;
1487
+ const user = await this.config.prisma.user.findFirst({
1488
+ where: { id: userId },
1489
+ select: { twoFaEnabled: true, oauthProvider: true, password: true }
1490
+ });
1491
+ if (!user) {
1492
+ throw new TRPCError6({ code: "NOT_FOUND", message: "User not found." });
1493
+ }
1494
+ if (user.oauthProvider) {
1495
+ throw new TRPCError6({
1496
+ code: "FORBIDDEN",
1497
+ message: "2FA is not available for social login accounts."
1498
+ });
1499
+ }
1500
+ if (user.twoFaEnabled) {
1501
+ throw new TRPCError6({ code: "BAD_REQUEST", message: "2FA already enabled." });
1502
+ }
1503
+ const checkSession = await this.config.prisma.session.findFirst({
1504
+ where: { userId, id: sessionId },
1505
+ select: { deviceId: true }
1506
+ });
1507
+ if (!checkSession?.deviceId) {
1508
+ throw new TRPCError6({
1509
+ code: "BAD_REQUEST",
1510
+ message: "You must be logged in on mobile to enable 2FA."
1511
+ });
1512
+ }
1513
+ await this.config.prisma.session.updateMany({
1514
+ where: { userId, revokedAt: null, NOT: { id: sessionId } },
1515
+ data: { revokedAt: /* @__PURE__ */ new Date() }
1516
+ });
1517
+ await this.config.prisma.session.updateMany({
1518
+ where: { userId, NOT: { id: sessionId } },
1519
+ data: { twoFaSecret: null }
1520
+ });
1521
+ const secret = generateTotpSecret();
1522
+ await this.config.prisma.user.update({
1523
+ where: { id: userId },
1524
+ data: { twoFaEnabled: true }
1525
+ });
1526
+ await this.config.prisma.session.update({
1527
+ where: { id: sessionId },
1528
+ data: { twoFaSecret: secret }
1529
+ });
1530
+ if (this.config.hooks?.onTwoFaStatusChanged) {
1531
+ await this.config.hooks.onTwoFaStatusChanged(userId, true);
1532
+ }
1533
+ return { secret };
1534
+ });
1535
+ }
1536
+ disableTwofa() {
1537
+ return this.authProcedure.input(disableTwofaSchema).mutation(async ({ ctx, input }) => {
1538
+ this.checkConfig();
1539
+ const { userId, sessionId } = ctx;
1540
+ const { password } = input;
1541
+ const user = await this.config.prisma.user.findFirst({
1542
+ where: { id: userId },
1543
+ select: { password: true, status: true, oauthProvider: true }
1544
+ });
1545
+ if (!user) {
1546
+ throw new TRPCError6({ code: "NOT_FOUND", message: "User not found." });
1547
+ }
1548
+ if (user.status !== "ACTIVE") {
1549
+ throw new TRPCError6({ code: "FORBIDDEN", message: "Account deactivated." });
1550
+ }
1551
+ if (user.oauthProvider) {
1552
+ throw new TRPCError6({
1553
+ code: "FORBIDDEN",
1554
+ message: "2FA is not available for social login accounts."
1555
+ });
1556
+ }
1557
+ if (!user.password) {
1558
+ throw new TRPCError6({
1559
+ code: "BAD_REQUEST",
1560
+ message: "Cannot verify password for social login account."
1561
+ });
1562
+ }
1563
+ const isMatch = await comparePassword(password, user.password);
1564
+ if (!isMatch) {
1565
+ throw new TRPCError6({ code: "FORBIDDEN", message: "Incorrect password." });
1566
+ }
1567
+ await this.config.prisma.user.update({
1568
+ where: { id: userId },
1569
+ data: { twoFaEnabled: false }
1570
+ });
1571
+ await this.config.prisma.session.update({
1572
+ where: { id: sessionId },
1573
+ data: { twoFaSecret: null }
1574
+ });
1575
+ if (this.config.hooks?.onTwoFaStatusChanged) {
1576
+ await this.config.hooks.onTwoFaStatusChanged(userId, false);
1577
+ }
1578
+ return { disabled: true };
1579
+ });
1580
+ }
1581
+ getTwofaSecret() {
1582
+ return this.authProcedure.input(getTwofaSecretSchema).query(async ({ ctx, input }) => {
1583
+ this.checkConfig();
1584
+ const { userId, sessionId } = ctx;
1585
+ const { pushCode } = input;
1586
+ const user = await this.config.prisma.user.findFirst({
1587
+ where: { id: userId },
1588
+ select: { twoFaEnabled: true, oauthProvider: true }
1589
+ });
1590
+ if (user?.oauthProvider) {
1591
+ throw new TRPCError6({
1592
+ code: "FORBIDDEN",
1593
+ message: "2FA is not available for social login accounts."
1594
+ });
1595
+ }
1596
+ if (!user?.twoFaEnabled) {
1597
+ throw new TRPCError6({ code: "BAD_REQUEST", message: "2FA not enabled." });
1598
+ }
1599
+ const session = await this.config.prisma.session.findUnique({
1600
+ where: { id: sessionId, userId },
1601
+ select: { twoFaSecret: true, device: { select: { pushToken: true } } }
1602
+ });
1603
+ if (!session?.device) {
1604
+ throw new TRPCError6({ code: "BAD_REQUEST", message: "Invalid request" });
1605
+ }
1606
+ const expectedCode = await verifyTotp(pushCode, cleanBase32String(session.device.pushToken));
1607
+ if (!expectedCode) {
1608
+ throw new TRPCError6({ code: "BAD_REQUEST", message: "Invalid request" });
1609
+ }
1610
+ if (session.twoFaSecret) {
1611
+ return { secret: session.twoFaSecret };
1612
+ }
1613
+ const secret = generateTotpSecret();
1614
+ await this.config.prisma.session.update({
1615
+ where: { id: sessionId },
1616
+ data: { twoFaSecret: secret }
1617
+ });
1618
+ return { secret };
1619
+ });
1620
+ }
1621
+ twoFaReset() {
1622
+ return this.procedure.input(twoFaResetSchema).mutation(async ({ input }) => {
1623
+ this.checkConfig();
1624
+ const { username, password } = input;
1625
+ const user = await this.config.prisma.user.findFirst({
1626
+ where: { username: { equals: username, mode: "insensitive" }, twoFaEnabled: true },
1627
+ select: { id: true, password: true, email: true }
1628
+ });
1629
+ if (!user) {
1630
+ throw new TRPCError6({ code: "UNAUTHORIZED", message: "Invalid credentials." });
1631
+ }
1632
+ if (!user.password) {
1633
+ throw new TRPCError6({
1634
+ code: "BAD_REQUEST",
1635
+ message: "Social login accounts cannot use 2FA reset."
1636
+ });
1637
+ }
1638
+ const isMatch = await comparePassword(password, user.password);
1639
+ if (!isMatch) {
1640
+ throw new TRPCError6({ code: "FORBIDDEN", message: "Invalid credentials." });
1641
+ }
1642
+ const otp = generateOtp();
1643
+ await this.config.prisma.oTPBasedLogin.create({
1644
+ data: { userId: user.id, code: otp }
1645
+ });
1646
+ if (this.config.emailService) {
1647
+ await this.config.emailService.sendOTPEmail(user.email, otp);
1648
+ }
1649
+ return { success: true };
1650
+ });
1651
+ }
1652
+ twoFaResetVerify() {
1653
+ return this.procedure.input(twoFaResetVerifySchema).mutation(async ({ input }) => {
1654
+ this.checkConfig();
1655
+ const { code, username } = input;
1656
+ const user = await this.config.prisma.user.findFirst({
1657
+ where: { username: { equals: username, mode: "insensitive" } },
1658
+ select: { id: true }
1659
+ });
1660
+ if (!user) {
1661
+ throw new TRPCError6({ code: "NOT_FOUND", message: "User not found" });
1662
+ }
1663
+ const otp = await this.config.prisma.oTPBasedLogin.findFirst({
1664
+ where: {
1665
+ userId: user.id,
1666
+ code,
1667
+ disabled: false,
1668
+ createdAt: { gte: new Date(Date.now() - this.config.tokenSettings.otpValidityMs) }
1669
+ }
1670
+ });
1671
+ if (!otp) {
1672
+ throw new TRPCError6({ code: "FORBIDDEN", message: "Invalid or expired OTP" });
1673
+ }
1674
+ await this.config.prisma.oTPBasedLogin.update({
1675
+ where: { id: otp.id },
1676
+ data: { disabled: true }
1677
+ });
1678
+ await this.config.prisma.user.update({
1679
+ where: { id: user.id },
1680
+ data: { twoFaEnabled: false }
1681
+ });
1682
+ await this.config.prisma.session.updateMany({
1683
+ where: { userId: user.id },
1684
+ data: { twoFaSecret: null }
1685
+ });
1686
+ return { success: true, message: "2FA has been reset." };
1687
+ });
1688
+ }
1689
+ registerPushToken() {
1690
+ return this.authProcedure.input(registerPushTokenSchema).mutation(async ({ ctx, input }) => {
1691
+ this.checkConfig();
1692
+ const { userId, sessionId } = ctx;
1693
+ const { pushToken } = input;
1694
+ await this.config.prisma.session.updateMany({
1695
+ where: {
1696
+ userId,
1697
+ id: { not: sessionId },
1698
+ revokedAt: null,
1699
+ device: { pushToken }
1700
+ },
1701
+ data: { revokedAt: /* @__PURE__ */ new Date() }
1702
+ });
1703
+ const checkDevice = await this.config.prisma.device.findFirst({
1704
+ where: {
1705
+ pushToken,
1706
+ sessions: { some: { id: sessionId } },
1707
+ users: { some: { id: userId } }
1708
+ },
1709
+ select: { id: true }
1710
+ });
1711
+ if (!checkDevice) {
1712
+ await this.config.prisma.device.upsert({
1713
+ where: { pushToken },
1714
+ create: {
1715
+ pushToken,
1716
+ sessions: { connect: { id: sessionId } },
1717
+ users: { connect: { id: userId } }
1718
+ },
1719
+ update: {
1720
+ sessions: { connect: { id: sessionId } },
1721
+ users: { connect: { id: userId } }
1722
+ }
1723
+ });
1724
+ }
1725
+ return { registered: true };
1726
+ });
1727
+ }
1728
+ deregisterPushToken() {
1729
+ return this.authProcedure.meta({ ignoreExpiration: true }).input(deregisterPushTokenSchema).mutation(async ({ ctx, input }) => {
1730
+ this.checkConfig();
1731
+ const { userId } = ctx;
1732
+ const { pushToken } = input;
1733
+ const device = await this.config.prisma.device.findFirst({
1734
+ where: {
1735
+ ...userId !== 0 && { users: { some: { id: userId } } },
1736
+ pushToken
1737
+ },
1738
+ select: { id: true }
1739
+ });
1740
+ if (device) {
1741
+ await this.config.prisma.device.delete({
1742
+ where: { id: device.id }
1743
+ });
1744
+ }
1745
+ return { deregistered: true };
1746
+ });
1747
+ }
1748
+ };
1749
+
1750
+ // src/utilities/trpc.ts
1751
+ import { initTRPC } from "@trpc/server";
1752
+ import SuperJSON from "superjson";
1753
+ import { ZodError } from "zod";
1754
+ function isPrismaConnectionError(error) {
1755
+ if (!error || typeof error !== "object") {
1756
+ return false;
1757
+ }
1758
+ const errorCode = error.code;
1759
+ if (errorCode && typeof errorCode === "string") {
1760
+ const codeMatch = errorCode.match(/^P(\d+)$/);
1761
+ if (codeMatch) {
1762
+ const codeNum = parseInt(codeMatch[1], 10);
1763
+ if (codeNum >= 1e3 && codeNum <= 1003) {
1764
+ return true;
1765
+ }
1766
+ }
1767
+ }
1768
+ const constructorName = error.constructor?.name || "";
1769
+ if (constructorName.includes("Prisma")) {
1770
+ const errorMessage = error.message?.toLowerCase() || "";
1771
+ if (errorMessage.includes("can't reach database") || errorMessage.includes("authentication failed") || errorMessage.includes("database server") || errorMessage.includes("timeout") || errorMessage.includes("connection")) {
1772
+ return true;
1773
+ }
1774
+ }
1775
+ const cause = error.cause;
1776
+ if (cause) {
1777
+ return isPrismaConnectionError(cause);
1778
+ }
1779
+ return false;
1780
+ }
1781
+ function createTrpcBuilder(config) {
1782
+ return initTRPC.context().meta().create({
1783
+ transformer: SuperJSON,
1784
+ errorFormatter: (opts) => {
1785
+ const { shape, error } = opts;
1786
+ const { stack: _stack, ...safeData } = shape.data;
1787
+ if (error.code === "INTERNAL_SERVER_ERROR") {
1788
+ if (config.hooks?.logError) {
1789
+ const errorType = isPrismaConnectionError(error) || isPrismaConnectionError(error.cause) ? "DATABASE_ERROR" : "SERVER_ERROR";
1790
+ config.hooks.logError({
1791
+ type: errorType,
1792
+ description: error.message,
1793
+ stack: error.stack || "No stack trace",
1794
+ ip: opts.ctx?.ip,
1795
+ userId: opts.ctx?.userId ?? null
1796
+ }).catch(() => {
1797
+ });
1798
+ }
1799
+ return {
1800
+ ...shape,
1801
+ message: "An unexpected error occurred. Please try again later.",
1802
+ data: {
1803
+ ...safeData,
1804
+ zodError: error.cause instanceof ZodError ? error.cause.flatten() : null
1805
+ }
1806
+ };
1807
+ }
1808
+ return {
1809
+ ...shape,
1810
+ data: {
1811
+ ...safeData,
1812
+ zodError: error.cause instanceof ZodError ? error.cause.flatten() : null
1813
+ }
1814
+ };
1815
+ }
1816
+ });
1817
+ }
1818
+ function createBaseProcedure(t, authGuard) {
1819
+ return t.procedure.use(authGuard);
1820
+ }
1821
+ function getClientIp(req) {
1822
+ const forwarded = req.headers["x-forwarded-for"];
1823
+ if (forwarded) {
1824
+ return forwarded.split(",")[0]?.trim();
1825
+ }
1826
+ return req.socket.remoteAddress || void 0;
1827
+ }
1828
+
1829
+ // src/router.ts
1830
+ var createContext = ({
1831
+ req,
1832
+ res
1833
+ }) => ({
1834
+ headers: req.headers,
1835
+ userId: null,
1836
+ sessionId: null,
1837
+ refreshToken: null,
1838
+ socketId: null,
1839
+ ip: getClientIp(req),
1840
+ res
1841
+ });
1842
+ var AuthRouterFactory = class {
1843
+ constructor(userConfig) {
1844
+ this.userConfig = userConfig;
1845
+ this.config = createAuthConfig(this.userConfig);
1846
+ this.schemas = createSchemas(
1847
+ this.config.schemaExtensions
1848
+ );
1849
+ this.t = createTrpcBuilder(this.config);
1850
+ this.authGuard = createAuthGuard(this.config, this.t);
1851
+ this.procedure = createBaseProcedure(this.t, this.authGuard);
1852
+ this.authProcedure = this.procedure.meta({ authRequired: true });
1853
+ }
1854
+ createRouter() {
1855
+ const baseRoutes = new BaseProcedureFactory(
1856
+ this.config,
1857
+ this.procedure,
1858
+ this.authProcedure
1859
+ );
1860
+ const biometricRoutes = new BiometricProcedureFactory(
1861
+ this.config,
1862
+ this.authProcedure
1863
+ );
1864
+ const emailVerificationRoutes = new EmailVerificationProcedureFactory(
1865
+ this.config,
1866
+ this.authProcedure
1867
+ );
1868
+ const oAuthLoginRoutes = new OAuthLoginProcedureFactory(
1869
+ this.config,
1870
+ this.procedure
1871
+ );
1872
+ const twoFaRoutes = new TwoFaProcedureFactory(
1873
+ this.config,
1874
+ this.procedure,
1875
+ this.authProcedure
1876
+ );
1877
+ return this.t.router({
1878
+ ...baseRoutes.createBaseProcedures(this.schemas),
1879
+ ...oAuthLoginRoutes.createOAuthLoginProcedures(this.schemas),
1880
+ ...twoFaRoutes.createTwoFaProcedures(),
1881
+ ...biometricRoutes.createBiometricProcedures(),
1882
+ ...emailVerificationRoutes.createEmailVerificationProcedures()
1883
+ });
1884
+ }
1885
+ };
1886
+ function createAuthRouter(config) {
1887
+ const factory = new AuthRouterFactory(config);
1888
+ const router = factory.t.router({
1889
+ auth: factory.createRouter()
1890
+ });
1891
+ return {
1892
+ router,
1893
+ t: factory.t,
1894
+ procedure: factory.procedure,
1895
+ authProcedure: factory.authProcedure,
1896
+ createContext
1897
+ };
1898
+ }
1899
+ export {
1900
+ DEFAULT_STORAGE_KEYS,
1901
+ OAuthVerificationError,
1902
+ biometricVerifySchema,
1903
+ changePasswordSchema,
1904
+ cleanBase32String,
1905
+ clearAuthCookies,
1906
+ comparePassword,
1907
+ createAccessToken,
1908
+ createAuthConfig,
1909
+ createAuthGuard,
1910
+ createAuthRouter,
1911
+ createConsoleEmailAdapter,
1912
+ createNoopEmailAdapter,
1913
+ createOAuthVerifier,
1914
+ decodeToken,
1915
+ defaultAuthConfig,
1916
+ defaultCookieSettings,
1917
+ defaultStorageKeys,
1918
+ defaultTokenSettings,
1919
+ detectBrowser,
1920
+ endAllSessionsSchema,
1921
+ generateOtp,
1922
+ generateTotpCode,
1923
+ generateTotpSecret,
1924
+ hashPassword,
1925
+ isMobileDevice,
1926
+ isNativeApp,
1927
+ isTokenExpiredError,
1928
+ isTokenInvalidError,
1929
+ loginSchema,
1930
+ logoutSchema,
1931
+ oAuthLoginSchema,
1932
+ otpLoginRequestSchema,
1933
+ otpLoginVerifySchema,
1934
+ parseAuthCookies,
1935
+ requestPasswordResetSchema,
1936
+ resetPasswordSchema,
1937
+ setAuthCookies,
1938
+ signupSchema,
1939
+ twoFaResetSchema,
1940
+ twoFaSetupSchema,
1941
+ twoFaVerifySchema,
1942
+ validatePasswordStrength,
1943
+ verifyAccessToken,
1944
+ verifyEmailSchema,
1945
+ verifyTotp
1946
+ };
1947
+ //# sourceMappingURL=index.mjs.map