@factiii/auth 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +74 -102
- package/dist/index.mjs +69 -99
- 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";
|
|
@@ -326,6 +323,7 @@ function createAuthGuard(config, t) {
|
|
|
326
323
|
clearAuthCookies(ctx.res, cookieSettings, storageKeys);
|
|
327
324
|
if (config.hooks?.logError) {
|
|
328
325
|
try {
|
|
326
|
+
const cookieHeader = ctx.headers.cookie;
|
|
329
327
|
const contextInfo = {
|
|
330
328
|
reason: description,
|
|
331
329
|
sessionId,
|
|
@@ -333,6 +331,11 @@ function createAuthGuard(config, t) {
|
|
|
333
331
|
ip: ctx.ip,
|
|
334
332
|
userAgent: ctx.headers["user-agent"],
|
|
335
333
|
...path ? { path } : {},
|
|
334
|
+
// Diagnostic: was Cookie header present at all, and which keys were sent?
|
|
335
|
+
hasCookieHeader: Boolean(cookieHeader),
|
|
336
|
+
cookieKeys: cookieHeader ? cookieHeader.split(";").map((c) => c.trim().split("=")[0]).filter(Boolean) : [],
|
|
337
|
+
origin: ctx.headers.origin ?? null,
|
|
338
|
+
referer: ctx.headers.referer ?? null,
|
|
336
339
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
337
340
|
};
|
|
338
341
|
const combinedStack = [
|
|
@@ -363,11 +366,7 @@ ${errorStack}` : null,
|
|
|
363
366
|
select: { id: true, userId: true, socketId: true }
|
|
364
367
|
});
|
|
365
368
|
if (session) {
|
|
366
|
-
await config.hooks.onSessionRevoked(
|
|
367
|
-
session.userId,
|
|
368
|
-
session.socketId,
|
|
369
|
-
description
|
|
370
|
-
);
|
|
369
|
+
await config.hooks.onSessionRevoked(session.userId, session.socketId, description);
|
|
371
370
|
}
|
|
372
371
|
}
|
|
373
372
|
} catch {
|
|
@@ -436,13 +435,7 @@ ${errorStack}` : null,
|
|
|
436
435
|
});
|
|
437
436
|
}
|
|
438
437
|
if (session.user.status === "BANNED") {
|
|
439
|
-
await revokeSession(
|
|
440
|
-
ctx,
|
|
441
|
-
session.id,
|
|
442
|
-
"Session revoked: User banned",
|
|
443
|
-
void 0,
|
|
444
|
-
path
|
|
445
|
-
);
|
|
438
|
+
await revokeSession(ctx, session.id, "Session revoked: User banned", void 0, path);
|
|
446
439
|
throw new import_server.TRPCError({
|
|
447
440
|
message: "Unauthorized",
|
|
448
441
|
code: "UNAUTHORIZED"
|
|
@@ -450,9 +443,7 @@ ${errorStack}` : null,
|
|
|
450
443
|
}
|
|
451
444
|
if (config.features?.biometric && config.hooks?.getBiometricTimeout) {
|
|
452
445
|
const timeoutMs = await config.hooks.getBiometricTimeout();
|
|
453
|
-
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(
|
|
454
|
-
path
|
|
455
|
-
)) {
|
|
446
|
+
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(path)) {
|
|
456
447
|
if (!session.user.verifiedHumanAt) {
|
|
457
448
|
throw new import_server.TRPCError({
|
|
458
449
|
message: "Biometric verification not completed. Please verify again.",
|
|
@@ -460,9 +451,7 @@ ${errorStack}` : null,
|
|
|
460
451
|
});
|
|
461
452
|
}
|
|
462
453
|
const now = /* @__PURE__ */ new Date();
|
|
463
|
-
const verificationExpiry = new Date(
|
|
464
|
-
session.user.verifiedHumanAt.getTime() + timeoutMs
|
|
465
|
-
);
|
|
454
|
+
const verificationExpiry = new Date(session.user.verifiedHumanAt.getTime() + timeoutMs);
|
|
466
455
|
if (now > verificationExpiry) {
|
|
467
456
|
throw new import_server.TRPCError({
|
|
468
457
|
message: "Biometric verification expired. Please verify again.",
|
|
@@ -534,13 +523,7 @@ ${errorStack}` : null,
|
|
|
534
523
|
});
|
|
535
524
|
}
|
|
536
525
|
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
|
-
);
|
|
526
|
+
await revokeSession(ctx, null, "Session revoked: Unauthorized", errorStack, path);
|
|
544
527
|
throw new import_server.TRPCError({
|
|
545
528
|
message: "Unauthorized",
|
|
546
529
|
code: "UNAUTHORIZED"
|
|
@@ -552,13 +535,7 @@ ${errorStack}` : null,
|
|
|
552
535
|
if (!meta?.authRequired) {
|
|
553
536
|
return next({ ctx: { ...ctx, userId: 0 } });
|
|
554
537
|
}
|
|
555
|
-
await revokeSession(
|
|
556
|
-
ctx,
|
|
557
|
-
null,
|
|
558
|
-
"Session revoked: No token sent",
|
|
559
|
-
void 0,
|
|
560
|
-
path
|
|
561
|
-
);
|
|
538
|
+
await revokeSession(ctx, null, "Session revoked: No token sent", void 0, path);
|
|
562
539
|
throw new import_server.TRPCError({ message: "Unauthorized", code: "UNAUTHORIZED" });
|
|
563
540
|
}
|
|
564
541
|
});
|
|
@@ -579,25 +556,19 @@ function detectBrowser(userAgent) {
|
|
|
579
556
|
return "iOS Browser (Chrome)";
|
|
580
557
|
if (/iphone|ipad|ipod/i.test(userAgent) && /fxios/i.test(userAgent))
|
|
581
558
|
return "iOS Browser (Firefox)";
|
|
582
|
-
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
583
|
-
return "iOS Browser (Edge)";
|
|
559
|
+
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent)) return "iOS Browser (Edge)";
|
|
584
560
|
if (/android/i.test(userAgent) && !/chrome|firefox|samsungbrowser|opr\/|edg\//i.test(userAgent)) {
|
|
585
561
|
return "Android App";
|
|
586
562
|
}
|
|
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)";
|
|
563
|
+
if (/android/i.test(userAgent) && /chrome/i.test(userAgent)) return "Android Browser (Chrome)";
|
|
564
|
+
if (/android/i.test(userAgent) && /firefox/i.test(userAgent)) return "Android Browser (Firefox)";
|
|
591
565
|
if (/android/i.test(userAgent) && /samsungbrowser/i.test(userAgent))
|
|
592
566
|
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)";
|
|
567
|
+
if (/android/i.test(userAgent) && /opr\//i.test(userAgent)) return "Android Browser (Opera)";
|
|
568
|
+
if (/android/i.test(userAgent) && /edg\//i.test(userAgent)) return "Android Browser (Edge)";
|
|
597
569
|
if (/chrome|chromium/i.test(userAgent)) return "Chrome";
|
|
598
570
|
if (/firefox/i.test(userAgent)) return "Firefox";
|
|
599
|
-
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent))
|
|
600
|
-
return "Safari";
|
|
571
|
+
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent)) return "Safari";
|
|
601
572
|
if (/opr\//i.test(userAgent)) return "Opera";
|
|
602
573
|
if (/edg\//i.test(userAgent)) return "Edge";
|
|
603
574
|
return "Unknown";
|
|
@@ -631,16 +602,10 @@ function createOAuthVerifier(keys) {
|
|
|
631
602
|
return async function verifyOAuthToken(provider, token, extra) {
|
|
632
603
|
if (provider === "GOOGLE") {
|
|
633
604
|
if (!keys.google?.clientId) {
|
|
634
|
-
throw new OAuthVerificationError(
|
|
635
|
-
"Google OAuth configuration missing",
|
|
636
|
-
500
|
|
637
|
-
);
|
|
605
|
+
throw new OAuthVerificationError("Google OAuth configuration missing", 500);
|
|
638
606
|
}
|
|
639
607
|
if (!googleClient) {
|
|
640
|
-
throw new OAuthVerificationError(
|
|
641
|
-
"Google OAuth client not initialized",
|
|
642
|
-
500
|
|
643
|
-
);
|
|
608
|
+
throw new OAuthVerificationError("Google OAuth client not initialized", 500);
|
|
644
609
|
}
|
|
645
610
|
const audience = [keys.google.clientId];
|
|
646
611
|
if (keys.google.iosClientId) {
|
|
@@ -661,10 +626,7 @@ function createOAuthVerifier(keys) {
|
|
|
661
626
|
}
|
|
662
627
|
if (provider === "APPLE") {
|
|
663
628
|
if (!keys.apple?.clientId) {
|
|
664
|
-
throw new OAuthVerificationError(
|
|
665
|
-
"Apple OAuth configuration missing",
|
|
666
|
-
500
|
|
667
|
-
);
|
|
629
|
+
throw new OAuthVerificationError("Apple OAuth configuration missing", 500);
|
|
668
630
|
}
|
|
669
631
|
const audience = [keys.apple.clientId];
|
|
670
632
|
if (keys.apple.iosClientId) {
|
|
@@ -744,8 +706,10 @@ var signupSchema = import_zod.z.object({
|
|
|
744
706
|
username: import_zod.z.string().min(1, { message: "Username is required" }).max(30, { message: "Username must be 30 characters or less" }).regex(usernameValidationRegex, {
|
|
745
707
|
message: "Username can only contain letters, numbers, and underscores"
|
|
746
708
|
}),
|
|
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" })
|
|
709
|
+
email: import_zod.z.string().max(254, { message: "Email must be 254 characters or less" }).email({ message: "Invalid email address" }),
|
|
710
|
+
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, {
|
|
711
|
+
message: "Password cannot be only whitespace"
|
|
712
|
+
})
|
|
749
713
|
});
|
|
750
714
|
var loginSchema = import_zod.z.object({
|
|
751
715
|
username: import_zod.z.string().min(1, { message: "Username or email is required" }),
|
|
@@ -765,14 +729,14 @@ var requestPasswordResetSchema = import_zod.z.object({
|
|
|
765
729
|
});
|
|
766
730
|
var resetPasswordSchema = import_zod.z.object({
|
|
767
731
|
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" })
|
|
732
|
+
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
733
|
});
|
|
770
734
|
var checkPasswordResetSchema = import_zod.z.object({
|
|
771
735
|
token: import_zod.z.string().min(1, { message: "Reset token is required" })
|
|
772
736
|
});
|
|
773
737
|
var changePasswordSchema = import_zod.z.object({
|
|
774
738
|
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" })
|
|
739
|
+
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
740
|
});
|
|
777
741
|
var twoFaVerifySchema = import_zod.z.object({
|
|
778
742
|
code: import_zod.z.string().min(6, { message: "Verification code is required" }),
|
|
@@ -1162,7 +1126,11 @@ var BaseProcedureFactory = class {
|
|
|
1162
1126
|
});
|
|
1163
1127
|
for (const session of sessionsToRevoke) {
|
|
1164
1128
|
if (this.config.hooks?.onSessionRevoked) {
|
|
1165
|
-
await this.config.hooks.onSessionRevoked(
|
|
1129
|
+
await this.config.hooks.onSessionRevoked(
|
|
1130
|
+
session.id,
|
|
1131
|
+
session.socketId,
|
|
1132
|
+
"End all sessions"
|
|
1133
|
+
);
|
|
1166
1134
|
}
|
|
1167
1135
|
}
|
|
1168
1136
|
if (!skipCurrentSession) {
|
|
@@ -1491,14 +1459,14 @@ var OAuthLoginProcedureFactory = class {
|
|
|
1491
1459
|
}
|
|
1492
1460
|
const { email, oauthId } = await this.verifyOAuthToken(provider, idToken, appleUser);
|
|
1493
1461
|
if (!email) {
|
|
1494
|
-
throw new import_server5.TRPCError({
|
|
1462
|
+
throw new import_server5.TRPCError({
|
|
1463
|
+
code: "BAD_REQUEST",
|
|
1464
|
+
message: "Email not provided by OAuth provider"
|
|
1465
|
+
});
|
|
1495
1466
|
}
|
|
1496
1467
|
let user = await this.config.prisma.user.findFirst({
|
|
1497
1468
|
where: {
|
|
1498
|
-
OR: [
|
|
1499
|
-
{ email: { equals: email, mode: "insensitive" } },
|
|
1500
|
-
{ oauthId: { equals: oauthId } }
|
|
1501
|
-
]
|
|
1469
|
+
OR: [{ email: { equals: email, mode: "insensitive" } }, { oauthId: { equals: oauthId } }]
|
|
1502
1470
|
},
|
|
1503
1471
|
select: {
|
|
1504
1472
|
id: true,
|
|
@@ -1648,15 +1616,17 @@ var TwoFaProcedureFactory = class {
|
|
|
1648
1616
|
if (user.twoFaEnabled) {
|
|
1649
1617
|
throw new import_server6.TRPCError({ code: "BAD_REQUEST", message: "2FA already enabled." });
|
|
1650
1618
|
}
|
|
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."
|
|
1619
|
+
if (this.config.features.twoFaRequiresDevice !== false) {
|
|
1620
|
+
const checkSession = await this.config.prisma.session.findFirst({
|
|
1621
|
+
where: { userId, id: sessionId },
|
|
1622
|
+
select: { deviceId: true }
|
|
1659
1623
|
});
|
|
1624
|
+
if (!checkSession?.deviceId) {
|
|
1625
|
+
throw new import_server6.TRPCError({
|
|
1626
|
+
code: "BAD_REQUEST",
|
|
1627
|
+
message: "You must be logged in on mobile to enable 2FA."
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1660
1630
|
}
|
|
1661
1631
|
await this.config.prisma.session.updateMany({
|
|
1662
1632
|
where: { userId, revokedAt: null, NOT: { id: sessionId } },
|
|
@@ -1880,15 +1850,29 @@ var TwoFaProcedureFactory = class {
|
|
|
1880
1850
|
const { pushToken } = input;
|
|
1881
1851
|
const device = await this.config.prisma.device.findFirst({
|
|
1882
1852
|
where: {
|
|
1883
|
-
|
|
1853
|
+
users: { some: { id: userId } },
|
|
1884
1854
|
pushToken
|
|
1885
1855
|
},
|
|
1886
1856
|
select: { id: true }
|
|
1887
1857
|
});
|
|
1888
1858
|
if (device) {
|
|
1889
|
-
await this.config.prisma.
|
|
1890
|
-
where: {
|
|
1859
|
+
await this.config.prisma.session.updateMany({
|
|
1860
|
+
where: { userId, deviceId: device.id },
|
|
1861
|
+
data: { deviceId: null }
|
|
1862
|
+
});
|
|
1863
|
+
await this.config.prisma.device.update({
|
|
1864
|
+
where: { id: device.id },
|
|
1865
|
+
data: { users: { disconnect: { id: userId } } }
|
|
1891
1866
|
});
|
|
1867
|
+
const remainingUsers = await this.config.prisma.device.findUnique({
|
|
1868
|
+
where: { id: device.id },
|
|
1869
|
+
select: { users: { select: { id: true }, take: 1 } }
|
|
1870
|
+
});
|
|
1871
|
+
if (!remainingUsers?.users.length) {
|
|
1872
|
+
await this.config.prisma.device.delete({
|
|
1873
|
+
where: { id: device.id }
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1892
1876
|
}
|
|
1893
1877
|
return { deregistered: true };
|
|
1894
1878
|
});
|
|
@@ -1975,10 +1959,7 @@ function getClientIp(req) {
|
|
|
1975
1959
|
}
|
|
1976
1960
|
|
|
1977
1961
|
// src/router.ts
|
|
1978
|
-
var createContext = ({
|
|
1979
|
-
req,
|
|
1980
|
-
res
|
|
1981
|
-
}) => ({
|
|
1962
|
+
var createContext = ({ req, res }) => ({
|
|
1982
1963
|
headers: req.headers,
|
|
1983
1964
|
userId: null,
|
|
1984
1965
|
sessionId: null,
|
|
@@ -1991,9 +1972,7 @@ var AuthRouterFactory = class {
|
|
|
1991
1972
|
constructor(userConfig) {
|
|
1992
1973
|
this.userConfig = userConfig;
|
|
1993
1974
|
this.config = createAuthConfig(this.userConfig);
|
|
1994
|
-
this.schemas = createSchemas(
|
|
1995
|
-
this.config.schemaExtensions
|
|
1996
|
-
);
|
|
1975
|
+
this.schemas = createSchemas(this.config.schemaExtensions);
|
|
1997
1976
|
this.t = createTrpcBuilder(this.config);
|
|
1998
1977
|
this.authGuard = createAuthGuard(this.config, this.t);
|
|
1999
1978
|
this.procedure = createBaseProcedure(this.t, this.authGuard);
|
|
@@ -2005,10 +1984,7 @@ var AuthRouterFactory = class {
|
|
|
2005
1984
|
this.procedure,
|
|
2006
1985
|
this.authProcedure
|
|
2007
1986
|
);
|
|
2008
|
-
const biometricRoutes = new BiometricProcedureFactory(
|
|
2009
|
-
this.config,
|
|
2010
|
-
this.authProcedure
|
|
2011
|
-
);
|
|
1987
|
+
const biometricRoutes = new BiometricProcedureFactory(this.config, this.authProcedure);
|
|
2012
1988
|
const emailVerificationRoutes = new EmailVerificationProcedureFactory(
|
|
2013
1989
|
this.config,
|
|
2014
1990
|
this.authProcedure
|
|
@@ -2017,11 +1993,7 @@ var AuthRouterFactory = class {
|
|
|
2017
1993
|
this.config,
|
|
2018
1994
|
this.procedure
|
|
2019
1995
|
);
|
|
2020
|
-
const twoFaRoutes = new TwoFaProcedureFactory(
|
|
2021
|
-
this.config,
|
|
2022
|
-
this.procedure,
|
|
2023
|
-
this.authProcedure
|
|
2024
|
-
);
|
|
1996
|
+
const twoFaRoutes = new TwoFaProcedureFactory(this.config, this.procedure, this.authProcedure);
|
|
2025
1997
|
return this.t.router({
|
|
2026
1998
|
...baseRoutes.createBaseProcedures(this.schemas),
|
|
2027
1999
|
...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";
|
|
@@ -270,6 +267,7 @@ function createAuthGuard(config, t) {
|
|
|
270
267
|
clearAuthCookies(ctx.res, cookieSettings, storageKeys);
|
|
271
268
|
if (config.hooks?.logError) {
|
|
272
269
|
try {
|
|
270
|
+
const cookieHeader = ctx.headers.cookie;
|
|
273
271
|
const contextInfo = {
|
|
274
272
|
reason: description,
|
|
275
273
|
sessionId,
|
|
@@ -277,6 +275,11 @@ function createAuthGuard(config, t) {
|
|
|
277
275
|
ip: ctx.ip,
|
|
278
276
|
userAgent: ctx.headers["user-agent"],
|
|
279
277
|
...path ? { path } : {},
|
|
278
|
+
// Diagnostic: was Cookie header present at all, and which keys were sent?
|
|
279
|
+
hasCookieHeader: Boolean(cookieHeader),
|
|
280
|
+
cookieKeys: cookieHeader ? cookieHeader.split(";").map((c) => c.trim().split("=")[0]).filter(Boolean) : [],
|
|
281
|
+
origin: ctx.headers.origin ?? null,
|
|
282
|
+
referer: ctx.headers.referer ?? null,
|
|
280
283
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
281
284
|
};
|
|
282
285
|
const combinedStack = [
|
|
@@ -307,11 +310,7 @@ ${errorStack}` : null,
|
|
|
307
310
|
select: { id: true, userId: true, socketId: true }
|
|
308
311
|
});
|
|
309
312
|
if (session) {
|
|
310
|
-
await config.hooks.onSessionRevoked(
|
|
311
|
-
session.userId,
|
|
312
|
-
session.socketId,
|
|
313
|
-
description
|
|
314
|
-
);
|
|
313
|
+
await config.hooks.onSessionRevoked(session.userId, session.socketId, description);
|
|
315
314
|
}
|
|
316
315
|
}
|
|
317
316
|
} catch {
|
|
@@ -380,13 +379,7 @@ ${errorStack}` : null,
|
|
|
380
379
|
});
|
|
381
380
|
}
|
|
382
381
|
if (session.user.status === "BANNED") {
|
|
383
|
-
await revokeSession(
|
|
384
|
-
ctx,
|
|
385
|
-
session.id,
|
|
386
|
-
"Session revoked: User banned",
|
|
387
|
-
void 0,
|
|
388
|
-
path
|
|
389
|
-
);
|
|
382
|
+
await revokeSession(ctx, session.id, "Session revoked: User banned", void 0, path);
|
|
390
383
|
throw new TRPCError({
|
|
391
384
|
message: "Unauthorized",
|
|
392
385
|
code: "UNAUTHORIZED"
|
|
@@ -394,9 +387,7 @@ ${errorStack}` : null,
|
|
|
394
387
|
}
|
|
395
388
|
if (config.features?.biometric && config.hooks?.getBiometricTimeout) {
|
|
396
389
|
const timeoutMs = await config.hooks.getBiometricTimeout();
|
|
397
|
-
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(
|
|
398
|
-
path
|
|
399
|
-
)) {
|
|
390
|
+
if (timeoutMs !== null && !["auth.refresh", "auth.verifyBiometric", "auth.logout"].includes(path)) {
|
|
400
391
|
if (!session.user.verifiedHumanAt) {
|
|
401
392
|
throw new TRPCError({
|
|
402
393
|
message: "Biometric verification not completed. Please verify again.",
|
|
@@ -404,9 +395,7 @@ ${errorStack}` : null,
|
|
|
404
395
|
});
|
|
405
396
|
}
|
|
406
397
|
const now = /* @__PURE__ */ new Date();
|
|
407
|
-
const verificationExpiry = new Date(
|
|
408
|
-
session.user.verifiedHumanAt.getTime() + timeoutMs
|
|
409
|
-
);
|
|
398
|
+
const verificationExpiry = new Date(session.user.verifiedHumanAt.getTime() + timeoutMs);
|
|
410
399
|
if (now > verificationExpiry) {
|
|
411
400
|
throw new TRPCError({
|
|
412
401
|
message: "Biometric verification expired. Please verify again.",
|
|
@@ -478,13 +467,7 @@ ${errorStack}` : null,
|
|
|
478
467
|
});
|
|
479
468
|
}
|
|
480
469
|
if (err instanceof TRPCError && err.code === "UNAUTHORIZED") {
|
|
481
|
-
await revokeSession(
|
|
482
|
-
ctx,
|
|
483
|
-
null,
|
|
484
|
-
"Session revoked: Unauthorized",
|
|
485
|
-
errorStack,
|
|
486
|
-
path
|
|
487
|
-
);
|
|
470
|
+
await revokeSession(ctx, null, "Session revoked: Unauthorized", errorStack, path);
|
|
488
471
|
throw new TRPCError({
|
|
489
472
|
message: "Unauthorized",
|
|
490
473
|
code: "UNAUTHORIZED"
|
|
@@ -496,13 +479,7 @@ ${errorStack}` : null,
|
|
|
496
479
|
if (!meta?.authRequired) {
|
|
497
480
|
return next({ ctx: { ...ctx, userId: 0 } });
|
|
498
481
|
}
|
|
499
|
-
await revokeSession(
|
|
500
|
-
ctx,
|
|
501
|
-
null,
|
|
502
|
-
"Session revoked: No token sent",
|
|
503
|
-
void 0,
|
|
504
|
-
path
|
|
505
|
-
);
|
|
482
|
+
await revokeSession(ctx, null, "Session revoked: No token sent", void 0, path);
|
|
506
483
|
throw new TRPCError({ message: "Unauthorized", code: "UNAUTHORIZED" });
|
|
507
484
|
}
|
|
508
485
|
});
|
|
@@ -523,25 +500,19 @@ function detectBrowser(userAgent) {
|
|
|
523
500
|
return "iOS Browser (Chrome)";
|
|
524
501
|
if (/iphone|ipad|ipod/i.test(userAgent) && /fxios/i.test(userAgent))
|
|
525
502
|
return "iOS Browser (Firefox)";
|
|
526
|
-
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent))
|
|
527
|
-
return "iOS Browser (Edge)";
|
|
503
|
+
if (/iphone|ipad|ipod/i.test(userAgent) && /edg\//i.test(userAgent)) return "iOS Browser (Edge)";
|
|
528
504
|
if (/android/i.test(userAgent) && !/chrome|firefox|samsungbrowser|opr\/|edg\//i.test(userAgent)) {
|
|
529
505
|
return "Android App";
|
|
530
506
|
}
|
|
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)";
|
|
507
|
+
if (/android/i.test(userAgent) && /chrome/i.test(userAgent)) return "Android Browser (Chrome)";
|
|
508
|
+
if (/android/i.test(userAgent) && /firefox/i.test(userAgent)) return "Android Browser (Firefox)";
|
|
535
509
|
if (/android/i.test(userAgent) && /samsungbrowser/i.test(userAgent))
|
|
536
510
|
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)";
|
|
511
|
+
if (/android/i.test(userAgent) && /opr\//i.test(userAgent)) return "Android Browser (Opera)";
|
|
512
|
+
if (/android/i.test(userAgent) && /edg\//i.test(userAgent)) return "Android Browser (Edge)";
|
|
541
513
|
if (/chrome|chromium/i.test(userAgent)) return "Chrome";
|
|
542
514
|
if (/firefox/i.test(userAgent)) return "Firefox";
|
|
543
|
-
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent))
|
|
544
|
-
return "Safari";
|
|
515
|
+
if (/safari/i.test(userAgent) && !/chrome|chromium|crios/i.test(userAgent)) return "Safari";
|
|
545
516
|
if (/opr\//i.test(userAgent)) return "Opera";
|
|
546
517
|
if (/edg\//i.test(userAgent)) return "Edge";
|
|
547
518
|
return "Unknown";
|
|
@@ -575,16 +546,10 @@ function createOAuthVerifier(keys) {
|
|
|
575
546
|
return async function verifyOAuthToken(provider, token, extra) {
|
|
576
547
|
if (provider === "GOOGLE") {
|
|
577
548
|
if (!keys.google?.clientId) {
|
|
578
|
-
throw new OAuthVerificationError(
|
|
579
|
-
"Google OAuth configuration missing",
|
|
580
|
-
500
|
|
581
|
-
);
|
|
549
|
+
throw new OAuthVerificationError("Google OAuth configuration missing", 500);
|
|
582
550
|
}
|
|
583
551
|
if (!googleClient) {
|
|
584
|
-
throw new OAuthVerificationError(
|
|
585
|
-
"Google OAuth client not initialized",
|
|
586
|
-
500
|
|
587
|
-
);
|
|
552
|
+
throw new OAuthVerificationError("Google OAuth client not initialized", 500);
|
|
588
553
|
}
|
|
589
554
|
const audience = [keys.google.clientId];
|
|
590
555
|
if (keys.google.iosClientId) {
|
|
@@ -605,10 +570,7 @@ function createOAuthVerifier(keys) {
|
|
|
605
570
|
}
|
|
606
571
|
if (provider === "APPLE") {
|
|
607
572
|
if (!keys.apple?.clientId) {
|
|
608
|
-
throw new OAuthVerificationError(
|
|
609
|
-
"Apple OAuth configuration missing",
|
|
610
|
-
500
|
|
611
|
-
);
|
|
573
|
+
throw new OAuthVerificationError("Apple OAuth configuration missing", 500);
|
|
612
574
|
}
|
|
613
575
|
const audience = [keys.apple.clientId];
|
|
614
576
|
if (keys.apple.iosClientId) {
|
|
@@ -1014,7 +976,11 @@ var BaseProcedureFactory = class {
|
|
|
1014
976
|
});
|
|
1015
977
|
for (const session of sessionsToRevoke) {
|
|
1016
978
|
if (this.config.hooks?.onSessionRevoked) {
|
|
1017
|
-
await this.config.hooks.onSessionRevoked(
|
|
979
|
+
await this.config.hooks.onSessionRevoked(
|
|
980
|
+
session.id,
|
|
981
|
+
session.socketId,
|
|
982
|
+
"End all sessions"
|
|
983
|
+
);
|
|
1018
984
|
}
|
|
1019
985
|
}
|
|
1020
986
|
if (!skipCurrentSession) {
|
|
@@ -1343,14 +1309,14 @@ var OAuthLoginProcedureFactory = class {
|
|
|
1343
1309
|
}
|
|
1344
1310
|
const { email, oauthId } = await this.verifyOAuthToken(provider, idToken, appleUser);
|
|
1345
1311
|
if (!email) {
|
|
1346
|
-
throw new TRPCError5({
|
|
1312
|
+
throw new TRPCError5({
|
|
1313
|
+
code: "BAD_REQUEST",
|
|
1314
|
+
message: "Email not provided by OAuth provider"
|
|
1315
|
+
});
|
|
1347
1316
|
}
|
|
1348
1317
|
let user = await this.config.prisma.user.findFirst({
|
|
1349
1318
|
where: {
|
|
1350
|
-
OR: [
|
|
1351
|
-
{ email: { equals: email, mode: "insensitive" } },
|
|
1352
|
-
{ oauthId: { equals: oauthId } }
|
|
1353
|
-
]
|
|
1319
|
+
OR: [{ email: { equals: email, mode: "insensitive" } }, { oauthId: { equals: oauthId } }]
|
|
1354
1320
|
},
|
|
1355
1321
|
select: {
|
|
1356
1322
|
id: true,
|
|
@@ -1500,15 +1466,17 @@ var TwoFaProcedureFactory = class {
|
|
|
1500
1466
|
if (user.twoFaEnabled) {
|
|
1501
1467
|
throw new TRPCError6({ code: "BAD_REQUEST", message: "2FA already enabled." });
|
|
1502
1468
|
}
|
|
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."
|
|
1469
|
+
if (this.config.features.twoFaRequiresDevice !== false) {
|
|
1470
|
+
const checkSession = await this.config.prisma.session.findFirst({
|
|
1471
|
+
where: { userId, id: sessionId },
|
|
1472
|
+
select: { deviceId: true }
|
|
1511
1473
|
});
|
|
1474
|
+
if (!checkSession?.deviceId) {
|
|
1475
|
+
throw new TRPCError6({
|
|
1476
|
+
code: "BAD_REQUEST",
|
|
1477
|
+
message: "You must be logged in on mobile to enable 2FA."
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1512
1480
|
}
|
|
1513
1481
|
await this.config.prisma.session.updateMany({
|
|
1514
1482
|
where: { userId, revokedAt: null, NOT: { id: sessionId } },
|
|
@@ -1732,15 +1700,29 @@ var TwoFaProcedureFactory = class {
|
|
|
1732
1700
|
const { pushToken } = input;
|
|
1733
1701
|
const device = await this.config.prisma.device.findFirst({
|
|
1734
1702
|
where: {
|
|
1735
|
-
|
|
1703
|
+
users: { some: { id: userId } },
|
|
1736
1704
|
pushToken
|
|
1737
1705
|
},
|
|
1738
1706
|
select: { id: true }
|
|
1739
1707
|
});
|
|
1740
1708
|
if (device) {
|
|
1741
|
-
await this.config.prisma.
|
|
1742
|
-
where: {
|
|
1709
|
+
await this.config.prisma.session.updateMany({
|
|
1710
|
+
where: { userId, deviceId: device.id },
|
|
1711
|
+
data: { deviceId: null }
|
|
1712
|
+
});
|
|
1713
|
+
await this.config.prisma.device.update({
|
|
1714
|
+
where: { id: device.id },
|
|
1715
|
+
data: { users: { disconnect: { id: userId } } }
|
|
1743
1716
|
});
|
|
1717
|
+
const remainingUsers = await this.config.prisma.device.findUnique({
|
|
1718
|
+
where: { id: device.id },
|
|
1719
|
+
select: { users: { select: { id: true }, take: 1 } }
|
|
1720
|
+
});
|
|
1721
|
+
if (!remainingUsers?.users.length) {
|
|
1722
|
+
await this.config.prisma.device.delete({
|
|
1723
|
+
where: { id: device.id }
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1744
1726
|
}
|
|
1745
1727
|
return { deregistered: true };
|
|
1746
1728
|
});
|
|
@@ -1827,10 +1809,7 @@ function getClientIp(req) {
|
|
|
1827
1809
|
}
|
|
1828
1810
|
|
|
1829
1811
|
// src/router.ts
|
|
1830
|
-
var createContext = ({
|
|
1831
|
-
req,
|
|
1832
|
-
res
|
|
1833
|
-
}) => ({
|
|
1812
|
+
var createContext = ({ req, res }) => ({
|
|
1834
1813
|
headers: req.headers,
|
|
1835
1814
|
userId: null,
|
|
1836
1815
|
sessionId: null,
|
|
@@ -1843,9 +1822,7 @@ var AuthRouterFactory = class {
|
|
|
1843
1822
|
constructor(userConfig) {
|
|
1844
1823
|
this.userConfig = userConfig;
|
|
1845
1824
|
this.config = createAuthConfig(this.userConfig);
|
|
1846
|
-
this.schemas = createSchemas(
|
|
1847
|
-
this.config.schemaExtensions
|
|
1848
|
-
);
|
|
1825
|
+
this.schemas = createSchemas(this.config.schemaExtensions);
|
|
1849
1826
|
this.t = createTrpcBuilder(this.config);
|
|
1850
1827
|
this.authGuard = createAuthGuard(this.config, this.t);
|
|
1851
1828
|
this.procedure = createBaseProcedure(this.t, this.authGuard);
|
|
@@ -1857,10 +1834,7 @@ var AuthRouterFactory = class {
|
|
|
1857
1834
|
this.procedure,
|
|
1858
1835
|
this.authProcedure
|
|
1859
1836
|
);
|
|
1860
|
-
const biometricRoutes = new BiometricProcedureFactory(
|
|
1861
|
-
this.config,
|
|
1862
|
-
this.authProcedure
|
|
1863
|
-
);
|
|
1837
|
+
const biometricRoutes = new BiometricProcedureFactory(this.config, this.authProcedure);
|
|
1864
1838
|
const emailVerificationRoutes = new EmailVerificationProcedureFactory(
|
|
1865
1839
|
this.config,
|
|
1866
1840
|
this.authProcedure
|
|
@@ -1869,11 +1843,7 @@ var AuthRouterFactory = class {
|
|
|
1869
1843
|
this.config,
|
|
1870
1844
|
this.procedure
|
|
1871
1845
|
);
|
|
1872
|
-
const twoFaRoutes = new TwoFaProcedureFactory(
|
|
1873
|
-
this.config,
|
|
1874
|
-
this.procedure,
|
|
1875
|
-
this.authProcedure
|
|
1876
|
-
);
|
|
1846
|
+
const twoFaRoutes = new TwoFaProcedureFactory(this.config, this.procedure, this.authProcedure);
|
|
1877
1847
|
return this.t.router({
|
|
1878
1848
|
...baseRoutes.createBaseProcedures(this.schemas),
|
|
1879
1849
|
...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.3.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
|
}
|