najm-auth 4.0.0 → 4.0.2
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 +19 -5
- package/dist/index.d.ts +17 -2
- package/dist/index.js +42 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -138,8 +138,9 @@ auth({
|
|
|
138
138
|
publicRegistration?: boolean // Default: true; mounts POST /auth/register
|
|
139
139
|
bcryptRounds?: number // Default: 10 (valid: 4-31)
|
|
140
140
|
|
|
141
|
-
// Frontend
|
|
142
|
-
frontendUrl?: string // Password reset link base URL
|
|
141
|
+
// Frontend
|
|
142
|
+
frontendUrl?: string // Password reset link base URL
|
|
143
|
+
appName?: string // Security email brand (default: 'Your app')
|
|
143
144
|
|
|
144
145
|
// Login identifier normalization (see "Identity presets")
|
|
145
146
|
identity?: {
|
|
@@ -1119,15 +1120,28 @@ errors cannot change the authentication result.
|
|
|
1119
1120
|
### Password Reset Tokens
|
|
1120
1121
|
|
|
1121
1122
|
Reset and invite links are signed JWTs whose `jti` is stored in the configured
|
|
1122
|
-
cache with the same expiry. `
|
|
1123
|
-
deletes that value
|
|
1124
|
-
|
|
1123
|
+
cache with the same expiry. `consumeSetPasswordToken()` atomically compares and
|
|
1124
|
+
deletes that value while preserving whether the token was a reset or invite;
|
|
1125
|
+
the backward-compatible `verifyResetToken()` returns only the user id. Exactly
|
|
1126
|
+
one concurrent caller can consume a link, and a stale link cannot delete the
|
|
1127
|
+
value for a newer one.
|
|
1125
1128
|
|
|
1126
1129
|
`AuthService.resetPassword()` validates the replacement password before
|
|
1127
1130
|
consumption. Once consumed, a token stays consumed even if the later user
|
|
1128
1131
|
mutation fails; restoring it would make the link replayable, so the user must
|
|
1129
1132
|
request a new one.
|
|
1130
1133
|
|
|
1134
|
+
Accepting an account invitation also marks the destination email verified and
|
|
1135
|
+
activates the account when its status is `pending`. An ordinary password reset
|
|
1136
|
+
changes neither verification nor lifecycle status, and an explicitly inactive
|
|
1137
|
+
invited account remains inactive.
|
|
1138
|
+
|
|
1139
|
+
Set `appName` on `auth()` to brand the invitation subject and email card. The
|
|
1140
|
+
provisioned role is presented as the account type, so a sponsor invitation can
|
|
1141
|
+
say “Activate your sponsor account” without application-owned HTML. The shared
|
|
1142
|
+
template uses inline critical styles for Gmail and keeps the raw token URL out
|
|
1143
|
+
of visible fallback copy.
|
|
1144
|
+
|
|
1131
1145
|
The built-in memory and Redis drivers implement the required atomic primitive.
|
|
1132
1146
|
A custom cache driver may omit `compareAndDelete()` for compatibility with
|
|
1133
1147
|
unrelated cache usage, but reset and invite consumption then fails closed. Do
|
package/dist/index.d.ts
CHANGED
|
@@ -198,6 +198,8 @@ interface AuthConfig {
|
|
|
198
198
|
defaultRole: string | null;
|
|
199
199
|
/** Frontend URL for password reset links (default: 'http://localhost:3000') */
|
|
200
200
|
frontendUrl: string;
|
|
201
|
+
/** Product name used in security email subjects and templates. */
|
|
202
|
+
appName: string;
|
|
201
203
|
/** Registration mode: 'active' auto-activates, 'pending' requires admin approval (default: 'active') */
|
|
202
204
|
registrationMode: 'active' | 'pending';
|
|
203
205
|
/** Whether the unauthenticated POST /auth/register route is mounted. */
|
|
@@ -265,6 +267,8 @@ type AuthPluginConfig = {
|
|
|
265
267
|
defaultRole?: string | null;
|
|
266
268
|
/** Frontend URL for password reset links. Falls back to FRONTEND_URL env var, then 'http://localhost:3000' */
|
|
267
269
|
frontendUrl?: string;
|
|
270
|
+
/** Product name used in account invitation emails (default: 'Your app'). */
|
|
271
|
+
appName?: string;
|
|
268
272
|
/** Registration mode: 'active' auto-activates new users, 'pending' requires admin approval (default: 'active') */
|
|
269
273
|
registrationMode?: 'active' | 'pending';
|
|
270
274
|
/**
|
|
@@ -432,7 +436,7 @@ var auth = {
|
|
|
432
436
|
subject: "Reset your password"
|
|
433
437
|
},
|
|
434
438
|
accountInvite: {
|
|
435
|
-
subject: "
|
|
439
|
+
subject: "{{appName}}: activate your {{accountLabel}}"
|
|
436
440
|
}
|
|
437
441
|
}
|
|
438
442
|
};
|
|
@@ -1233,6 +1237,11 @@ declare class CredentialSetupRequirementRepository {
|
|
|
1233
1237
|
complete(userId: string, purpose: string): Promise<CredentialSetupRequirementRow | undefined>;
|
|
1234
1238
|
}
|
|
1235
1239
|
|
|
1240
|
+
type SetPasswordTokenType = 'reset' | 'invite';
|
|
1241
|
+
interface ConsumedSetPasswordToken {
|
|
1242
|
+
userId: string;
|
|
1243
|
+
type: SetPasswordTokenType;
|
|
1244
|
+
}
|
|
1236
1245
|
declare class TokenService {
|
|
1237
1246
|
private tokenRepository;
|
|
1238
1247
|
private cookieManager;
|
|
@@ -1480,6 +1489,12 @@ declare class TokenService {
|
|
|
1480
1489
|
* request that then failed validation would cost the user their link for
|
|
1481
1490
|
* nothing.
|
|
1482
1491
|
*/
|
|
1492
|
+
consumeSetPasswordToken(token: string): Promise<ConsumedSetPasswordToken>;
|
|
1493
|
+
/**
|
|
1494
|
+
* Backward-compatible user-id-only reset/invite token consumption.
|
|
1495
|
+
* Prefer `consumeSetPasswordToken()` when the caller must distinguish an
|
|
1496
|
+
* account invitation from an ordinary password reset.
|
|
1497
|
+
*/
|
|
1483
1498
|
verifyResetToken(token: string): Promise<string>;
|
|
1484
1499
|
private getUserSessionVersion;
|
|
1485
1500
|
}
|
|
@@ -3037,4 +3052,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
|
|
|
3037
3052
|
*/
|
|
3038
3053
|
declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
|
|
3039
3054
|
|
|
3040
|
-
export { AUTH_CONFIG, AUTH_CORE_MODULE, en as AUTH_EN, AUTH_LOCALES, AUTH_LOGIN_RATE_LIMIT_ENV, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthLoginRateLimitConfig, type AuthPluginConfig, AuthQueries, type AuthRateLimitEnvironment, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, 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 CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_AUTH_LOGIN_RATE_LIMIT, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GitHubOAuthConfig, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PUBLIC_REGISTRATION_MODULE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, RegistrationController, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, 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, SessionInvalidationService, TOKEN_STATUS, TOKEN_TYPE, TemporaryCredentialInput, 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, authEmailRateLimitKey, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, resolveAuthLoginRateLimitConfig, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
|
|
3055
|
+
export { AUTH_CONFIG, AUTH_CORE_MODULE, en as AUTH_EN, AUTH_LOCALES, AUTH_LOGIN_RATE_LIMIT_ENV, AUTH_MODULE, AUTH_PERMISSIONS, AUTH_ROLE, AUTH_SCHEMA, AUTH_SUPPORTED_LANGUAGES, AUTH_USER, type AssignPermissionDto, type AssignRoleDto, type AssignRoleParams, type AuthConfig, AuthController, AuthGuard, type AuthLoginRateLimitConfig, type AuthPluginConfig, AuthQueries, type AuthRateLimitEnvironment, AuthResolver, type AuthSchema, type AuthSeedConfig, AuthService, AuthSessionService, type AuthUser, CREDENTIAL_SETUP_CODES, CREDENTIAL_SETUP_MODULE, Can, CanCreate, CanDelete, CanList, CanRead, CanUpdate, type ChainableGuard, type ChangePasswordDto, type CheckPermissionDto, type ConfiguredOwnership, type ConfirmResetPasswordDto, type ConsumedSetPasswordToken, CookieManager, type CreatePermissionDto, type CreateRoleDto, type CreateTokenDto, type CreateUserDto, type CredentialSetupChangeDto, type CredentialSetupCode, type CredentialSetupConfig, CredentialSetupController, type CredentialSetupOptions, type CredentialSetupPasswordOptions, type CredentialSetupPending, CredentialSetupRepository, CredentialSetupRequirementRepository, type CredentialSetupRequirementRow, CredentialSetupRequirementService, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, DEFAULT_AUTH_LOGIN_RATE_LIMIT, DEFAULT_CREDENTIAL_SETUP_COOKIE_NAME, DEFAULT_CREDENTIAL_SETUP_TTL_MS, type DefineRolesOptions, type EmailParam, EncryptionService, type GitHubOAuthConfig, type GoogleOAuthConfig, IdentityConfig, type IdentityResolver, type InviteUserDto, type JwtConfig, type JwtPayload, type LanguageParam, type LoginDto, type LoginResult, NewPermission, NewRoleEntity, NewUser, type OAuthConfig, type OAuthProvider, Owned, type OwnedMethods, type OwnershipConfig, type OwnershipProvider, type OwnershipRule, OwnershipToken, type OwnershipTokenOptions, PASSWORD_SETUP_PURPOSE, PUBLIC_REGISTRATION_MODULE, PasswordSetupService, Permission, PermissionController, PermissionGuard, type PermissionIdParam, PermissionRepository, PermissionService, PermissionValidator, Policy, type ProvisionUserInput, type ProvisionUserWithPasswordInput, type ProvisionUserWithSetupInput, ROLES, ROLE_GROUPS, type RefreshTokenDto, type RegisterDto, RegistrationController, type ResetPasswordDto, type ResolvedCredentialSetupConfig, ResolvedIdentityConfig, 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, SessionInvalidationService, type SetPasswordTokenType, TOKEN_STATUS, TOKEN_TYPE, TemporaryCredentialInput, 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, authEmailRateLimitKey, authIdentityRateLimitKey, authSeed, avatarsPath, calculateAge, calculateYearsOfExperience, changePasswordDto, checkPermissionDto, clean, configureOwnership, confirmResetPasswordDto, createIdentityResolver, createPermissionDto, createRoleDto, createTokenDto, createUserDto, credentialSetupChangeDto, credentialSetupError, defaultCredentialSetupPasswordSchema, defineRoles, emailParam, formatDate, getAuthLocale, getAvatarFile, inviteUserDto, isAdmin, isAdministrator, isAuth, isEmailIdentifier, isEmpty, isFile, isPath, join, languageParam, loginDto, normalizeAuthIdentifier, normalizeSetupPurpose, own, parseSchema, permissionIdParam, pickProps, refreshTokenDto, registerDto, resetPasswordDto, resolveAuthLoginRateLimitConfig, revokeTokenDto, roleIdParam, runAsUser, seedAuthData, tokenIdParam, updatePermissionDto, updateRoleDto, updateTokenDto, updateUserDto, userIdInParam, userIdParam, userListQuery, verifyTokenDto, where };
|
package/dist/index.js
CHANGED
|
@@ -2708,7 +2708,7 @@ var TokenService = class TokenService2 {
|
|
|
2708
2708
|
* request that then failed validation would cost the user their link for
|
|
2709
2709
|
* nothing.
|
|
2710
2710
|
*/
|
|
2711
|
-
async
|
|
2711
|
+
async consumeSetPasswordToken(token) {
|
|
2712
2712
|
let decoded;
|
|
2713
2713
|
try {
|
|
2714
2714
|
decoded = jwt.verify(token, this.config.jwt.refreshSecret);
|
|
@@ -2726,7 +2726,18 @@ var TokenService = class TokenService2 {
|
|
|
2726
2726
|
if (!await consume.call(this.cache, key, decoded.jti)) {
|
|
2727
2727
|
Err7(this.t("errors.invalidResetToken"));
|
|
2728
2728
|
}
|
|
2729
|
-
return
|
|
2729
|
+
return {
|
|
2730
|
+
userId: decoded.userId,
|
|
2731
|
+
type: decoded.type
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Backward-compatible user-id-only reset/invite token consumption.
|
|
2736
|
+
* Prefer `consumeSetPasswordToken()` when the caller must distinguish an
|
|
2737
|
+
* account invitation from an ordinary password reset.
|
|
2738
|
+
*/
|
|
2739
|
+
async verifyResetToken(token) {
|
|
2740
|
+
return (await this.consumeSetPasswordToken(token)).userId;
|
|
2730
2741
|
}
|
|
2731
2742
|
async getUserSessionVersion(userId) {
|
|
2732
2743
|
return this.invalidation.getSessionVersion(userId);
|
|
@@ -3477,9 +3488,16 @@ var AuthService = class AuthService2 {
|
|
|
3477
3488
|
});
|
|
3478
3489
|
const { token } = await this.tokenService.generateInviteToken(user.id);
|
|
3479
3490
|
const inviteLink = `${this.config.frontendUrl}/reset-password?token=${token}`;
|
|
3491
|
+
const accountType = body.role?.trim().toLowerCase();
|
|
3492
|
+
const accountLabel = accountType ? `${accountType} account` : "account";
|
|
3480
3493
|
let emailSent = false;
|
|
3481
3494
|
try {
|
|
3482
|
-
await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject"
|
|
3495
|
+
await this.emailService.sendHtml(body.email, this.t("emails.accountInvite.subject", {
|
|
3496
|
+
accountLabel,
|
|
3497
|
+
appName: this.config.appName
|
|
3498
|
+
}), accountInviteTemplate({
|
|
3499
|
+
accountType,
|
|
3500
|
+
appName: this.config.appName,
|
|
3483
3501
|
inviteLink,
|
|
3484
3502
|
userName: user.name || body.email
|
|
3485
3503
|
}));
|
|
@@ -3818,10 +3836,18 @@ var AuthService = class AuthService2 {
|
|
|
3818
3836
|
}
|
|
3819
3837
|
async resetPassword(token, newPassword) {
|
|
3820
3838
|
this.userValidator.validatePasswordStrength(newPassword);
|
|
3821
|
-
const
|
|
3822
|
-
await this.userService.
|
|
3823
|
-
|
|
3824
|
-
await this.
|
|
3839
|
+
const consumed = await this.tokenService.consumeSetPasswordToken(token);
|
|
3840
|
+
const user = await this.userService.getById(consumed.userId);
|
|
3841
|
+
const acceptsInvitation = consumed.type === "invite";
|
|
3842
|
+
await this.userService.update(consumed.userId, {
|
|
3843
|
+
password: newPassword,
|
|
3844
|
+
...acceptsInvitation ? {
|
|
3845
|
+
emailVerified: true,
|
|
3846
|
+
...user.status === "pending" ? { status: "active" } : {}
|
|
3847
|
+
} : {}
|
|
3848
|
+
});
|
|
3849
|
+
await this.tokenService.invalidateUserAccessTokens(consumed.userId);
|
|
3850
|
+
await this.tokenService.revokeAllForUser(consumed.userId);
|
|
3825
3851
|
this.cookieManager.clearRefreshToken();
|
|
3826
3852
|
this.cookieManager.clearSessionCookie();
|
|
3827
3853
|
return { message: this.t("success.passwordReset") };
|
|
@@ -6327,7 +6353,7 @@ var en_default = {
|
|
|
6327
6353
|
subject: "Reset your password"
|
|
6328
6354
|
},
|
|
6329
6355
|
accountInvite: {
|
|
6330
|
-
subject: "
|
|
6356
|
+
subject: "{{appName}}: activate your {{accountLabel}}"
|
|
6331
6357
|
}
|
|
6332
6358
|
}
|
|
6333
6359
|
},
|
|
@@ -7459,6 +7485,13 @@ var validateCallbackUrl = /* @__PURE__ */ __name((value, name) => {
|
|
|
7459
7485
|
}
|
|
7460
7486
|
return callback.toString();
|
|
7461
7487
|
}, "validateCallbackUrl");
|
|
7488
|
+
var resolveAppName = /* @__PURE__ */ __name((value) => {
|
|
7489
|
+
const appName = value?.trim() || "Your app";
|
|
7490
|
+
if (appName.length > 80 || /[\u0000-\u001f\u007f]/.test(appName)) {
|
|
7491
|
+
throw new Error("auth.appName must be at most 80 characters without control characters");
|
|
7492
|
+
}
|
|
7493
|
+
return appName;
|
|
7494
|
+
}, "resolveAppName");
|
|
7462
7495
|
var resolveGoogleConfig = /* @__PURE__ */ __name((config) => {
|
|
7463
7496
|
const configuredGoogle = config?.oauth?.google;
|
|
7464
7497
|
if (!configuredGoogle)
|
|
@@ -7535,6 +7568,7 @@ var resolveAuthConfig = /* @__PURE__ */ __name((config) => {
|
|
|
7535
7568
|
blacklistPrefix: config?.blacklistPrefix ?? "auth:blacklist:",
|
|
7536
7569
|
defaultRole: config?.defaultRole ?? null,
|
|
7537
7570
|
frontendUrl: config?.frontendUrl ?? process.env.FRONTEND_URL ?? "http://localhost:3000",
|
|
7571
|
+
appName: resolveAppName(config?.appName),
|
|
7538
7572
|
registrationMode: config?.registrationMode ?? "active",
|
|
7539
7573
|
publicRegistration: config?.publicRegistration ?? true,
|
|
7540
7574
|
requireVerifiedEmail: config?.requireVerifiedEmail ?? false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "najm-auth",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Authentication and authorization library for najm framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"najm-guard": "^2.0.2",
|
|
98
98
|
"najm-i18n": "^2.1.2",
|
|
99
99
|
"najm-cache": "^2.2.0",
|
|
100
|
-
"najm-email": "^2.0.
|
|
100
|
+
"najm-email": "^2.0.4",
|
|
101
101
|
"najm-rate": "^2.1.1",
|
|
102
102
|
"najm-validation": "^2.0.2",
|
|
103
103
|
"jsonwebtoken": "^9.0.3",
|