najm-auth 2.0.9 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +801 -778
- package/dist/client/edge.js +21 -12
- package/dist/client/server/index.js +21 -12
- package/dist/index.d.ts +24 -5
- package/dist/index.js +90 -40
- package/package.json +1 -1
package/dist/client/edge.js
CHANGED
|
@@ -185,7 +185,12 @@ async function requestSessionRecovery(options) {
|
|
|
185
185
|
} catch (error) {
|
|
186
186
|
reportFailure(options, {
|
|
187
187
|
reason: "fetch-error",
|
|
188
|
-
error: safeErrorDetails(error
|
|
188
|
+
error: safeErrorDetails(error, [
|
|
189
|
+
options.refreshCookieValue,
|
|
190
|
+
options.sessionSecret,
|
|
191
|
+
endpoint,
|
|
192
|
+
options.requestOrigin
|
|
193
|
+
])
|
|
189
194
|
});
|
|
190
195
|
return { status: "unavailable" };
|
|
191
196
|
}
|
|
@@ -289,33 +294,37 @@ function reportFailure(options, failure) {
|
|
|
289
294
|
}
|
|
290
295
|
}
|
|
291
296
|
__name(reportFailure, "reportFailure");
|
|
292
|
-
function safeErrorDetails(value) {
|
|
297
|
+
function safeErrorDetails(value, sensitiveValues) {
|
|
293
298
|
const error = isRecord2(value) ? value : {};
|
|
294
299
|
const details = {
|
|
295
|
-
name: safeText(error.name, "Error"),
|
|
296
|
-
message: safeText(error.message, "Session recovery fetch failed")
|
|
300
|
+
name: safeText(error.name, "Error", sensitiveValues),
|
|
301
|
+
message: safeText(error.message, "Session recovery fetch failed", sensitiveValues)
|
|
297
302
|
};
|
|
298
|
-
const code = safeOptionalText(error.code);
|
|
303
|
+
const code = safeOptionalText(error.code, sensitiveValues);
|
|
299
304
|
if (code) details.code = code;
|
|
300
305
|
if (isRecord2(error.cause)) {
|
|
301
|
-
const causeCode = safeOptionalText(error.cause.code);
|
|
306
|
+
const causeCode = safeOptionalText(error.cause.code, sensitiveValues);
|
|
302
307
|
details.cause = {
|
|
303
|
-
name: safeText(error.cause.name, "Error"),
|
|
304
|
-
message: safeText(error.cause.message, "Session recovery fetch failed"),
|
|
308
|
+
name: safeText(error.cause.name, "Error", sensitiveValues),
|
|
309
|
+
message: safeText(error.cause.message, "Session recovery fetch failed", sensitiveValues),
|
|
305
310
|
...causeCode ? { code: causeCode } : {}
|
|
306
311
|
};
|
|
307
312
|
}
|
|
308
313
|
return details;
|
|
309
314
|
}
|
|
310
315
|
__name(safeErrorDetails, "safeErrorDetails");
|
|
311
|
-
function safeText(value, fallback) {
|
|
316
|
+
function safeText(value, fallback, sensitiveValues) {
|
|
312
317
|
if (typeof value !== "string" || !value) return fallback;
|
|
313
|
-
|
|
318
|
+
let safe = value.replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\b(authorization|cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted]");
|
|
319
|
+
for (const sensitive of sensitiveValues) {
|
|
320
|
+
if (sensitive) safe = safe.split(sensitive).join("[redacted]");
|
|
321
|
+
}
|
|
322
|
+
return safe.slice(0, 300);
|
|
314
323
|
}
|
|
315
324
|
__name(safeText, "safeText");
|
|
316
|
-
function safeOptionalText(value) {
|
|
325
|
+
function safeOptionalText(value, sensitiveValues) {
|
|
317
326
|
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
318
|
-
return safeText(String(value), "");
|
|
327
|
+
return safeText(String(value), "", sensitiveValues);
|
|
319
328
|
}
|
|
320
329
|
__name(safeOptionalText, "safeOptionalText");
|
|
321
330
|
function isRecord2(value) {
|
|
@@ -197,7 +197,12 @@ async function requestSessionRecovery(options) {
|
|
|
197
197
|
} catch (error) {
|
|
198
198
|
reportFailure(options, {
|
|
199
199
|
reason: "fetch-error",
|
|
200
|
-
error: safeErrorDetails(error
|
|
200
|
+
error: safeErrorDetails(error, [
|
|
201
|
+
options.refreshCookieValue,
|
|
202
|
+
options.sessionSecret,
|
|
203
|
+
endpoint,
|
|
204
|
+
options.requestOrigin
|
|
205
|
+
])
|
|
201
206
|
});
|
|
202
207
|
return { status: "unavailable" };
|
|
203
208
|
}
|
|
@@ -292,31 +297,35 @@ function reportFailure(options, failure) {
|
|
|
292
297
|
} catch {
|
|
293
298
|
}
|
|
294
299
|
}
|
|
295
|
-
function safeErrorDetails(value) {
|
|
300
|
+
function safeErrorDetails(value, sensitiveValues) {
|
|
296
301
|
const error = isRecord2(value) ? value : {};
|
|
297
302
|
const details = {
|
|
298
|
-
name: safeText(error.name, "Error"),
|
|
299
|
-
message: safeText(error.message, "Session recovery fetch failed")
|
|
303
|
+
name: safeText(error.name, "Error", sensitiveValues),
|
|
304
|
+
message: safeText(error.message, "Session recovery fetch failed", sensitiveValues)
|
|
300
305
|
};
|
|
301
|
-
const code = safeOptionalText(error.code);
|
|
306
|
+
const code = safeOptionalText(error.code, sensitiveValues);
|
|
302
307
|
if (code) details.code = code;
|
|
303
308
|
if (isRecord2(error.cause)) {
|
|
304
|
-
const causeCode = safeOptionalText(error.cause.code);
|
|
309
|
+
const causeCode = safeOptionalText(error.cause.code, sensitiveValues);
|
|
305
310
|
details.cause = {
|
|
306
|
-
name: safeText(error.cause.name, "Error"),
|
|
307
|
-
message: safeText(error.cause.message, "Session recovery fetch failed"),
|
|
311
|
+
name: safeText(error.cause.name, "Error", sensitiveValues),
|
|
312
|
+
message: safeText(error.cause.message, "Session recovery fetch failed", sensitiveValues),
|
|
308
313
|
...causeCode ? { code: causeCode } : {}
|
|
309
314
|
};
|
|
310
315
|
}
|
|
311
316
|
return details;
|
|
312
317
|
}
|
|
313
|
-
function safeText(value, fallback) {
|
|
318
|
+
function safeText(value, fallback, sensitiveValues) {
|
|
314
319
|
if (typeof value !== "string" || !value) return fallback;
|
|
315
|
-
|
|
320
|
+
let safe = value.replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\b(authorization|cookie)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]").replace(/\bBearer\s+[^\s,;]+/gi, "Bearer [redacted]").replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[redacted]");
|
|
321
|
+
for (const sensitive of sensitiveValues) {
|
|
322
|
+
if (sensitive) safe = safe.split(sensitive).join("[redacted]");
|
|
323
|
+
}
|
|
324
|
+
return safe.slice(0, 300);
|
|
316
325
|
}
|
|
317
|
-
function safeOptionalText(value) {
|
|
326
|
+
function safeOptionalText(value, sensitiveValues) {
|
|
318
327
|
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
319
|
-
return safeText(String(value), "");
|
|
328
|
+
return safeText(String(value), "", sensitiveValues);
|
|
320
329
|
}
|
|
321
330
|
function isRecord2(value) {
|
|
322
331
|
return typeof value === "object" && value !== null;
|
package/dist/index.d.ts
CHANGED
|
@@ -545,7 +545,7 @@ declare class UserRepository {
|
|
|
545
545
|
private schema;
|
|
546
546
|
private get users();
|
|
547
547
|
private get roles();
|
|
548
|
-
/** Shared query helper */
|
|
548
|
+
/** Shared query helper, scoped to the current database/transaction identity. */
|
|
549
549
|
private queryHelper?;
|
|
550
550
|
private get q();
|
|
551
551
|
getAll(limit?: number, offset?: number): Promise<UserWithPermissions[]>;
|
|
@@ -820,6 +820,7 @@ declare class UserService {
|
|
|
820
820
|
findByEmailInsensitive(email: string): Promise<(User & {
|
|
821
821
|
role?: string | null;
|
|
822
822
|
}) | undefined>;
|
|
823
|
+
findByPhone(phone: string): Promise<UserWithPermissions>;
|
|
823
824
|
getAuthRecordById(id: string): Promise<User | undefined>;
|
|
824
825
|
create(data: Record<string, any>): Promise<SanitizedUser>;
|
|
825
826
|
update(id: string, data: Record<string, any>): Promise<SanitizedUser>;
|
|
@@ -846,7 +847,7 @@ declare class TokenRepository {
|
|
|
846
847
|
private schema;
|
|
847
848
|
private get tokens();
|
|
848
849
|
private get users();
|
|
849
|
-
/** Shared query helper */
|
|
850
|
+
/** Shared query helper, scoped to the current database/transaction identity. */
|
|
850
851
|
private queryHelper?;
|
|
851
852
|
private get q();
|
|
852
853
|
/**
|
|
@@ -1144,10 +1145,13 @@ declare const inviteUserDto: z.ZodObject<{
|
|
|
1144
1145
|
declare const userIdParam: z.ZodObject<{
|
|
1145
1146
|
id: z.ZodString;
|
|
1146
1147
|
}, z.core.$strip>;
|
|
1147
|
-
declare const loginDto: z.ZodObject<{
|
|
1148
|
+
declare const loginDto: z.ZodUnion<readonly [z.ZodObject<{
|
|
1149
|
+
identifier: z.ZodString;
|
|
1150
|
+
password: z.ZodString;
|
|
1151
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1148
1152
|
email: z.ZodString;
|
|
1149
1153
|
password: z.ZodString;
|
|
1150
|
-
}, z.core.$strip>;
|
|
1154
|
+
}, z.core.$strip>]>;
|
|
1151
1155
|
declare const changePasswordDto: z.ZodObject<{
|
|
1152
1156
|
currentPassword: z.ZodString;
|
|
1153
1157
|
newPassword: z.ZodString;
|
|
@@ -1306,6 +1310,12 @@ declare class AuthService {
|
|
|
1306
1310
|
}>;
|
|
1307
1311
|
}
|
|
1308
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* Composite key: IP + hashed normalized login/registration identity.
|
|
1315
|
+
* Buckets rate limits per IP+credential combo so different users
|
|
1316
|
+
* on the same IP (e.g. localhost, NAT) don't share a single bucket.
|
|
1317
|
+
*/
|
|
1318
|
+
declare const authIdentityRateLimitKey: (ctx: Context) => Promise<string>;
|
|
1309
1319
|
declare class AuthController {
|
|
1310
1320
|
private authService;
|
|
1311
1321
|
constructor(authService: AuthService);
|
|
@@ -1419,6 +1429,15 @@ declare class AuthResolver {
|
|
|
1419
1429
|
onReady(): Promise<void>;
|
|
1420
1430
|
}
|
|
1421
1431
|
|
|
1432
|
+
/**
|
|
1433
|
+
* Normalize an authentication identifier for both login resolution and
|
|
1434
|
+
* rate-limit bucketing. Email comparison is case-insensitive. Phone login uses
|
|
1435
|
+
* an E.164-compatible value and accepts common visual separators plus a `00`
|
|
1436
|
+
* international prefix.
|
|
1437
|
+
*/
|
|
1438
|
+
declare function normalizeAuthIdentifier(value: unknown): string | null;
|
|
1439
|
+
declare function isEmailIdentifier(value: string): boolean;
|
|
1440
|
+
|
|
1422
1441
|
interface RunAsUser {
|
|
1423
1442
|
id: string;
|
|
1424
1443
|
role?: string | null;
|
|
@@ -2317,4 +2336,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
|
|
|
2317
2336
|
*/
|
|
2318
2337
|
declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
|
|
2319
2338
|
|
|
2320
|
-
export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type DefineRolesOptions, type EmailParam, EncryptionService, type GoogleOAuthConfig, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, type ResetPasswordDto, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmpty, isFile, isPath, join, languageParam, loginDto, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
|
|
2339
|
+
export { AUTH_CONFIG, en as AUTH_EN, AUTH_LOCALES, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthPluginConfig, AuthQueries, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type DefineRolesOptions, type EmailParam, EncryptionService, type GoogleOAuthConfig, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, type ResetPasswordDto, type ResourceAccessor, type ResourceGuards, type ResourceGuardsOptions, type RevokeTokenDto, Role, RoleController, RoleEntity, RoleGuard, type RoleIdParam, type RoleInput, RolePermission, RoleRepository, RoleService, type RoleType, RoleValidator, type RunAsUser, type SanitizedUser, ScopeContext, type ScopeResult, type SeedAuthDataConfig, type SeedAuthDataResult, type SeedUserConfig, type SessionCookieData, TOKEN_STATUS, TOKEN_TYPE, type TokenIdParam, type TokenPair, TokenRepository, TokenService, USER_STATUS, type UpdatePermissionDto, type UpdateRoleDto, type UpdateTokenDto, type UpdateUserDto, User, UserController, type UserIdInParam, type UserIdParam, type UserListQuery, UserRepository, UserService, UserValidator, type UserWithPermissions, type VerifyTokenDto, assignPermissionDto, assignRoleDto, assignRoleParams, auth$1 as auth, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createPermissionDto, createRoleDto, createTokenDto, createUserDto, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
|
package/dist/index.js
CHANGED
|
@@ -573,10 +573,14 @@ var UserRepository = class UserRepository2 {
|
|
|
573
573
|
get roles() {
|
|
574
574
|
return this.schema.roles;
|
|
575
575
|
}
|
|
576
|
-
/** Shared query helper */
|
|
576
|
+
/** Shared query helper, scoped to the current database/transaction identity. */
|
|
577
577
|
queryHelper;
|
|
578
578
|
get q() {
|
|
579
|
-
|
|
579
|
+
const db = this.db;
|
|
580
|
+
if (this.queryHelper?.db !== db) {
|
|
581
|
+
this.queryHelper = { db, queries: new AuthQueries(db, this.schema) };
|
|
582
|
+
}
|
|
583
|
+
return this.queryHelper.queries;
|
|
580
584
|
}
|
|
581
585
|
async getAll(limit = 50, offset = 0) {
|
|
582
586
|
const allUsers = await this.db.select(this.q.userSelection()).from(this.users).leftJoin(this.roles, eq2(this.users.roleId, this.roles.id)).limit(limit).offset(offset);
|
|
@@ -1260,6 +1264,9 @@ var UserService = class UserService2 {
|
|
|
1260
1264
|
async findByEmailInsensitive(email2) {
|
|
1261
1265
|
return await this.userRepository.getByEmailInsensitive(email2);
|
|
1262
1266
|
}
|
|
1267
|
+
async findByPhone(phone) {
|
|
1268
|
+
return await this.userRepository.findByPhone(phone);
|
|
1269
|
+
}
|
|
1263
1270
|
async getAuthRecordById(id) {
|
|
1264
1271
|
return await this.userRepository.getRawById(id);
|
|
1265
1272
|
}
|
|
@@ -1420,10 +1427,14 @@ var TokenRepository = class TokenRepository2 {
|
|
|
1420
1427
|
get users() {
|
|
1421
1428
|
return this.schema.users;
|
|
1422
1429
|
}
|
|
1423
|
-
/** Shared query helper */
|
|
1430
|
+
/** Shared query helper, scoped to the current database/transaction identity. */
|
|
1424
1431
|
queryHelper;
|
|
1425
1432
|
get q() {
|
|
1426
|
-
|
|
1433
|
+
const db = this.db;
|
|
1434
|
+
if (this.queryHelper?.db !== db) {
|
|
1435
|
+
this.queryHelper = { db, queries: new AuthQueries(db, this.schema) };
|
|
1436
|
+
}
|
|
1437
|
+
return this.queryHelper.queries;
|
|
1427
1438
|
}
|
|
1428
1439
|
/**
|
|
1429
1440
|
* Upsert the refresh-token row for a session, keyed on `tokenFamily` (the
|
|
@@ -1575,7 +1586,7 @@ var TokenService = class TokenService2 {
|
|
|
1575
1586
|
if (authorization?.startsWith("Bearer ")) {
|
|
1576
1587
|
return authorization.split(" ")[1];
|
|
1577
1588
|
}
|
|
1578
|
-
Err6(this.t("errors.tokenMissing"));
|
|
1589
|
+
Err6(this.t("errors.tokenMissing"), 401);
|
|
1579
1590
|
}
|
|
1580
1591
|
/**
|
|
1581
1592
|
* Verify access token and check blacklist
|
|
@@ -1586,7 +1597,7 @@ var TokenService = class TokenService2 {
|
|
|
1586
1597
|
try {
|
|
1587
1598
|
payload = jwt.verify(token, this.config.jwt.accessSecret);
|
|
1588
1599
|
} catch {
|
|
1589
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1600
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1590
1601
|
}
|
|
1591
1602
|
const sessionKey = this.sessionVersionKey(payload.userId);
|
|
1592
1603
|
const blacklistKey = payload.jti ? `${this.blacklistPrefix}${payload.jti}` : null;
|
|
@@ -1599,15 +1610,15 @@ var TokenService = class TokenService2 {
|
|
|
1599
1610
|
const values = await this.getCacheValues(keys);
|
|
1600
1611
|
const valueByKey = new Map(keys.map((key, i) => [key, values[i]]));
|
|
1601
1612
|
if (blacklistKey && valueByKey.get(blacklistKey) != null) {
|
|
1602
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1613
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1603
1614
|
}
|
|
1604
1615
|
if (familyKey && valueByKey.get(familyKey) != null) {
|
|
1605
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1616
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1606
1617
|
}
|
|
1607
1618
|
const activeSessionVersion = this.parseSessionVersion(valueByKey.get(sessionKey) ?? null);
|
|
1608
1619
|
const tokenSessionVersion = payload.sessionVersion ?? 0;
|
|
1609
1620
|
if (tokenSessionVersion !== activeSessionVersion) {
|
|
1610
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1621
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1611
1622
|
}
|
|
1612
1623
|
return payload;
|
|
1613
1624
|
}
|
|
@@ -1616,13 +1627,13 @@ var TokenService = class TokenService2 {
|
|
|
1616
1627
|
try {
|
|
1617
1628
|
decoded = jwt.verify(token, this.config.jwt.refreshSecret);
|
|
1618
1629
|
} catch {
|
|
1619
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1630
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1620
1631
|
}
|
|
1621
1632
|
if (decoded.type && decoded.type !== "refresh") {
|
|
1622
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1633
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1623
1634
|
}
|
|
1624
1635
|
if (!decoded.tokenFamily) {
|
|
1625
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1636
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1626
1637
|
}
|
|
1627
1638
|
return { userId: decoded.userId, tokenFamily: decoded.tokenFamily };
|
|
1628
1639
|
}
|
|
@@ -1641,12 +1652,12 @@ var TokenService = class TokenService2 {
|
|
|
1641
1652
|
async resolveRefreshSessionFromCookie() {
|
|
1642
1653
|
const refreshToken = this.cookieManager.getRefreshToken();
|
|
1643
1654
|
if (!refreshToken) {
|
|
1644
|
-
Err6(this.t("errors.refreshTokenMissing"));
|
|
1655
|
+
Err6(this.t("errors.refreshTokenMissing"), 401);
|
|
1645
1656
|
}
|
|
1646
1657
|
const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
|
|
1647
1658
|
const stored = await this.tokenRepository.getByFamily(tokenFamily);
|
|
1648
1659
|
if (!stored || stored.userId !== userId) {
|
|
1649
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1660
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1650
1661
|
}
|
|
1651
1662
|
const presentedHash = this.hashToken(refreshToken);
|
|
1652
1663
|
if (presentedHash === stored.token) {
|
|
@@ -1656,7 +1667,7 @@ var TokenService = class TokenService2 {
|
|
|
1656
1667
|
if (canRecover) {
|
|
1657
1668
|
return { userId, tokenFamily };
|
|
1658
1669
|
}
|
|
1659
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1670
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1660
1671
|
}
|
|
1661
1672
|
async resolveUserFromCookie() {
|
|
1662
1673
|
return (await this.resolveRefreshSessionFromCookie()).userId;
|
|
@@ -1833,12 +1844,12 @@ var TokenService = class TokenService2 {
|
|
|
1833
1844
|
async refreshTokens() {
|
|
1834
1845
|
const refreshToken = this.cookieManager.getRefreshToken();
|
|
1835
1846
|
if (!refreshToken) {
|
|
1836
|
-
Err6(this.t("errors.refreshTokenMissing"));
|
|
1847
|
+
Err6(this.t("errors.refreshTokenMissing"), 401);
|
|
1837
1848
|
}
|
|
1838
1849
|
const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
|
|
1839
1850
|
const stored = await this.tokenRepository.getByFamily(tokenFamily);
|
|
1840
1851
|
if (!stored || stored.userId !== userId) {
|
|
1841
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1852
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1842
1853
|
}
|
|
1843
1854
|
const presentedHash = this.hashToken(refreshToken);
|
|
1844
1855
|
await this.requireActiveRefreshUser(userId, tokenFamily);
|
|
@@ -1849,18 +1860,18 @@ var TokenService = class TokenService2 {
|
|
|
1849
1860
|
if (canRecover) {
|
|
1850
1861
|
const claimed = await this.tokenRepository.markPreviousUsed(tokenFamily, presentedHash);
|
|
1851
1862
|
if (!claimed?.length) {
|
|
1852
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1863
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1853
1864
|
}
|
|
1854
1865
|
return this.generateTokens(userId, tokenFamily);
|
|
1855
1866
|
}
|
|
1856
1867
|
await this.revokeSuspectRefreshFamily(userId, tokenFamily);
|
|
1857
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1868
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1858
1869
|
}
|
|
1859
1870
|
async requireActiveRefreshUser(userId, tokenFamily) {
|
|
1860
1871
|
const user = await this.tokenRepository.getUser(userId);
|
|
1861
1872
|
if (!user || user.status !== "active") {
|
|
1862
1873
|
await this.revokeFamily(tokenFamily);
|
|
1863
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1874
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1864
1875
|
}
|
|
1865
1876
|
return user;
|
|
1866
1877
|
}
|
|
@@ -1894,7 +1905,7 @@ var TokenService = class TokenService2 {
|
|
|
1894
1905
|
const userId = await this.resolveUserFromCookie();
|
|
1895
1906
|
const user = await this.getUserById(userId);
|
|
1896
1907
|
if (!user) {
|
|
1897
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1908
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1898
1909
|
}
|
|
1899
1910
|
return user;
|
|
1900
1911
|
}
|
|
@@ -2126,6 +2137,27 @@ AuthSessionService = __decorate11([
|
|
|
2126
2137
|
__metadata11("design:paramtypes", [typeof (_a7 = typeof TokenService !== "undefined" && TokenService) === "function" ? _a7 : Object, typeof (_b5 = typeof UserService !== "undefined" && UserService) === "function" ? _b5 : Object, typeof (_c3 = typeof CookieManager !== "undefined" && CookieManager) === "function" ? _c3 : Object])
|
|
2127
2138
|
], AuthSessionService);
|
|
2128
2139
|
|
|
2140
|
+
// src/auth/authIdentity.ts
|
|
2141
|
+
var PHONE_PATTERN = /^\+[1-9]\d{7,14}$/;
|
|
2142
|
+
function normalizeAuthIdentifier(value) {
|
|
2143
|
+
if (typeof value !== "string")
|
|
2144
|
+
return null;
|
|
2145
|
+
const trimmed = value.trim();
|
|
2146
|
+
if (!trimmed || trimmed.length > 254)
|
|
2147
|
+
return null;
|
|
2148
|
+
if (trimmed.includes("@")) {
|
|
2149
|
+
return trimmed.toLowerCase();
|
|
2150
|
+
}
|
|
2151
|
+
const compact = trimmed.replace(/[\s().-]+/g, "");
|
|
2152
|
+
const international = compact.startsWith("00") ? `+${compact.slice(2)}` : compact;
|
|
2153
|
+
return PHONE_PATTERN.test(international) ? international : null;
|
|
2154
|
+
}
|
|
2155
|
+
__name(normalizeAuthIdentifier, "normalizeAuthIdentifier");
|
|
2156
|
+
function isEmailIdentifier(value) {
|
|
2157
|
+
return value.includes("@");
|
|
2158
|
+
}
|
|
2159
|
+
__name(isEmailIdentifier, "isEmailIdentifier");
|
|
2160
|
+
|
|
2129
2161
|
// src/auth/AuthService.ts
|
|
2130
2162
|
var __decorate12 = function(decorators, target, key, desc) {
|
|
2131
2163
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2256,8 +2288,16 @@ var AuthService = class AuthService2 {
|
|
|
2256
2288
|
return this.inviteUser(body);
|
|
2257
2289
|
}
|
|
2258
2290
|
async loginUser(body) {
|
|
2259
|
-
const
|
|
2260
|
-
const
|
|
2291
|
+
const password = body.password;
|
|
2292
|
+
const rawIdentifier = "identifier" in body ? body.identifier : body.email;
|
|
2293
|
+
const identifier = normalizeAuthIdentifier(rawIdentifier);
|
|
2294
|
+
let user;
|
|
2295
|
+
if (identifier && isEmailIdentifier(identifier)) {
|
|
2296
|
+
user = await this.userService.findByEmailInsensitive(identifier);
|
|
2297
|
+
} else if (identifier) {
|
|
2298
|
+
const phoneUser = await this.userService.findByPhone(identifier);
|
|
2299
|
+
user = phoneUser ? await this.userService.findByEmail(phoneUser.email) : void 0;
|
|
2300
|
+
}
|
|
2261
2301
|
if (user?.lockoutUntil && !this.isLockoutActive(user.lockoutUntil)) {
|
|
2262
2302
|
await this.userService.resetFailedAttempts(user.id);
|
|
2263
2303
|
user.failedLoginAttempts = 0;
|
|
@@ -2276,10 +2316,10 @@ var AuthService = class AuthService2 {
|
|
|
2276
2316
|
Err8(this.t("errors.accountLocked"), 423);
|
|
2277
2317
|
}
|
|
2278
2318
|
}
|
|
2279
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2319
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2280
2320
|
}
|
|
2281
2321
|
if (user.status !== "active") {
|
|
2282
|
-
Err8(this.t("errors.accountInactive"));
|
|
2322
|
+
Err8(this.t("errors.accountInactive"), 403);
|
|
2283
2323
|
}
|
|
2284
2324
|
if (this.config.requireVerifiedEmail && !user.emailVerified) {
|
|
2285
2325
|
Err8(this.t("errors.emailNotVerified"), 403);
|
|
@@ -2406,11 +2446,11 @@ var AuthService = class AuthService2 {
|
|
|
2406
2446
|
async changePassword(userId, currentPassword, newPassword) {
|
|
2407
2447
|
const user = await this.userService.getAuthRecordById(userId);
|
|
2408
2448
|
if (!user?.password) {
|
|
2409
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2449
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2410
2450
|
}
|
|
2411
2451
|
const isValid = await this.userValidator.comparePassword(currentPassword, user.password);
|
|
2412
2452
|
if (!isValid) {
|
|
2413
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2453
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2414
2454
|
}
|
|
2415
2455
|
this.userValidator.validatePasswordStrength(newPassword);
|
|
2416
2456
|
await this.userService.update(userId, { password: newPassword });
|
|
@@ -2566,10 +2606,17 @@ var inviteUserDto = z.object({
|
|
|
2566
2606
|
var userIdParam = z.object({
|
|
2567
2607
|
id: z.string().min(1, "User ID is required")
|
|
2568
2608
|
});
|
|
2569
|
-
var
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2609
|
+
var identifierField = z.string().trim().min(1).max(254);
|
|
2610
|
+
var loginDto = z.union([
|
|
2611
|
+
z.object({
|
|
2612
|
+
identifier: identifierField,
|
|
2613
|
+
password: passwordField
|
|
2614
|
+
}),
|
|
2615
|
+
z.object({
|
|
2616
|
+
email: emailField,
|
|
2617
|
+
password: passwordField
|
|
2618
|
+
})
|
|
2619
|
+
]);
|
|
2573
2620
|
var changePasswordDto = z.object({
|
|
2574
2621
|
currentPassword: passwordField,
|
|
2575
2622
|
newPassword: passwordField
|
|
@@ -2622,19 +2669,19 @@ var cookieFingerprint = /* @__PURE__ */ __name(() => (ctx) => {
|
|
|
2622
2669
|
const fingerprint = cookie ? hashKeyPart(cookie) : "none";
|
|
2623
2670
|
return `${ip}:${fingerprint}`;
|
|
2624
2671
|
}, "cookieFingerprint");
|
|
2625
|
-
var
|
|
2672
|
+
var authIdentityRateLimitKey = /* @__PURE__ */ __name(async (ctx) => {
|
|
2626
2673
|
const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
|
|
2627
2674
|
try {
|
|
2628
2675
|
const body = await ctx.req.json();
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2676
|
+
const identity = body?.identifier ?? body?.email;
|
|
2677
|
+
const normalizedIdentity = normalizeAuthIdentifier(identity);
|
|
2678
|
+
if (normalizedIdentity) {
|
|
2679
|
+
return `${ip}:${hashKeyPart(normalizedIdentity)}`;
|
|
2633
2680
|
}
|
|
2634
2681
|
} catch {
|
|
2635
2682
|
}
|
|
2636
2683
|
return ip;
|
|
2637
|
-
}, "
|
|
2684
|
+
}, "authIdentityRateLimitKey");
|
|
2638
2685
|
var AuthController = class AuthController2 {
|
|
2639
2686
|
static {
|
|
2640
2687
|
__name(this, "AuthController");
|
|
@@ -2681,7 +2728,7 @@ var AuthController = class AuthController2 {
|
|
|
2681
2728
|
};
|
|
2682
2729
|
__decorate15([
|
|
2683
2730
|
Post("/register"),
|
|
2684
|
-
RateLimit({ limit: 5, window: "15m", key:
|
|
2731
|
+
RateLimit({ limit: 5, window: "15m", key: authIdentityRateLimitKey }),
|
|
2685
2732
|
Validate(registerDto),
|
|
2686
2733
|
ResMsg("auth.success.register"),
|
|
2687
2734
|
__param5(0, Body()),
|
|
@@ -2691,7 +2738,7 @@ __decorate15([
|
|
|
2691
2738
|
], AuthController.prototype, "registerUser", null);
|
|
2692
2739
|
__decorate15([
|
|
2693
2740
|
Post("/login"),
|
|
2694
|
-
RateLimit({ limit: 5, window: "15m", key:
|
|
2741
|
+
RateLimit({ limit: 5, window: "15m", key: authIdentityRateLimitKey, message: "Too many login attempts. Please try again later." }),
|
|
2695
2742
|
Validate(loginDto),
|
|
2696
2743
|
ResMsg("auth.success.login"),
|
|
2697
2744
|
__param5(0, Body()),
|
|
@@ -2760,7 +2807,7 @@ __decorate15([
|
|
|
2760
2807
|
], AuthController.prototype, "userProfile", null);
|
|
2761
2808
|
__decorate15([
|
|
2762
2809
|
Post("/forgot-password"),
|
|
2763
|
-
RateLimit({ limit: 3, window: "15m", key:
|
|
2810
|
+
RateLimit({ limit: 3, window: "15m", key: authIdentityRateLimitKey, message: "Too many password reset requests. Please try again later." }),
|
|
2764
2811
|
Validate(resetPasswordDto),
|
|
2765
2812
|
ResMsg("auth.success.passwordResetSent"),
|
|
2766
2813
|
__param5(0, Body()),
|
|
@@ -5699,6 +5746,7 @@ export {
|
|
|
5699
5746
|
assignRoleDto,
|
|
5700
5747
|
assignRoleParams,
|
|
5701
5748
|
auth,
|
|
5749
|
+
authIdentityRateLimitKey,
|
|
5702
5750
|
authSchema,
|
|
5703
5751
|
authSeed,
|
|
5704
5752
|
avatarsPath,
|
|
@@ -5723,12 +5771,14 @@ export {
|
|
|
5723
5771
|
isAdmin,
|
|
5724
5772
|
isAdministrator,
|
|
5725
5773
|
isAuth,
|
|
5774
|
+
isEmailIdentifier,
|
|
5726
5775
|
isEmpty,
|
|
5727
5776
|
isFile,
|
|
5728
5777
|
isPath,
|
|
5729
5778
|
join2 as join,
|
|
5730
5779
|
languageParam,
|
|
5731
5780
|
loginDto,
|
|
5781
|
+
normalizeAuthIdentifier,
|
|
5732
5782
|
oauthAccountsTable,
|
|
5733
5783
|
own,
|
|
5734
5784
|
parseSchema,
|