najm-auth 2.0.9 → 2.0.10
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 +22 -3
- package/dist/index.js +78 -36
- 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
|
@@ -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>;
|
|
@@ -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
|
@@ -1260,6 +1260,9 @@ var UserService = class UserService2 {
|
|
|
1260
1260
|
async findByEmailInsensitive(email2) {
|
|
1261
1261
|
return await this.userRepository.getByEmailInsensitive(email2);
|
|
1262
1262
|
}
|
|
1263
|
+
async findByPhone(phone) {
|
|
1264
|
+
return await this.userRepository.findByPhone(phone);
|
|
1265
|
+
}
|
|
1263
1266
|
async getAuthRecordById(id) {
|
|
1264
1267
|
return await this.userRepository.getRawById(id);
|
|
1265
1268
|
}
|
|
@@ -1575,7 +1578,7 @@ var TokenService = class TokenService2 {
|
|
|
1575
1578
|
if (authorization?.startsWith("Bearer ")) {
|
|
1576
1579
|
return authorization.split(" ")[1];
|
|
1577
1580
|
}
|
|
1578
|
-
Err6(this.t("errors.tokenMissing"));
|
|
1581
|
+
Err6(this.t("errors.tokenMissing"), 401);
|
|
1579
1582
|
}
|
|
1580
1583
|
/**
|
|
1581
1584
|
* Verify access token and check blacklist
|
|
@@ -1586,7 +1589,7 @@ var TokenService = class TokenService2 {
|
|
|
1586
1589
|
try {
|
|
1587
1590
|
payload = jwt.verify(token, this.config.jwt.accessSecret);
|
|
1588
1591
|
} catch {
|
|
1589
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1592
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1590
1593
|
}
|
|
1591
1594
|
const sessionKey = this.sessionVersionKey(payload.userId);
|
|
1592
1595
|
const blacklistKey = payload.jti ? `${this.blacklistPrefix}${payload.jti}` : null;
|
|
@@ -1599,15 +1602,15 @@ var TokenService = class TokenService2 {
|
|
|
1599
1602
|
const values = await this.getCacheValues(keys);
|
|
1600
1603
|
const valueByKey = new Map(keys.map((key, i) => [key, values[i]]));
|
|
1601
1604
|
if (blacklistKey && valueByKey.get(blacklistKey) != null) {
|
|
1602
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1605
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1603
1606
|
}
|
|
1604
1607
|
if (familyKey && valueByKey.get(familyKey) != null) {
|
|
1605
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1608
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1606
1609
|
}
|
|
1607
1610
|
const activeSessionVersion = this.parseSessionVersion(valueByKey.get(sessionKey) ?? null);
|
|
1608
1611
|
const tokenSessionVersion = payload.sessionVersion ?? 0;
|
|
1609
1612
|
if (tokenSessionVersion !== activeSessionVersion) {
|
|
1610
|
-
Err6(this.t("errors.tokenRevoked"));
|
|
1613
|
+
Err6(this.t("errors.tokenRevoked"), 401);
|
|
1611
1614
|
}
|
|
1612
1615
|
return payload;
|
|
1613
1616
|
}
|
|
@@ -1616,13 +1619,13 @@ var TokenService = class TokenService2 {
|
|
|
1616
1619
|
try {
|
|
1617
1620
|
decoded = jwt.verify(token, this.config.jwt.refreshSecret);
|
|
1618
1621
|
} catch {
|
|
1619
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1622
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1620
1623
|
}
|
|
1621
1624
|
if (decoded.type && decoded.type !== "refresh") {
|
|
1622
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1625
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1623
1626
|
}
|
|
1624
1627
|
if (!decoded.tokenFamily) {
|
|
1625
|
-
Err6(this.t("errors.tokenVerificationFailed"));
|
|
1628
|
+
Err6(this.t("errors.tokenVerificationFailed"), 401);
|
|
1626
1629
|
}
|
|
1627
1630
|
return { userId: decoded.userId, tokenFamily: decoded.tokenFamily };
|
|
1628
1631
|
}
|
|
@@ -1641,12 +1644,12 @@ var TokenService = class TokenService2 {
|
|
|
1641
1644
|
async resolveRefreshSessionFromCookie() {
|
|
1642
1645
|
const refreshToken = this.cookieManager.getRefreshToken();
|
|
1643
1646
|
if (!refreshToken) {
|
|
1644
|
-
Err6(this.t("errors.refreshTokenMissing"));
|
|
1647
|
+
Err6(this.t("errors.refreshTokenMissing"), 401);
|
|
1645
1648
|
}
|
|
1646
1649
|
const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
|
|
1647
1650
|
const stored = await this.tokenRepository.getByFamily(tokenFamily);
|
|
1648
1651
|
if (!stored || stored.userId !== userId) {
|
|
1649
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1652
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1650
1653
|
}
|
|
1651
1654
|
const presentedHash = this.hashToken(refreshToken);
|
|
1652
1655
|
if (presentedHash === stored.token) {
|
|
@@ -1656,7 +1659,7 @@ var TokenService = class TokenService2 {
|
|
|
1656
1659
|
if (canRecover) {
|
|
1657
1660
|
return { userId, tokenFamily };
|
|
1658
1661
|
}
|
|
1659
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1662
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1660
1663
|
}
|
|
1661
1664
|
async resolveUserFromCookie() {
|
|
1662
1665
|
return (await this.resolveRefreshSessionFromCookie()).userId;
|
|
@@ -1833,12 +1836,12 @@ var TokenService = class TokenService2 {
|
|
|
1833
1836
|
async refreshTokens() {
|
|
1834
1837
|
const refreshToken = this.cookieManager.getRefreshToken();
|
|
1835
1838
|
if (!refreshToken) {
|
|
1836
|
-
Err6(this.t("errors.refreshTokenMissing"));
|
|
1839
|
+
Err6(this.t("errors.refreshTokenMissing"), 401);
|
|
1837
1840
|
}
|
|
1838
1841
|
const { userId, tokenFamily } = this.verifyRefreshToken(refreshToken);
|
|
1839
1842
|
const stored = await this.tokenRepository.getByFamily(tokenFamily);
|
|
1840
1843
|
if (!stored || stored.userId !== userId) {
|
|
1841
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1844
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1842
1845
|
}
|
|
1843
1846
|
const presentedHash = this.hashToken(refreshToken);
|
|
1844
1847
|
await this.requireActiveRefreshUser(userId, tokenFamily);
|
|
@@ -1849,18 +1852,18 @@ var TokenService = class TokenService2 {
|
|
|
1849
1852
|
if (canRecover) {
|
|
1850
1853
|
const claimed = await this.tokenRepository.markPreviousUsed(tokenFamily, presentedHash);
|
|
1851
1854
|
if (!claimed?.length) {
|
|
1852
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1855
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1853
1856
|
}
|
|
1854
1857
|
return this.generateTokens(userId, tokenFamily);
|
|
1855
1858
|
}
|
|
1856
1859
|
await this.revokeSuspectRefreshFamily(userId, tokenFamily);
|
|
1857
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1860
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1858
1861
|
}
|
|
1859
1862
|
async requireActiveRefreshUser(userId, tokenFamily) {
|
|
1860
1863
|
const user = await this.tokenRepository.getUser(userId);
|
|
1861
1864
|
if (!user || user.status !== "active") {
|
|
1862
1865
|
await this.revokeFamily(tokenFamily);
|
|
1863
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1866
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1864
1867
|
}
|
|
1865
1868
|
return user;
|
|
1866
1869
|
}
|
|
@@ -1894,7 +1897,7 @@ var TokenService = class TokenService2 {
|
|
|
1894
1897
|
const userId = await this.resolveUserFromCookie();
|
|
1895
1898
|
const user = await this.getUserById(userId);
|
|
1896
1899
|
if (!user) {
|
|
1897
|
-
Err6(this.t("errors.refreshTokenInvalid"));
|
|
1900
|
+
Err6(this.t("errors.refreshTokenInvalid"), 401);
|
|
1898
1901
|
}
|
|
1899
1902
|
return user;
|
|
1900
1903
|
}
|
|
@@ -2126,6 +2129,27 @@ AuthSessionService = __decorate11([
|
|
|
2126
2129
|
__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
2130
|
], AuthSessionService);
|
|
2128
2131
|
|
|
2132
|
+
// src/auth/authIdentity.ts
|
|
2133
|
+
var PHONE_PATTERN = /^\+[1-9]\d{7,14}$/;
|
|
2134
|
+
function normalizeAuthIdentifier(value) {
|
|
2135
|
+
if (typeof value !== "string")
|
|
2136
|
+
return null;
|
|
2137
|
+
const trimmed = value.trim();
|
|
2138
|
+
if (!trimmed || trimmed.length > 254)
|
|
2139
|
+
return null;
|
|
2140
|
+
if (trimmed.includes("@")) {
|
|
2141
|
+
return trimmed.toLowerCase();
|
|
2142
|
+
}
|
|
2143
|
+
const compact = trimmed.replace(/[\s().-]+/g, "");
|
|
2144
|
+
const international = compact.startsWith("00") ? `+${compact.slice(2)}` : compact;
|
|
2145
|
+
return PHONE_PATTERN.test(international) ? international : null;
|
|
2146
|
+
}
|
|
2147
|
+
__name(normalizeAuthIdentifier, "normalizeAuthIdentifier");
|
|
2148
|
+
function isEmailIdentifier(value) {
|
|
2149
|
+
return value.includes("@");
|
|
2150
|
+
}
|
|
2151
|
+
__name(isEmailIdentifier, "isEmailIdentifier");
|
|
2152
|
+
|
|
2129
2153
|
// src/auth/AuthService.ts
|
|
2130
2154
|
var __decorate12 = function(decorators, target, key, desc) {
|
|
2131
2155
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2256,8 +2280,16 @@ var AuthService = class AuthService2 {
|
|
|
2256
2280
|
return this.inviteUser(body);
|
|
2257
2281
|
}
|
|
2258
2282
|
async loginUser(body) {
|
|
2259
|
-
const
|
|
2260
|
-
const
|
|
2283
|
+
const password = body.password;
|
|
2284
|
+
const rawIdentifier = "identifier" in body ? body.identifier : body.email;
|
|
2285
|
+
const identifier = normalizeAuthIdentifier(rawIdentifier);
|
|
2286
|
+
let user;
|
|
2287
|
+
if (identifier && isEmailIdentifier(identifier)) {
|
|
2288
|
+
user = await this.userService.findByEmailInsensitive(identifier);
|
|
2289
|
+
} else if (identifier) {
|
|
2290
|
+
const phoneUser = await this.userService.findByPhone(identifier);
|
|
2291
|
+
user = phoneUser ? await this.userService.findByEmail(phoneUser.email) : void 0;
|
|
2292
|
+
}
|
|
2261
2293
|
if (user?.lockoutUntil && !this.isLockoutActive(user.lockoutUntil)) {
|
|
2262
2294
|
await this.userService.resetFailedAttempts(user.id);
|
|
2263
2295
|
user.failedLoginAttempts = 0;
|
|
@@ -2276,10 +2308,10 @@ var AuthService = class AuthService2 {
|
|
|
2276
2308
|
Err8(this.t("errors.accountLocked"), 423);
|
|
2277
2309
|
}
|
|
2278
2310
|
}
|
|
2279
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2311
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2280
2312
|
}
|
|
2281
2313
|
if (user.status !== "active") {
|
|
2282
|
-
Err8(this.t("errors.accountInactive"));
|
|
2314
|
+
Err8(this.t("errors.accountInactive"), 403);
|
|
2283
2315
|
}
|
|
2284
2316
|
if (this.config.requireVerifiedEmail && !user.emailVerified) {
|
|
2285
2317
|
Err8(this.t("errors.emailNotVerified"), 403);
|
|
@@ -2406,11 +2438,11 @@ var AuthService = class AuthService2 {
|
|
|
2406
2438
|
async changePassword(userId, currentPassword, newPassword) {
|
|
2407
2439
|
const user = await this.userService.getAuthRecordById(userId);
|
|
2408
2440
|
if (!user?.password) {
|
|
2409
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2441
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2410
2442
|
}
|
|
2411
2443
|
const isValid = await this.userValidator.comparePassword(currentPassword, user.password);
|
|
2412
2444
|
if (!isValid) {
|
|
2413
|
-
Err8(this.t("errors.invalidCredentials"));
|
|
2445
|
+
Err8(this.t("errors.invalidCredentials"), 401);
|
|
2414
2446
|
}
|
|
2415
2447
|
this.userValidator.validatePasswordStrength(newPassword);
|
|
2416
2448
|
await this.userService.update(userId, { password: newPassword });
|
|
@@ -2566,10 +2598,17 @@ var inviteUserDto = z.object({
|
|
|
2566
2598
|
var userIdParam = z.object({
|
|
2567
2599
|
id: z.string().min(1, "User ID is required")
|
|
2568
2600
|
});
|
|
2569
|
-
var
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2601
|
+
var identifierField = z.string().trim().min(1).max(254);
|
|
2602
|
+
var loginDto = z.union([
|
|
2603
|
+
z.object({
|
|
2604
|
+
identifier: identifierField,
|
|
2605
|
+
password: passwordField
|
|
2606
|
+
}),
|
|
2607
|
+
z.object({
|
|
2608
|
+
email: emailField,
|
|
2609
|
+
password: passwordField
|
|
2610
|
+
})
|
|
2611
|
+
]);
|
|
2573
2612
|
var changePasswordDto = z.object({
|
|
2574
2613
|
currentPassword: passwordField,
|
|
2575
2614
|
newPassword: passwordField
|
|
@@ -2622,19 +2661,19 @@ var cookieFingerprint = /* @__PURE__ */ __name(() => (ctx) => {
|
|
|
2622
2661
|
const fingerprint = cookie ? hashKeyPart(cookie) : "none";
|
|
2623
2662
|
return `${ip}:${fingerprint}`;
|
|
2624
2663
|
}, "cookieFingerprint");
|
|
2625
|
-
var
|
|
2664
|
+
var authIdentityRateLimitKey = /* @__PURE__ */ __name(async (ctx) => {
|
|
2626
2665
|
const ip = ctx.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? ctx.req.header("x-real-ip") ?? "unknown";
|
|
2627
2666
|
try {
|
|
2628
2667
|
const body = await ctx.req.json();
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2668
|
+
const identity = body?.identifier ?? body?.email;
|
|
2669
|
+
const normalizedIdentity = normalizeAuthIdentifier(identity);
|
|
2670
|
+
if (normalizedIdentity) {
|
|
2671
|
+
return `${ip}:${hashKeyPart(normalizedIdentity)}`;
|
|
2633
2672
|
}
|
|
2634
2673
|
} catch {
|
|
2635
2674
|
}
|
|
2636
2675
|
return ip;
|
|
2637
|
-
}, "
|
|
2676
|
+
}, "authIdentityRateLimitKey");
|
|
2638
2677
|
var AuthController = class AuthController2 {
|
|
2639
2678
|
static {
|
|
2640
2679
|
__name(this, "AuthController");
|
|
@@ -2681,7 +2720,7 @@ var AuthController = class AuthController2 {
|
|
|
2681
2720
|
};
|
|
2682
2721
|
__decorate15([
|
|
2683
2722
|
Post("/register"),
|
|
2684
|
-
RateLimit({ limit: 5, window: "15m", key:
|
|
2723
|
+
RateLimit({ limit: 5, window: "15m", key: authIdentityRateLimitKey }),
|
|
2685
2724
|
Validate(registerDto),
|
|
2686
2725
|
ResMsg("auth.success.register"),
|
|
2687
2726
|
__param5(0, Body()),
|
|
@@ -2691,7 +2730,7 @@ __decorate15([
|
|
|
2691
2730
|
], AuthController.prototype, "registerUser", null);
|
|
2692
2731
|
__decorate15([
|
|
2693
2732
|
Post("/login"),
|
|
2694
|
-
RateLimit({ limit: 5, window: "15m", key:
|
|
2733
|
+
RateLimit({ limit: 5, window: "15m", key: authIdentityRateLimitKey, message: "Too many login attempts. Please try again later." }),
|
|
2695
2734
|
Validate(loginDto),
|
|
2696
2735
|
ResMsg("auth.success.login"),
|
|
2697
2736
|
__param5(0, Body()),
|
|
@@ -2760,7 +2799,7 @@ __decorate15([
|
|
|
2760
2799
|
], AuthController.prototype, "userProfile", null);
|
|
2761
2800
|
__decorate15([
|
|
2762
2801
|
Post("/forgot-password"),
|
|
2763
|
-
RateLimit({ limit: 3, window: "15m", key:
|
|
2802
|
+
RateLimit({ limit: 3, window: "15m", key: authIdentityRateLimitKey, message: "Too many password reset requests. Please try again later." }),
|
|
2764
2803
|
Validate(resetPasswordDto),
|
|
2765
2804
|
ResMsg("auth.success.passwordResetSent"),
|
|
2766
2805
|
__param5(0, Body()),
|
|
@@ -5699,6 +5738,7 @@ export {
|
|
|
5699
5738
|
assignRoleDto,
|
|
5700
5739
|
assignRoleParams,
|
|
5701
5740
|
auth,
|
|
5741
|
+
authIdentityRateLimitKey,
|
|
5702
5742
|
authSchema,
|
|
5703
5743
|
authSeed,
|
|
5704
5744
|
avatarsPath,
|
|
@@ -5723,12 +5763,14 @@ export {
|
|
|
5723
5763
|
isAdmin,
|
|
5724
5764
|
isAdministrator,
|
|
5725
5765
|
isAuth,
|
|
5766
|
+
isEmailIdentifier,
|
|
5726
5767
|
isEmpty,
|
|
5727
5768
|
isFile,
|
|
5728
5769
|
isPath,
|
|
5729
5770
|
join2 as join,
|
|
5730
5771
|
languageParam,
|
|
5731
5772
|
loginDto,
|
|
5773
|
+
normalizeAuthIdentifier,
|
|
5732
5774
|
oauthAccountsTable,
|
|
5733
5775
|
own,
|
|
5734
5776
|
parseSchema,
|