@factiii/auth 0.1.1 → 0.2.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/{chunk-2DOUP275.mjs → chunk-PYVDWODF.mjs} +6 -4
- package/dist/{hooks-CK4f4PHo.d.mts → hooks-B41uikq7.d.mts} +1 -1
- package/dist/{hooks-CK4f4PHo.d.ts → hooks-B41uikq7.d.ts} +1 -1
- package/dist/index.d.mts +12 -10
- package/dist/index.d.ts +12 -10
- package/dist/index.js +51 -99
- package/dist/index.mjs +46 -96
- package/dist/validators.d.mts +1 -1
- package/dist/validators.d.ts +1 -1
- package/dist/validators.js +6 -4
- package/dist/validators.mjs +1 -1
- package/package.json +28 -5
|
@@ -5,8 +5,10 @@ var signupSchema = z.object({
|
|
|
5
5
|
username: z.string().min(1, { message: "Username is required" }).max(30, { message: "Username must be 30 characters or less" }).regex(usernameValidationRegex, {
|
|
6
6
|
message: "Username can only contain letters, numbers, and underscores"
|
|
7
7
|
}),
|
|
8
|
-
email: z.string().email({ message: "Invalid email address" }),
|
|
9
|
-
password: z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
8
|
+
email: z.string().max(254, { message: "Email must be 254 characters or less" }).email({ message: "Invalid email address" }),
|
|
9
|
+
password: z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" }).refine((val) => val.trim().length >= 6, {
|
|
10
|
+
message: "Password cannot be only whitespace"
|
|
11
|
+
})
|
|
10
12
|
});
|
|
11
13
|
var loginSchema = z.object({
|
|
12
14
|
username: z.string().min(1, { message: "Username or email is required" }),
|
|
@@ -26,14 +28,14 @@ var requestPasswordResetSchema = z.object({
|
|
|
26
28
|
});
|
|
27
29
|
var resetPasswordSchema = z.object({
|
|
28
30
|
token: z.string().min(1, { message: "Reset token is required" }),
|
|
29
|
-
password: z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
31
|
+
password: z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
30
32
|
});
|
|
31
33
|
var checkPasswordResetSchema = z.object({
|
|
32
34
|
token: z.string().min(1, { message: "Reset token is required" })
|
|
33
35
|
});
|
|
34
36
|
var changePasswordSchema = z.object({
|
|
35
37
|
currentPassword: z.string().min(1, { message: "Current password is required" }),
|
|
36
|
-
newPassword: z.string().min(6, { message: "New password must contain at least 6 characters" })
|
|
38
|
+
newPassword: z.string().min(6, { message: "New password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
37
39
|
});
|
|
38
40
|
var twoFaVerifySchema = z.object({
|
|
39
41
|
code: z.string().min(6, { message: "Verification code is required" }),
|
|
@@ -6,7 +6,7 @@ import { z, AnyZodObject } from 'zod';
|
|
|
6
6
|
declare const signupSchema: z.ZodObject<{
|
|
7
7
|
username: z.ZodString;
|
|
8
8
|
email: z.ZodString;
|
|
9
|
-
password: z.ZodString
|
|
9
|
+
password: z.ZodEffects<z.ZodString, string, string>;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
email: string;
|
|
12
12
|
username: string;
|
|
@@ -6,7 +6,7 @@ import { z, AnyZodObject } from 'zod';
|
|
|
6
6
|
declare const signupSchema: z.ZodObject<{
|
|
7
7
|
username: z.ZodString;
|
|
8
8
|
email: z.ZodString;
|
|
9
|
-
password: z.ZodString
|
|
9
|
+
password: z.ZodEffects<z.ZodString, string, string>;
|
|
10
10
|
}, "strip", z.ZodTypeAny, {
|
|
11
11
|
email: string;
|
|
12
12
|
username: string;
|
package/dist/index.d.mts
CHANGED
|
@@ -6,8 +6,8 @@ import { PrismaClient } from '@prisma/client';
|
|
|
6
6
|
import * as _trpc_server from '@trpc/server';
|
|
7
7
|
import * as zod from 'zod';
|
|
8
8
|
import { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
|
|
9
|
-
import { S as SchemaExtensions, A as AuthHooks } from './hooks-
|
|
10
|
-
export { C as ChangePasswordInput, L as LoginInput, a as LogoutInput, O as OAuthLoginInput, R as ResetPasswordInput, b as SignupInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, e as endAllSessionsSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, r as requestPasswordResetSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-
|
|
9
|
+
import { S as SchemaExtensions, A as AuthHooks } from './hooks-B41uikq7.mjs';
|
|
10
|
+
export { C as ChangePasswordInput, L as LoginInput, a as LogoutInput, O as OAuthLoginInput, R as ResetPasswordInput, b as SignupInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, e as endAllSessionsSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, r as requestPasswordResetSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-B41uikq7.mjs';
|
|
11
11
|
import { SignOptions } from 'jsonwebtoken';
|
|
12
12
|
|
|
13
13
|
//# sourceMappingURL=TRPCError.d.ts.map
|
|
@@ -232,6 +232,8 @@ interface TokenSettings {
|
|
|
232
232
|
interface AuthFeatures {
|
|
233
233
|
/** Enable two-factor authentication */
|
|
234
234
|
twoFa?: boolean;
|
|
235
|
+
/** Require mobile device to enable 2FA (default: true). Set to false for testing. */
|
|
236
|
+
twoFaRequiresDevice?: boolean;
|
|
235
237
|
/** OAuth providers configuration */
|
|
236
238
|
oauth?: {
|
|
237
239
|
google?: boolean;
|
|
@@ -657,19 +659,19 @@ declare function createAuthRouter<TExtensions extends SchemaExtensions = {}>(con
|
|
|
657
659
|
input: inferParser<[TExtensions["signup"]] extends [zod.AnyZodObject] ? zod.ZodObject<{
|
|
658
660
|
username: zod.ZodString;
|
|
659
661
|
email: zod.ZodString;
|
|
660
|
-
password: zod.ZodString
|
|
662
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
661
663
|
} & TExtensions["signup"]["shape"], "strip", zod.ZodTypeAny, zod.objectUtil.addQuestionMarks<zod.baseObjectOutputType<{
|
|
662
664
|
username: zod.ZodString;
|
|
663
665
|
email: zod.ZodString;
|
|
664
|
-
password: zod.ZodString
|
|
666
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
665
667
|
} & TExtensions["signup"]["shape"]>, any> extends infer T_5 ? { [k_2 in keyof T_5]: T_5[k_2]; } : never, zod.baseObjectInputType<{
|
|
666
668
|
username: zod.ZodString;
|
|
667
669
|
email: zod.ZodString;
|
|
668
|
-
password: zod.ZodString
|
|
670
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
669
671
|
} & TExtensions["signup"]["shape"]> extends infer T_6 ? { [k_3 in keyof T_6]: T_6[k_3]; } : never> : zod.ZodObject<{
|
|
670
672
|
username: zod.ZodString;
|
|
671
673
|
email: zod.ZodString;
|
|
672
|
-
password: zod.ZodString
|
|
674
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
673
675
|
}, "strip", zod.ZodTypeAny, {
|
|
674
676
|
email: string;
|
|
675
677
|
username: string;
|
|
@@ -681,19 +683,19 @@ declare function createAuthRouter<TExtensions extends SchemaExtensions = {}>(con
|
|
|
681
683
|
}>>["in"] extends infer T_7 ? T_7 extends inferParser<[TExtensions["signup"]] extends [zod.AnyZodObject] ? zod.ZodObject<{
|
|
682
684
|
username: zod.ZodString;
|
|
683
685
|
email: zod.ZodString;
|
|
684
|
-
password: zod.ZodString
|
|
686
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
685
687
|
} & TExtensions["signup"]["shape"], "strip", zod.ZodTypeAny, zod.objectUtil.addQuestionMarks<zod.baseObjectOutputType<{
|
|
686
688
|
username: zod.ZodString;
|
|
687
689
|
email: zod.ZodString;
|
|
688
|
-
password: zod.ZodString
|
|
690
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
689
691
|
} & TExtensions["signup"]["shape"]>, any> extends infer T_8 ? { [k_2 in keyof T_8]: T_8[k_2]; } : never, zod.baseObjectInputType<{
|
|
690
692
|
username: zod.ZodString;
|
|
691
693
|
email: zod.ZodString;
|
|
692
|
-
password: zod.ZodString
|
|
694
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
693
695
|
} & TExtensions["signup"]["shape"]> extends infer T_9 ? { [k_3 in keyof T_9]: T_9[k_3]; } : never> : zod.ZodObject<{
|
|
694
696
|
username: zod.ZodString;
|
|
695
697
|
email: zod.ZodString;
|
|
696
|
-
password: zod.ZodString
|
|
698
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
697
699
|
}, "strip", zod.ZodTypeAny, {
|
|
698
700
|
email: string;
|
|
699
701
|
username: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { PrismaClient } from '@prisma/client';
|
|
|
6
6
|
import * as _trpc_server from '@trpc/server';
|
|
7
7
|
import * as zod from 'zod';
|
|
8
8
|
import { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
|
|
9
|
-
import { S as SchemaExtensions, A as AuthHooks } from './hooks-
|
|
10
|
-
export { C as ChangePasswordInput, L as LoginInput, a as LogoutInput, O as OAuthLoginInput, R as ResetPasswordInput, b as SignupInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, e as endAllSessionsSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, r as requestPasswordResetSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-
|
|
9
|
+
import { S as SchemaExtensions, A as AuthHooks } from './hooks-B41uikq7.js';
|
|
10
|
+
export { C as ChangePasswordInput, L as LoginInput, a as LogoutInput, O as OAuthLoginInput, R as ResetPasswordInput, b as SignupInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, e as endAllSessionsSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, r as requestPasswordResetSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-B41uikq7.js';
|
|
11
11
|
import { SignOptions } from 'jsonwebtoken';
|
|
12
12
|
|
|
13
13
|
//# sourceMappingURL=TRPCError.d.ts.map
|
|
@@ -232,6 +232,8 @@ interface TokenSettings {
|
|
|
232
232
|
interface AuthFeatures {
|
|
233
233
|
/** Enable two-factor authentication */
|
|
234
234
|
twoFa?: boolean;
|
|
235
|
+
/** Require mobile device to enable 2FA (default: true). Set to false for testing. */
|
|
236
|
+
twoFaRequiresDevice?: boolean;
|
|
235
237
|
/** OAuth providers configuration */
|
|
236
238
|
oauth?: {
|
|
237
239
|
google?: boolean;
|
|
@@ -657,19 +659,19 @@ declare function createAuthRouter<TExtensions extends SchemaExtensions = {}>(con
|
|
|
657
659
|
input: inferParser<[TExtensions["signup"]] extends [zod.AnyZodObject] ? zod.ZodObject<{
|
|
658
660
|
username: zod.ZodString;
|
|
659
661
|
email: zod.ZodString;
|
|
660
|
-
password: zod.ZodString
|
|
662
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
661
663
|
} & TExtensions["signup"]["shape"], "strip", zod.ZodTypeAny, zod.objectUtil.addQuestionMarks<zod.baseObjectOutputType<{
|
|
662
664
|
username: zod.ZodString;
|
|
663
665
|
email: zod.ZodString;
|
|
664
|
-
password: zod.ZodString
|
|
666
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
665
667
|
} & TExtensions["signup"]["shape"]>, any> extends infer T_5 ? { [k_2 in keyof T_5]: T_5[k_2]; } : never, zod.baseObjectInputType<{
|
|
666
668
|
username: zod.ZodString;
|
|
667
669
|
email: zod.ZodString;
|
|
668
|
-
password: zod.ZodString
|
|
670
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
669
671
|
} & TExtensions["signup"]["shape"]> extends infer T_6 ? { [k_3 in keyof T_6]: T_6[k_3]; } : never> : zod.ZodObject<{
|
|
670
672
|
username: zod.ZodString;
|
|
671
673
|
email: zod.ZodString;
|
|
672
|
-
password: zod.ZodString
|
|
674
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
673
675
|
}, "strip", zod.ZodTypeAny, {
|
|
674
676
|
email: string;
|
|
675
677
|
username: string;
|
|
@@ -681,19 +683,19 @@ declare function createAuthRouter<TExtensions extends SchemaExtensions = {}>(con
|
|
|
681
683
|
}>>["in"] extends infer T_7 ? T_7 extends inferParser<[TExtensions["signup"]] extends [zod.AnyZodObject] ? zod.ZodObject<{
|
|
682
684
|
username: zod.ZodString;
|
|
683
685
|
email: zod.ZodString;
|
|
684
|
-
password: zod.ZodString
|
|
686
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
685
687
|
} & TExtensions["signup"]["shape"], "strip", zod.ZodTypeAny, zod.objectUtil.addQuestionMarks<zod.baseObjectOutputType<{
|
|
686
688
|
username: zod.ZodString;
|
|
687
689
|
email: zod.ZodString;
|
|
688
|
-
password: zod.ZodString
|
|
690
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
689
691
|
} & TExtensions["signup"]["shape"]>, any> extends infer T_8 ? { [k_2 in keyof T_8]: T_8[k_2]; } : never, zod.baseObjectInputType<{
|
|
690
692
|
username: zod.ZodString;
|
|
691
693
|
email: zod.ZodString;
|
|
692
|
-
password: zod.ZodString
|
|
694
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
693
695
|
} & TExtensions["signup"]["shape"]> extends infer T_9 ? { [k_3 in keyof T_9]: T_9[k_3]; } : never> : zod.ZodObject<{
|
|
694
696
|
username: zod.ZodString;
|
|
695
697
|
email: zod.ZodString;
|
|
696
|
-
password: zod.ZodString
|
|
698
|
+
password: zod.ZodEffects<zod.ZodString, string, string>;
|
|
697
699
|
}, "strip", zod.ZodTypeAny, {
|
|
698
700
|
email: string;
|
|
699
701
|
username: string;
|
package/dist/index.js
CHANGED
|
@@ -86,22 +86,20 @@ var import_server = require("@trpc/server");
|
|
|
86
86
|
function createNoopEmailAdapter() {
|
|
87
87
|
return {
|
|
88
88
|
async sendVerificationEmail(email, code) {
|
|
89
|
-
console.
|
|
89
|
+
console.debug(
|
|
90
90
|
`[NoopEmailAdapter] Would send verification email to ${email} with code ${code}`
|
|
91
91
|
);
|
|
92
92
|
},
|
|
93
93
|
async sendPasswordResetEmail(email, token) {
|
|
94
|
-
console.
|
|
94
|
+
console.debug(
|
|
95
95
|
`[NoopEmailAdapter] Would send password reset email to ${email} with token ${token}`
|
|
96
96
|
);
|
|
97
97
|
},
|
|
98
98
|
async sendOTPEmail(email, otp) {
|
|
99
|
-
console.
|
|
100
|
-
`[NoopEmailAdapter] Would send OTP email to ${email} with code ${otp}`
|
|
101
|
-
);
|
|
99
|
+
console.debug(`[NoopEmailAdapter] Would send OTP email to ${email} with code ${otp}`);
|
|
102
100
|
},
|
|
103
101
|
async sendLoginNotification(email, browserName, ip) {
|
|
104
|
-
console.
|
|
102
|
+
console.debug(
|
|
105
103
|
`[NoopEmailAdapter] Would send login notification to ${email} from ${browserName} (${ip})`
|
|
106
104
|
);
|
|
107
105
|
}
|
|
@@ -160,6 +158,7 @@ var defaultStorageKeys = {
|
|
|
160
158
|
};
|
|
161
159
|
var defaultFeatures = {
|
|
162
160
|
twoFa: true,
|
|
161
|
+
twoFaRequiresDevice: true,
|
|
163
162
|
oauth: { google: true, apple: true },
|
|
164
163
|
biometric: false,
|
|
165
164
|
emailVerification: true,
|
|
@@ -307,9 +306,7 @@ function decodeToken(token) {
|
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
308
|
function isJwtError(error) {
|
|
310
|
-
return error instanceof Error && ["TokenExpiredError", "JsonWebTokenError", "NotBeforeError"].includes(
|
|
311
|
-
error.name
|
|
312
|
-
);
|
|
309
|
+
return error instanceof Error && ["TokenExpiredError", "JsonWebTokenError", "NotBeforeError"].includes(error.name);
|
|
313
310
|
}
|
|
314
311
|
function isTokenExpiredError(error) {
|
|
315
312
|
return isJwtError(error) && error.name === "TokenExpiredError";
|
|
@@ -363,11 +360,7 @@ ${errorStack}` : null,
|
|
|
363
360
|
select: { id: true, userId: true, socketId: true }
|
|
364
361
|
});
|
|
365
362
|
if (session) {
|
|
366
|
-
await config.hooks.onSessionRevoked(
|
|
367
|
-
session.userId,
|
|
368
|
-
session.socketId,
|
|
369
|
-
description
|
|
370
|
-
);
|
|
363
|
+
await config.hooks.onSessionRevoked(session.userId, session.socketId, description);
|
|
371
364
|
}
|
|
372
365
|
}
|
|
373
366
|
} catch {
|
|
@@ -436,13 +429,7 @@ ${errorStack}` : null,
|
|
|
436
429
|
});
|
|
437
430
|
}
|
|
438
431
|
if (session.user.status === "BANNED") {
|
|
439
|
-
await revokeSession(
|
|
440
|
-
ctx,
|
|
441
|
-
session.id,
|
|
442
|
-
"Session revoked: User banned",
|
|
443
|
-
void 0,
|
|
444
|
-
path
|
|
445
|
-
);
|
|
432
|
+
await revokeSession(ctx, session.id, "Session revoked: User banned", void 0, path);
|
|
446
433
|
throw new import_server.TRPCError({
|
|
447
434
|
message: "Unauthorized",
|
|
448
435
|
code: "UNAUTHORIZED"
|
|
@@ -450,9 +437,7 @@ ${errorStack}` : null,
|
|
|
450
437
|
}
|
|
451
438
|
if (config.features?.biometric && config.hooks?.getBiometricTimeout) {
|
|
452
439
|
const timeoutMs = await config.hooks.getBiometricTimeout();
|
|
453
|
-
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(
|
|
454
|
-
path
|
|
455
|
-
)) {
|
|
440
|
+
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(path)) {
|
|
456
441
|
if (!session.user.verifiedHumanAt) {
|
|
457
442
|
throw new import_server.TRPCError({
|
|
458
443
|
message: "Biometric verification not completed. Please verify again.",
|
|
@@ -460,9 +445,7 @@ ${errorStack}` : null,
|
|
|
460
445
|
});
|
|
461
446
|
}
|
|
462
447
|
const now = /* @__PURE__ */ new Date();
|
|
463
|
-
const verificationExpiry = new Date(
|
|
464
|
-
session.user.verifiedHumanAt.getTime() + timeoutMs
|
|
465
|
-
);
|
|
448
|
+
const verificationExpiry = new Date(session.user.verifiedHumanAt.getTime() + timeoutMs);
|
|
466
449
|
if (now > verificationExpiry) {
|
|
467
450
|
throw new import_server.TRPCError({
|
|
468
451
|
message: "Biometric verification expired. Please verify again.",
|
|
@@ -534,13 +517,7 @@ ${errorStack}` : null,
|
|
|
534
517
|
});
|
|
535
518
|
}
|
|
536
519
|
if (err instanceof import_server.TRPCError && err.code === "UNAUTHORIZED") {
|
|
537
|
-
await revokeSession(
|
|
538
|
-
ctx,
|
|
539
|
-
null,
|
|
540
|
-
"Session revoked: Unauthorized",
|
|
541
|
-
errorStack,
|
|
542
|
-
path
|
|
543
|
-
);
|
|
520
|
+
await revokeSession(ctx, null, "Session revoked: Unauthorized", errorStack, path);
|
|
544
521
|
throw new import_server.TRPCError({
|
|
545
522
|
message: "Unauthorized",
|
|
546
523
|
code: "UNAUTHORIZED"
|
|
@@ -552,13 +529,7 @@ ${errorStack}` : null,
|
|
|
552
529
|
if (!meta?.authRequired) {
|
|
553
530
|
return next({ ctx: { ...ctx, userId: 0 } });
|
|
554
531
|
}
|
|
555
|
-
await revokeSession(
|
|
556
|
-
ctx,
|
|
557
|
-
null,
|
|
558
|
-
"Session revoked: No token sent",
|
|
559
|
-
void 0,
|
|
560
|
-
path
|
|
561
|
-
);
|
|
532
|
+
await revokeSession(ctx, null, "Session revoked: No token sent", void 0, path);
|
|
562
533
|
throw new import_server.TRPCError({ message: "Unauthorized", code: "UNAUTHORIZED" });
|
|
563
534
|
}
|
|
564
535
|
});
|
|
@@ -579,25 +550,19 @@ function detectBrowser(userAgent) {
|
|
|
579
550
|
return "iOS Browser (Chrome)";
|
|
580
551
|
if (/iphone|ipad|ipod/i.test(userAgent) && /fxios/i.test(userAgent))
|
|
581
552
|
return "iOS Browser (Firefox)";
|
|
582
|
-
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
583
|
-
return "iOS Browser (Edge)";
|
|
553
|
+
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent)) return "iOS Browser (Edge)";
|
|
584
554
|
if (/android/i.test(userAgent) && !/chrome|firefox|samsungbrowser|opr\/|edg\//i.test(userAgent)) {
|
|
585
555
|
return "Android App";
|
|
586
556
|
}
|
|
587
|
-
if (/android/i.test(userAgent) && /chrome/i.test(userAgent))
|
|
588
|
-
|
|
589
|
-
if (/android/i.test(userAgent) && /firefox/i.test(userAgent))
|
|
590
|
-
return "Android Browser (Firefox)";
|
|
557
|
+
if (/android/i.test(userAgent) && /chrome/i.test(userAgent)) return "Android Browser (Chrome)";
|
|
558
|
+
if (/android/i.test(userAgent) && /firefox/i.test(userAgent)) return "Android Browser (Firefox)";
|
|
591
559
|
if (/android/i.test(userAgent) && /samsungbrowser/i.test(userAgent))
|
|
592
560
|
return "Android Browser (Samsung)";
|
|
593
|
-
if (/android/i.test(userAgent) && /opr\//i.test(userAgent))
|
|
594
|
-
|
|
595
|
-
if (/android/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
596
|
-
return "Android Browser (Edge)";
|
|
561
|
+
if (/android/i.test(userAgent) && /opr\//i.test(userAgent)) return "Android Browser (Opera)";
|
|
562
|
+
if (/android/i.test(userAgent) && /edg\//i.test(userAgent)) return "Android Browser (Edge)";
|
|
597
563
|
if (/chrome|chromium/i.test(userAgent)) return "Chrome";
|
|
598
564
|
if (/firefox/i.test(userAgent)) return "Firefox";
|
|
599
|
-
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent))
|
|
600
|
-
return "Safari";
|
|
565
|
+
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent)) return "Safari";
|
|
601
566
|
if (/opr\//i.test(userAgent)) return "Opera";
|
|
602
567
|
if (/edg\//i.test(userAgent)) return "Edge";
|
|
603
568
|
return "Unknown";
|
|
@@ -631,16 +596,10 @@ function createOAuthVerifier(keys) {
|
|
|
631
596
|
return async function verifyOAuthToken(provider, token, extra) {
|
|
632
597
|
if (provider === "GOOGLE") {
|
|
633
598
|
if (!keys.google?.clientId) {
|
|
634
|
-
throw new OAuthVerificationError(
|
|
635
|
-
"Google OAuth configuration missing",
|
|
636
|
-
500
|
|
637
|
-
);
|
|
599
|
+
throw new OAuthVerificationError("Google OAuth configuration missing", 500);
|
|
638
600
|
}
|
|
639
601
|
if (!googleClient) {
|
|
640
|
-
throw new OAuthVerificationError(
|
|
641
|
-
"Google OAuth client not initialized",
|
|
642
|
-
500
|
|
643
|
-
);
|
|
602
|
+
throw new OAuthVerificationError("Google OAuth client not initialized", 500);
|
|
644
603
|
}
|
|
645
604
|
const audience = [keys.google.clientId];
|
|
646
605
|
if (keys.google.iosClientId) {
|
|
@@ -661,10 +620,7 @@ function createOAuthVerifier(keys) {
|
|
|
661
620
|
}
|
|
662
621
|
if (provider === "APPLE") {
|
|
663
622
|
if (!keys.apple?.clientId) {
|
|
664
|
-
throw new OAuthVerificationError(
|
|
665
|
-
"Apple OAuth configuration missing",
|
|
666
|
-
500
|
|
667
|
-
);
|
|
623
|
+
throw new OAuthVerificationError("Apple OAuth configuration missing", 500);
|
|
668
624
|
}
|
|
669
625
|
const audience = [keys.apple.clientId];
|
|
670
626
|
if (keys.apple.iosClientId) {
|
|
@@ -744,8 +700,10 @@ var signupSchema = import_zod.z.object({
|
|
|
744
700
|
username: import_zod.z.string().min(1, { message: "Username is required" }).max(30, { message: "Username must be 30 characters or less" }).regex(usernameValidationRegex, {
|
|
745
701
|
message: "Username can only contain letters, numbers, and underscores"
|
|
746
702
|
}),
|
|
747
|
-
email: import_zod.z.string().email({ message: "Invalid email address" }),
|
|
748
|
-
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
703
|
+
email: import_zod.z.string().max(254, { message: "Email must be 254 characters or less" }).email({ message: "Invalid email address" }),
|
|
704
|
+
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" }).refine((val) => val.trim().length >= 6, {
|
|
705
|
+
message: "Password cannot be only whitespace"
|
|
706
|
+
})
|
|
749
707
|
});
|
|
750
708
|
var loginSchema = import_zod.z.object({
|
|
751
709
|
username: import_zod.z.string().min(1, { message: "Username or email is required" }),
|
|
@@ -765,14 +723,14 @@ var requestPasswordResetSchema = import_zod.z.object({
|
|
|
765
723
|
});
|
|
766
724
|
var resetPasswordSchema = import_zod.z.object({
|
|
767
725
|
token: import_zod.z.string().min(1, { message: "Reset token is required" }),
|
|
768
|
-
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
726
|
+
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
769
727
|
});
|
|
770
728
|
var checkPasswordResetSchema = import_zod.z.object({
|
|
771
729
|
token: import_zod.z.string().min(1, { message: "Reset token is required" })
|
|
772
730
|
});
|
|
773
731
|
var changePasswordSchema = import_zod.z.object({
|
|
774
732
|
currentPassword: import_zod.z.string().min(1, { message: "Current password is required" }),
|
|
775
|
-
newPassword: import_zod.z.string().min(6, { message: "New password must contain at least 6 characters" })
|
|
733
|
+
newPassword: import_zod.z.string().min(6, { message: "New password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
776
734
|
});
|
|
777
735
|
var twoFaVerifySchema = import_zod.z.object({
|
|
778
736
|
code: import_zod.z.string().min(6, { message: "Verification code is required" }),
|
|
@@ -1162,7 +1120,11 @@ var BaseProcedureFactory = class {
|
|
|
1162
1120
|
});
|
|
1163
1121
|
for (const session of sessionsToRevoke) {
|
|
1164
1122
|
if (this.config.hooks?.onSessionRevoked) {
|
|
1165
|
-
await this.config.hooks.onSessionRevoked(
|
|
1123
|
+
await this.config.hooks.onSessionRevoked(
|
|
1124
|
+
session.id,
|
|
1125
|
+
session.socketId,
|
|
1126
|
+
"End all sessions"
|
|
1127
|
+
);
|
|
1166
1128
|
}
|
|
1167
1129
|
}
|
|
1168
1130
|
if (!skipCurrentSession) {
|
|
@@ -1491,14 +1453,14 @@ var OAuthLoginProcedureFactory = class {
|
|
|
1491
1453
|
}
|
|
1492
1454
|
const { email, oauthId } = await this.verifyOAuthToken(provider, idToken, appleUser);
|
|
1493
1455
|
if (!email) {
|
|
1494
|
-
throw new import_server5.TRPCError({
|
|
1456
|
+
throw new import_server5.TRPCError({
|
|
1457
|
+
code: "BAD_REQUEST",
|
|
1458
|
+
message: "Email not provided by OAuth provider"
|
|
1459
|
+
});
|
|
1495
1460
|
}
|
|
1496
1461
|
let user = await this.config.prisma.user.findFirst({
|
|
1497
1462
|
where: {
|
|
1498
|
-
OR: [
|
|
1499
|
-
{ email: { equals: email, mode: "insensitive" } },
|
|
1500
|
-
{ oauthId: { equals: oauthId } }
|
|
1501
|
-
]
|
|
1463
|
+
OR: [{ email: { equals: email, mode: "insensitive" } }, { oauthId: { equals: oauthId } }]
|
|
1502
1464
|
},
|
|
1503
1465
|
select: {
|
|
1504
1466
|
id: true,
|
|
@@ -1648,15 +1610,17 @@ var TwoFaProcedureFactory = class {
|
|
|
1648
1610
|
if (user.twoFaEnabled) {
|
|
1649
1611
|
throw new import_server6.TRPCError({ code: "BAD_REQUEST", message: "2FA already enabled." });
|
|
1650
1612
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
if (!checkSession?.deviceId) {
|
|
1656
|
-
throw new import_server6.TRPCError({
|
|
1657
|
-
code: "BAD_REQUEST",
|
|
1658
|
-
message: "You must be logged in on mobile to enable 2FA."
|
|
1613
|
+
if (this.config.features.twoFaRequiresDevice !== false) {
|
|
1614
|
+
const checkSession = await this.config.prisma.session.findFirst({
|
|
1615
|
+
where: { userId, id: sessionId },
|
|
1616
|
+
select: { deviceId: true }
|
|
1659
1617
|
});
|
|
1618
|
+
if (!checkSession?.deviceId) {
|
|
1619
|
+
throw new import_server6.TRPCError({
|
|
1620
|
+
code: "BAD_REQUEST",
|
|
1621
|
+
message: "You must be logged in on mobile to enable 2FA."
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1660
1624
|
}
|
|
1661
1625
|
await this.config.prisma.session.updateMany({
|
|
1662
1626
|
where: { userId, revokedAt: null, NOT: { id: sessionId } },
|
|
@@ -1975,10 +1939,7 @@ function getClientIp(req) {
|
|
|
1975
1939
|
}
|
|
1976
1940
|
|
|
1977
1941
|
// src/router.ts
|
|
1978
|
-
var createContext = ({
|
|
1979
|
-
req,
|
|
1980
|
-
res
|
|
1981
|
-
}) => ({
|
|
1942
|
+
var createContext = ({ req, res }) => ({
|
|
1982
1943
|
headers: req.headers,
|
|
1983
1944
|
userId: null,
|
|
1984
1945
|
sessionId: null,
|
|
@@ -1991,9 +1952,7 @@ var AuthRouterFactory = class {
|
|
|
1991
1952
|
constructor(userConfig) {
|
|
1992
1953
|
this.userConfig = userConfig;
|
|
1993
1954
|
this.config = createAuthConfig(this.userConfig);
|
|
1994
|
-
this.schemas = createSchemas(
|
|
1995
|
-
this.config.schemaExtensions
|
|
1996
|
-
);
|
|
1955
|
+
this.schemas = createSchemas(this.config.schemaExtensions);
|
|
1997
1956
|
this.t = createTrpcBuilder(this.config);
|
|
1998
1957
|
this.authGuard = createAuthGuard(this.config, this.t);
|
|
1999
1958
|
this.procedure = createBaseProcedure(this.t, this.authGuard);
|
|
@@ -2005,10 +1964,7 @@ var AuthRouterFactory = class {
|
|
|
2005
1964
|
this.procedure,
|
|
2006
1965
|
this.authProcedure
|
|
2007
1966
|
);
|
|
2008
|
-
const biometricRoutes = new BiometricProcedureFactory(
|
|
2009
|
-
this.config,
|
|
2010
|
-
this.authProcedure
|
|
2011
|
-
);
|
|
1967
|
+
const biometricRoutes = new BiometricProcedureFactory(this.config, this.authProcedure);
|
|
2012
1968
|
const emailVerificationRoutes = new EmailVerificationProcedureFactory(
|
|
2013
1969
|
this.config,
|
|
2014
1970
|
this.authProcedure
|
|
@@ -2017,11 +1973,7 @@ var AuthRouterFactory = class {
|
|
|
2017
1973
|
this.config,
|
|
2018
1974
|
this.procedure
|
|
2019
1975
|
);
|
|
2020
|
-
const twoFaRoutes = new TwoFaProcedureFactory(
|
|
2021
|
-
this.config,
|
|
2022
|
-
this.procedure,
|
|
2023
|
-
this.authProcedure
|
|
2024
|
-
);
|
|
1976
|
+
const twoFaRoutes = new TwoFaProcedureFactory(this.config, this.procedure, this.authProcedure);
|
|
2025
1977
|
return this.t.router({
|
|
2026
1978
|
...baseRoutes.createBaseProcedures(this.schemas),
|
|
2027
1979
|
...oAuthLoginRoutes.createOAuthLoginProcedures(this.schemas),
|
package/dist/index.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
twoFaSetupSchema,
|
|
22
22
|
twoFaVerifySchema,
|
|
23
23
|
verifyEmailSchema
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-PYVDWODF.mjs";
|
|
25
25
|
|
|
26
26
|
// src/middleware/authGuard.ts
|
|
27
27
|
import { TRPCError } from "@trpc/server";
|
|
@@ -30,22 +30,20 @@ import { TRPCError } from "@trpc/server";
|
|
|
30
30
|
function createNoopEmailAdapter() {
|
|
31
31
|
return {
|
|
32
32
|
async sendVerificationEmail(email, code) {
|
|
33
|
-
console.
|
|
33
|
+
console.debug(
|
|
34
34
|
`[NoopEmailAdapter] Would send verification email to ${email} with code ${code}`
|
|
35
35
|
);
|
|
36
36
|
},
|
|
37
37
|
async sendPasswordResetEmail(email, token) {
|
|
38
|
-
console.
|
|
38
|
+
console.debug(
|
|
39
39
|
`[NoopEmailAdapter] Would send password reset email to ${email} with token ${token}`
|
|
40
40
|
);
|
|
41
41
|
},
|
|
42
42
|
async sendOTPEmail(email, otp) {
|
|
43
|
-
console.
|
|
44
|
-
`[NoopEmailAdapter] Would send OTP email to ${email} with code ${otp}`
|
|
45
|
-
);
|
|
43
|
+
console.debug(`[NoopEmailAdapter] Would send OTP email to ${email} with code ${otp}`);
|
|
46
44
|
},
|
|
47
45
|
async sendLoginNotification(email, browserName, ip) {
|
|
48
|
-
console.
|
|
46
|
+
console.debug(
|
|
49
47
|
`[NoopEmailAdapter] Would send login notification to ${email} from ${browserName} (${ip})`
|
|
50
48
|
);
|
|
51
49
|
}
|
|
@@ -104,6 +102,7 @@ var defaultStorageKeys = {
|
|
|
104
102
|
};
|
|
105
103
|
var defaultFeatures = {
|
|
106
104
|
twoFa: true,
|
|
105
|
+
twoFaRequiresDevice: true,
|
|
107
106
|
oauth: { google: true, apple: true },
|
|
108
107
|
biometric: false,
|
|
109
108
|
emailVerification: true,
|
|
@@ -251,9 +250,7 @@ function decodeToken(token) {
|
|
|
251
250
|
}
|
|
252
251
|
}
|
|
253
252
|
function isJwtError(error) {
|
|
254
|
-
return error instanceof Error && ["TokenExpiredError", "JsonWebTokenError", "NotBeforeError"].includes(
|
|
255
|
-
error.name
|
|
256
|
-
);
|
|
253
|
+
return error instanceof Error && ["TokenExpiredError", "JsonWebTokenError", "NotBeforeError"].includes(error.name);
|
|
257
254
|
}
|
|
258
255
|
function isTokenExpiredError(error) {
|
|
259
256
|
return isJwtError(error) && error.name === "TokenExpiredError";
|
|
@@ -307,11 +304,7 @@ ${errorStack}` : null,
|
|
|
307
304
|
select: { id: true, userId: true, socketId: true }
|
|
308
305
|
});
|
|
309
306
|
if (session) {
|
|
310
|
-
await config.hooks.onSessionRevoked(
|
|
311
|
-
session.userId,
|
|
312
|
-
session.socketId,
|
|
313
|
-
description
|
|
314
|
-
);
|
|
307
|
+
await config.hooks.onSessionRevoked(session.userId, session.socketId, description);
|
|
315
308
|
}
|
|
316
309
|
}
|
|
317
310
|
} catch {
|
|
@@ -380,13 +373,7 @@ ${errorStack}` : null,
|
|
|
380
373
|
});
|
|
381
374
|
}
|
|
382
375
|
if (session.user.status === "BANNED") {
|
|
383
|
-
await revokeSession(
|
|
384
|
-
ctx,
|
|
385
|
-
session.id,
|
|
386
|
-
"Session revoked: User banned",
|
|
387
|
-
void 0,
|
|
388
|
-
path
|
|
389
|
-
);
|
|
376
|
+
await revokeSession(ctx, session.id, "Session revoked: User banned", void 0, path);
|
|
390
377
|
throw new TRPCError({
|
|
391
378
|
message: "Unauthorized",
|
|
392
379
|
code: "UNAUTHORIZED"
|
|
@@ -394,9 +381,7 @@ ${errorStack}` : null,
|
|
|
394
381
|
}
|
|
395
382
|
if (config.features?.biometric && config.hooks?.getBiometricTimeout) {
|
|
396
383
|
const timeoutMs = await config.hooks.getBiometricTimeout();
|
|
397
|
-
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(
|
|
398
|
-
path
|
|
399
|
-
)) {
|
|
384
|
+
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(path)) {
|
|
400
385
|
if (!session.user.verifiedHumanAt) {
|
|
401
386
|
throw new TRPCError({
|
|
402
387
|
message: "Biometric verification not completed. Please verify again.",
|
|
@@ -404,9 +389,7 @@ ${errorStack}` : null,
|
|
|
404
389
|
});
|
|
405
390
|
}
|
|
406
391
|
const now = /* @__PURE__ */ new Date();
|
|
407
|
-
const verificationExpiry = new Date(
|
|
408
|
-
session.user.verifiedHumanAt.getTime() + timeoutMs
|
|
409
|
-
);
|
|
392
|
+
const verificationExpiry = new Date(session.user.verifiedHumanAt.getTime() + timeoutMs);
|
|
410
393
|
if (now > verificationExpiry) {
|
|
411
394
|
throw new TRPCError({
|
|
412
395
|
message: "Biometric verification expired. Please verify again.",
|
|
@@ -478,13 +461,7 @@ ${errorStack}` : null,
|
|
|
478
461
|
});
|
|
479
462
|
}
|
|
480
463
|
if (err instanceof TRPCError && err.code === "UNAUTHORIZED") {
|
|
481
|
-
await revokeSession(
|
|
482
|
-
ctx,
|
|
483
|
-
null,
|
|
484
|
-
"Session revoked: Unauthorized",
|
|
485
|
-
errorStack,
|
|
486
|
-
path
|
|
487
|
-
);
|
|
464
|
+
await revokeSession(ctx, null, "Session revoked: Unauthorized", errorStack, path);
|
|
488
465
|
throw new TRPCError({
|
|
489
466
|
message: "Unauthorized",
|
|
490
467
|
code: "UNAUTHORIZED"
|
|
@@ -496,13 +473,7 @@ ${errorStack}` : null,
|
|
|
496
473
|
if (!meta?.authRequired) {
|
|
497
474
|
return next({ ctx: { ...ctx, userId: 0 } });
|
|
498
475
|
}
|
|
499
|
-
await revokeSession(
|
|
500
|
-
ctx,
|
|
501
|
-
null,
|
|
502
|
-
"Session revoked: No token sent",
|
|
503
|
-
void 0,
|
|
504
|
-
path
|
|
505
|
-
);
|
|
476
|
+
await revokeSession(ctx, null, "Session revoked: No token sent", void 0, path);
|
|
506
477
|
throw new TRPCError({ message: "Unauthorized", code: "UNAUTHORIZED" });
|
|
507
478
|
}
|
|
508
479
|
});
|
|
@@ -523,25 +494,19 @@ function detectBrowser(userAgent) {
|
|
|
523
494
|
return "iOS Browser (Chrome)";
|
|
524
495
|
if (/iphone|ipad|ipod/i.test(userAgent) && /fxios/i.test(userAgent))
|
|
525
496
|
return "iOS Browser (Firefox)";
|
|
526
|
-
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
527
|
-
return "iOS Browser (Edge)";
|
|
497
|
+
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent)) return "iOS Browser (Edge)";
|
|
528
498
|
if (/android/i.test(userAgent) && !/chrome|firefox|samsungbrowser|opr\/|edg\//i.test(userAgent)) {
|
|
529
499
|
return "Android App";
|
|
530
500
|
}
|
|
531
|
-
if (/android/i.test(userAgent) && /chrome/i.test(userAgent))
|
|
532
|
-
|
|
533
|
-
if (/android/i.test(userAgent) && /firefox/i.test(userAgent))
|
|
534
|
-
return "Android Browser (Firefox)";
|
|
501
|
+
if (/android/i.test(userAgent) && /chrome/i.test(userAgent)) return "Android Browser (Chrome)";
|
|
502
|
+
if (/android/i.test(userAgent) && /firefox/i.test(userAgent)) return "Android Browser (Firefox)";
|
|
535
503
|
if (/android/i.test(userAgent) && /samsungbrowser/i.test(userAgent))
|
|
536
504
|
return "Android Browser (Samsung)";
|
|
537
|
-
if (/android/i.test(userAgent) && /opr\//i.test(userAgent))
|
|
538
|
-
|
|
539
|
-
if (/android/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
540
|
-
return "Android Browser (Edge)";
|
|
505
|
+
if (/android/i.test(userAgent) && /opr\//i.test(userAgent)) return "Android Browser (Opera)";
|
|
506
|
+
if (/android/i.test(userAgent) && /edg\//i.test(userAgent)) return "Android Browser (Edge)";
|
|
541
507
|
if (/chrome|chromium/i.test(userAgent)) return "Chrome";
|
|
542
508
|
if (/firefox/i.test(userAgent)) return "Firefox";
|
|
543
|
-
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent))
|
|
544
|
-
return "Safari";
|
|
509
|
+
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent)) return "Safari";
|
|
545
510
|
if (/opr\//i.test(userAgent)) return "Opera";
|
|
546
511
|
if (/edg\//i.test(userAgent)) return "Edge";
|
|
547
512
|
return "Unknown";
|
|
@@ -575,16 +540,10 @@ function createOAuthVerifier(keys) {
|
|
|
575
540
|
return async function verifyOAuthToken(provider, token, extra) {
|
|
576
541
|
if (provider === "GOOGLE") {
|
|
577
542
|
if (!keys.google?.clientId) {
|
|
578
|
-
throw new OAuthVerificationError(
|
|
579
|
-
"Google OAuth configuration missing",
|
|
580
|
-
500
|
|
581
|
-
);
|
|
543
|
+
throw new OAuthVerificationError("Google OAuth configuration missing", 500);
|
|
582
544
|
}
|
|
583
545
|
if (!googleClient) {
|
|
584
|
-
throw new OAuthVerificationError(
|
|
585
|
-
"Google OAuth client not initialized",
|
|
586
|
-
500
|
|
587
|
-
);
|
|
546
|
+
throw new OAuthVerificationError("Google OAuth client not initialized", 500);
|
|
588
547
|
}
|
|
589
548
|
const audience = [keys.google.clientId];
|
|
590
549
|
if (keys.google.iosClientId) {
|
|
@@ -605,10 +564,7 @@ function createOAuthVerifier(keys) {
|
|
|
605
564
|
}
|
|
606
565
|
if (provider === "APPLE") {
|
|
607
566
|
if (!keys.apple?.clientId) {
|
|
608
|
-
throw new OAuthVerificationError(
|
|
609
|
-
"Apple OAuth configuration missing",
|
|
610
|
-
500
|
|
611
|
-
);
|
|
567
|
+
throw new OAuthVerificationError("Apple OAuth configuration missing", 500);
|
|
612
568
|
}
|
|
613
569
|
const audience = [keys.apple.clientId];
|
|
614
570
|
if (keys.apple.iosClientId) {
|
|
@@ -1014,7 +970,11 @@ var BaseProcedureFactory = class {
|
|
|
1014
970
|
});
|
|
1015
971
|
for (const session of sessionsToRevoke) {
|
|
1016
972
|
if (this.config.hooks?.onSessionRevoked) {
|
|
1017
|
-
await this.config.hooks.onSessionRevoked(
|
|
973
|
+
await this.config.hooks.onSessionRevoked(
|
|
974
|
+
session.id,
|
|
975
|
+
session.socketId,
|
|
976
|
+
"End all sessions"
|
|
977
|
+
);
|
|
1018
978
|
}
|
|
1019
979
|
}
|
|
1020
980
|
if (!skipCurrentSession) {
|
|
@@ -1343,14 +1303,14 @@ var OAuthLoginProcedureFactory = class {
|
|
|
1343
1303
|
}
|
|
1344
1304
|
const { email, oauthId } = await this.verifyOAuthToken(provider, idToken, appleUser);
|
|
1345
1305
|
if (!email) {
|
|
1346
|
-
throw new TRPCError5({
|
|
1306
|
+
throw new TRPCError5({
|
|
1307
|
+
code: "BAD_REQUEST",
|
|
1308
|
+
message: "Email not provided by OAuth provider"
|
|
1309
|
+
});
|
|
1347
1310
|
}
|
|
1348
1311
|
let user = await this.config.prisma.user.findFirst({
|
|
1349
1312
|
where: {
|
|
1350
|
-
OR: [
|
|
1351
|
-
{ email: { equals: email, mode: "insensitive" } },
|
|
1352
|
-
{ oauthId: { equals: oauthId } }
|
|
1353
|
-
]
|
|
1313
|
+
OR: [{ email: { equals: email, mode: "insensitive" } }, { oauthId: { equals: oauthId } }]
|
|
1354
1314
|
},
|
|
1355
1315
|
select: {
|
|
1356
1316
|
id: true,
|
|
@@ -1500,15 +1460,17 @@ var TwoFaProcedureFactory = class {
|
|
|
1500
1460
|
if (user.twoFaEnabled) {
|
|
1501
1461
|
throw new TRPCError6({ code: "BAD_REQUEST", message: "2FA already enabled." });
|
|
1502
1462
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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."
|
|
1463
|
+
if (this.config.features.twoFaRequiresDevice !== false) {
|
|
1464
|
+
const checkSession = await this.config.prisma.session.findFirst({
|
|
1465
|
+
where: { userId, id: sessionId },
|
|
1466
|
+
select: { deviceId: true }
|
|
1511
1467
|
});
|
|
1468
|
+
if (!checkSession?.deviceId) {
|
|
1469
|
+
throw new TRPCError6({
|
|
1470
|
+
code: "BAD_REQUEST",
|
|
1471
|
+
message: "You must be logged in on mobile to enable 2FA."
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1512
1474
|
}
|
|
1513
1475
|
await this.config.prisma.session.updateMany({
|
|
1514
1476
|
where: { userId, revokedAt: null, NOT: { id: sessionId } },
|
|
@@ -1827,10 +1789,7 @@ function getClientIp(req) {
|
|
|
1827
1789
|
}
|
|
1828
1790
|
|
|
1829
1791
|
// src/router.ts
|
|
1830
|
-
var createContext = ({
|
|
1831
|
-
req,
|
|
1832
|
-
res
|
|
1833
|
-
}) => ({
|
|
1792
|
+
var createContext = ({ req, res }) => ({
|
|
1834
1793
|
headers: req.headers,
|
|
1835
1794
|
userId: null,
|
|
1836
1795
|
sessionId: null,
|
|
@@ -1843,9 +1802,7 @@ var AuthRouterFactory = class {
|
|
|
1843
1802
|
constructor(userConfig) {
|
|
1844
1803
|
this.userConfig = userConfig;
|
|
1845
1804
|
this.config = createAuthConfig(this.userConfig);
|
|
1846
|
-
this.schemas = createSchemas(
|
|
1847
|
-
this.config.schemaExtensions
|
|
1848
|
-
);
|
|
1805
|
+
this.schemas = createSchemas(this.config.schemaExtensions);
|
|
1849
1806
|
this.t = createTrpcBuilder(this.config);
|
|
1850
1807
|
this.authGuard = createAuthGuard(this.config, this.t);
|
|
1851
1808
|
this.procedure = createBaseProcedure(this.t, this.authGuard);
|
|
@@ -1857,10 +1814,7 @@ var AuthRouterFactory = class {
|
|
|
1857
1814
|
this.procedure,
|
|
1858
1815
|
this.authProcedure
|
|
1859
1816
|
);
|
|
1860
|
-
const biometricRoutes = new BiometricProcedureFactory(
|
|
1861
|
-
this.config,
|
|
1862
|
-
this.authProcedure
|
|
1863
|
-
);
|
|
1817
|
+
const biometricRoutes = new BiometricProcedureFactory(this.config, this.authProcedure);
|
|
1864
1818
|
const emailVerificationRoutes = new EmailVerificationProcedureFactory(
|
|
1865
1819
|
this.config,
|
|
1866
1820
|
this.authProcedure
|
|
@@ -1869,11 +1823,7 @@ var AuthRouterFactory = class {
|
|
|
1869
1823
|
this.config,
|
|
1870
1824
|
this.procedure
|
|
1871
1825
|
);
|
|
1872
|
-
const twoFaRoutes = new TwoFaProcedureFactory(
|
|
1873
|
-
this.config,
|
|
1874
|
-
this.procedure,
|
|
1875
|
-
this.authProcedure
|
|
1876
|
-
);
|
|
1826
|
+
const twoFaRoutes = new TwoFaProcedureFactory(this.config, this.procedure, this.authProcedure);
|
|
1877
1827
|
return this.t.router({
|
|
1878
1828
|
...baseRoutes.createBaseProcedures(this.schemas),
|
|
1879
1829
|
...oAuthLoginRoutes.createOAuthLoginProcedures(this.schemas),
|
package/dist/validators.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import 'zod';
|
|
2
|
-
export { m as AuthSchemas, C as ChangePasswordInput, n as CreatedSchemas, L as LoginInput, p as LoginSchemaInput, a as LogoutInput, O as OAuthLoginInput, q as OAuthSchemaInput, R as ResetPasswordInput, b as SignupInput, u as SignupSchemaInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, w as checkPasswordResetSchema, x as createSchemas, y as deregisterPushTokenSchema, z as disableTwofaSchema, e as endAllSessionsSchema, B as getTwofaSecretSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, D as registerPushTokenSchema, r as requestPasswordResetSchema, E as resendVerificationSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, F as twoFaResetVerifySchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-
|
|
2
|
+
export { m as AuthSchemas, C as ChangePasswordInput, n as CreatedSchemas, L as LoginInput, p as LoginSchemaInput, a as LogoutInput, O as OAuthLoginInput, q as OAuthSchemaInput, R as ResetPasswordInput, b as SignupInput, u as SignupSchemaInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, w as checkPasswordResetSchema, x as createSchemas, y as deregisterPushTokenSchema, z as disableTwofaSchema, e as endAllSessionsSchema, B as getTwofaSecretSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, D as registerPushTokenSchema, r as requestPasswordResetSchema, E as resendVerificationSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, F as twoFaResetVerifySchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-B41uikq7.mjs';
|
package/dist/validators.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import 'zod';
|
|
2
|
-
export { m as AuthSchemas, C as ChangePasswordInput, n as CreatedSchemas, L as LoginInput, p as LoginSchemaInput, a as LogoutInput, O as OAuthLoginInput, q as OAuthSchemaInput, R as ResetPasswordInput, b as SignupInput, u as SignupSchemaInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, w as checkPasswordResetSchema, x as createSchemas, y as deregisterPushTokenSchema, z as disableTwofaSchema, e as endAllSessionsSchema, B as getTwofaSecretSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, D as registerPushTokenSchema, r as requestPasswordResetSchema, E as resendVerificationSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, F as twoFaResetVerifySchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-
|
|
2
|
+
export { m as AuthSchemas, C as ChangePasswordInput, n as CreatedSchemas, L as LoginInput, p as LoginSchemaInput, a as LogoutInput, O as OAuthLoginInput, q as OAuthSchemaInput, R as ResetPasswordInput, b as SignupInput, u as SignupSchemaInput, T as TwoFaVerifyInput, V as VerifyEmailInput, c as biometricVerifySchema, d as changePasswordSchema, w as checkPasswordResetSchema, x as createSchemas, y as deregisterPushTokenSchema, z as disableTwofaSchema, e as endAllSessionsSchema, B as getTwofaSecretSchema, l as loginSchema, f as logoutSchema, o as oAuthLoginSchema, g as otpLoginRequestSchema, h as otpLoginVerifySchema, D as registerPushTokenSchema, r as requestPasswordResetSchema, E as resendVerificationSchema, i as resetPasswordSchema, s as signupSchema, t as twoFaResetSchema, F as twoFaResetVerifySchema, j as twoFaSetupSchema, k as twoFaVerifySchema, v as verifyEmailSchema } from './hooks-B41uikq7.js';
|
package/dist/validators.js
CHANGED
|
@@ -51,8 +51,10 @@ var signupSchema = import_zod.z.object({
|
|
|
51
51
|
username: import_zod.z.string().min(1, { message: "Username is required" }).max(30, { message: "Username must be 30 characters or less" }).regex(usernameValidationRegex, {
|
|
52
52
|
message: "Username can only contain letters, numbers, and underscores"
|
|
53
53
|
}),
|
|
54
|
-
email: import_zod.z.string().email({ message: "Invalid email address" }),
|
|
55
|
-
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
54
|
+
email: import_zod.z.string().max(254, { message: "Email must be 254 characters or less" }).email({ message: "Invalid email address" }),
|
|
55
|
+
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" }).refine((val) => val.trim().length >= 6, {
|
|
56
|
+
message: "Password cannot be only whitespace"
|
|
57
|
+
})
|
|
56
58
|
});
|
|
57
59
|
var loginSchema = import_zod.z.object({
|
|
58
60
|
username: import_zod.z.string().min(1, { message: "Username or email is required" }),
|
|
@@ -72,14 +74,14 @@ var requestPasswordResetSchema = import_zod.z.object({
|
|
|
72
74
|
});
|
|
73
75
|
var resetPasswordSchema = import_zod.z.object({
|
|
74
76
|
token: import_zod.z.string().min(1, { message: "Reset token is required" }),
|
|
75
|
-
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" })
|
|
77
|
+
password: import_zod.z.string().min(6, { message: "Password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
76
78
|
});
|
|
77
79
|
var checkPasswordResetSchema = import_zod.z.object({
|
|
78
80
|
token: import_zod.z.string().min(1, { message: "Reset token is required" })
|
|
79
81
|
});
|
|
80
82
|
var changePasswordSchema = import_zod.z.object({
|
|
81
83
|
currentPassword: import_zod.z.string().min(1, { message: "Current password is required" }),
|
|
82
|
-
newPassword: import_zod.z.string().min(6, { message: "New password must contain at least 6 characters" })
|
|
84
|
+
newPassword: import_zod.z.string().min(6, { message: "New password must contain at least 6 characters" }).max(72, { message: "Password must be 72 characters or less" })
|
|
83
85
|
});
|
|
84
86
|
var twoFaVerifySchema = import_zod.z.object({
|
|
85
87
|
code: import_zod.z.string().min(6, { message: "Verification code is required" }),
|
package/dist/validators.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factiii/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -53,10 +53,16 @@
|
|
|
53
53
|
"build": "tsup",
|
|
54
54
|
"dev": "tsup --watch",
|
|
55
55
|
"check-types": "tsc --noEmit",
|
|
56
|
-
"lint": "eslint src
|
|
57
|
-
"lint:fix": "eslint src --
|
|
56
|
+
"lint": "eslint src",
|
|
57
|
+
"lint:fix": "eslint src --fix",
|
|
58
|
+
"format": "prettier --check src",
|
|
59
|
+
"format:fix": "prettier --write src",
|
|
58
60
|
"clean": "rm -rf dist node_modules/.cache .turbo",
|
|
59
|
-
"prepublishOnly": "npm run build"
|
|
61
|
+
"prepublishOnly": "npm run build",
|
|
62
|
+
"e2e": "pnpm e2e:db:up && playwright test --reporter=list; pnpm e2e:db:down",
|
|
63
|
+
"e2e:ui": "pnpm e2e:db:up && playwright test --ui",
|
|
64
|
+
"e2e:db:up": "docker compose -f e2e/docker-compose.yml up -d --wait && prisma generate --schema=e2e/server/schema.prisma --config=e2e/server/prisma.config.ts && prisma migrate reset --schema=e2e/server/schema.prisma --config=e2e/server/prisma.config.ts --force",
|
|
65
|
+
"e2e:db:down": "docker compose -f e2e/docker-compose.yml down"
|
|
60
66
|
},
|
|
61
67
|
"dependencies": {
|
|
62
68
|
"@trpc/server": "11.8.0",
|
|
@@ -83,22 +89,39 @@
|
|
|
83
89
|
}
|
|
84
90
|
},
|
|
85
91
|
"devDependencies": {
|
|
92
|
+
"@playwright/test": "^1.58.1",
|
|
93
|
+
"@prisma/adapter-better-sqlite3": "^7.3.0",
|
|
94
|
+
"@prisma/adapter-pg": "^7.3.0",
|
|
86
95
|
"@prisma/client": "^7.1.0",
|
|
96
|
+
"@trpc/client": "^11.8.0",
|
|
87
97
|
"@trpc/server": "11.8.0",
|
|
88
98
|
"@types/bcryptjs": "^2.4.6",
|
|
99
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
89
100
|
"@types/jsonwebtoken": "^9.0.9",
|
|
90
101
|
"@types/node": "^20.19.0",
|
|
102
|
+
"@types/pg": "^8.16.0",
|
|
103
|
+
"@types/react": "^19.2.11",
|
|
104
|
+
"@types/react-dom": "^19.2.3",
|
|
91
105
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
92
106
|
"@typescript-eslint/parser": "^7.18.0",
|
|
107
|
+
"@vitejs/plugin-react": "^5.1.3",
|
|
108
|
+
"better-sqlite3": "^12.6.2",
|
|
93
109
|
"eslint": "^8.57.0",
|
|
110
|
+
"pg": "^8.18.0",
|
|
111
|
+
"prettier": "^3.8.1",
|
|
94
112
|
"prisma": "^7.1.0",
|
|
113
|
+
"react": "^19.2.4",
|
|
114
|
+
"react-dom": "^19.2.4",
|
|
115
|
+
"react-router-dom": "^7.2.0",
|
|
95
116
|
"superjson": "^1.13.3",
|
|
96
117
|
"tsup": "^8.3.5",
|
|
118
|
+
"tsx": "^4.21.0",
|
|
97
119
|
"typescript": "5.9.3",
|
|
120
|
+
"vite": "^7.3.1",
|
|
98
121
|
"zod": "3.24.2"
|
|
99
122
|
},
|
|
100
123
|
"engines": {
|
|
101
124
|
"node": ">=18.0.0"
|
|
102
125
|
},
|
|
103
|
-
"packageManager": "pnpm@10.26.0
|
|
126
|
+
"packageManager": "pnpm@10.26.0"
|
|
104
127
|
}
|