najm-auth 4.0.6 → 4.0.7
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/index.d.ts +104 -10
- package/dist/index.js +178 -9
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1227,7 +1227,15 @@ declare class UserService {
|
|
|
1227
1227
|
create(data: Record<string, any>, options?: {
|
|
1228
1228
|
validatePasswordStrength?: boolean;
|
|
1229
1229
|
}): Promise<SanitizedUser>;
|
|
1230
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
* `options.validatePasswordStrength: false` is for a system-issued temporary
|
|
1232
|
+
* credential the user is durably required to replace — the same opt-out
|
|
1233
|
+
* `create()` already offers provisioning. A user-chosen password never takes
|
|
1234
|
+
* it.
|
|
1235
|
+
*/
|
|
1236
|
+
update(id: string, data: Record<string, any>, options?: {
|
|
1237
|
+
validatePasswordStrength?: boolean;
|
|
1238
|
+
}): Promise<SanitizedUser>;
|
|
1231
1239
|
delete(id: string): Promise<SanitizedUser>;
|
|
1232
1240
|
deleteAll(): Promise<SanitizedUser[]>;
|
|
1233
1241
|
getRoleName(id: string): Promise<string | null>;
|
|
@@ -1263,6 +1271,16 @@ interface ConsumedSetPasswordToken {
|
|
|
1263
1271
|
userId: string;
|
|
1264
1272
|
type: SetPasswordTokenType;
|
|
1265
1273
|
}
|
|
1274
|
+
/**
|
|
1275
|
+
* A freshly minted one-time set-password token. `jti` identifies this exact
|
|
1276
|
+
* token so a caller that fails to deliver it can discard it again without
|
|
1277
|
+
* touching a newer one.
|
|
1278
|
+
*/
|
|
1279
|
+
interface SetPasswordToken {
|
|
1280
|
+
token: string;
|
|
1281
|
+
userId: string;
|
|
1282
|
+
jti: string;
|
|
1283
|
+
}
|
|
1266
1284
|
declare class TokenService {
|
|
1267
1285
|
private tokenRepository;
|
|
1268
1286
|
private cookieManager;
|
|
@@ -1481,19 +1499,24 @@ declare class TokenService {
|
|
|
1481
1499
|
* Generate secure password reset token
|
|
1482
1500
|
* Returns both the plain token (to send via email) and userId for identification
|
|
1483
1501
|
*/
|
|
1484
|
-
generateResetToken(userId: string): Promise<
|
|
1485
|
-
token: string;
|
|
1486
|
-
userId: string;
|
|
1487
|
-
}>;
|
|
1502
|
+
generateResetToken(userId: string): Promise<SetPasswordToken>;
|
|
1488
1503
|
/**
|
|
1489
1504
|
* Generate secure account-invite token.
|
|
1490
1505
|
* Longer expiry (3d) than reset because an invited user may not check
|
|
1491
1506
|
* their email immediately. Consumed via the same reset-password endpoint.
|
|
1492
1507
|
*/
|
|
1493
|
-
generateInviteToken(userId: string): Promise<
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1508
|
+
generateInviteToken(userId: string): Promise<SetPasswordToken>;
|
|
1509
|
+
/**
|
|
1510
|
+
* Discard a set-password token this process just minted, identified by the
|
|
1511
|
+
* `jti` its generator returned. For the caller whose email send failed:
|
|
1512
|
+
* minting already superseded any earlier link for that user, so leaving the
|
|
1513
|
+
* fresh one live would keep a link alive that nobody received.
|
|
1514
|
+
*
|
|
1515
|
+
* Compare-and-delete, never a blind delete — a newer link minted in the
|
|
1516
|
+
* meantime must survive a late failure from an older send. Returns whether
|
|
1517
|
+
* this exact token was still the live one.
|
|
1518
|
+
*/
|
|
1519
|
+
discardSetPasswordToken(userId: string, jti: string): Promise<boolean>;
|
|
1497
1520
|
/**
|
|
1498
1521
|
* Verify and CONSUME a password reset or invite token.
|
|
1499
1522
|
*
|
|
@@ -1782,6 +1805,20 @@ type ProvisionUserWithPasswordInput = ProvisionUserInput & {
|
|
|
1782
1805
|
temporaryCredential?: never;
|
|
1783
1806
|
requireCredentialSetup?: never;
|
|
1784
1807
|
};
|
|
1808
|
+
/** Outcome of an administrative reset to a system-issued temporary credential. */
|
|
1809
|
+
type TemporaryCredentialReset = {
|
|
1810
|
+
userId: string;
|
|
1811
|
+
purpose: typeof PASSWORD_SETUP_PURPOSE;
|
|
1812
|
+
temporaryCredentialKind: string;
|
|
1813
|
+
};
|
|
1814
|
+
/**
|
|
1815
|
+
* Outcome of an administrative mail-out. `emailSent` is what the provider
|
|
1816
|
+
* actually reported — never an assumption that sending succeeded.
|
|
1817
|
+
*/
|
|
1818
|
+
type AdministrativeDelivery = {
|
|
1819
|
+
userId: string;
|
|
1820
|
+
emailSent: boolean;
|
|
1821
|
+
};
|
|
1785
1822
|
/** Login answer: either a complete session, or a pending credential setup. */
|
|
1786
1823
|
type LoginResult = (TokenPair & {
|
|
1787
1824
|
nextStep: 'authenticated';
|
|
@@ -1820,6 +1857,14 @@ declare class AuthService {
|
|
|
1820
1857
|
inviteUser(body: ProvisionUserInput): Promise<SanitizedUser & {
|
|
1821
1858
|
emailSent: boolean;
|
|
1822
1859
|
}>;
|
|
1860
|
+
/**
|
|
1861
|
+
* Mint an invite token and send the activation mail. Shared by first-time
|
|
1862
|
+
* invitation and re-invitation, so both rest on one token contract and one
|
|
1863
|
+
* template and neither can drift into an ad hoc message.
|
|
1864
|
+
*
|
|
1865
|
+
* Nothing here logs the token, the link, the message body, or the recipient.
|
|
1866
|
+
*/
|
|
1867
|
+
private deliverInvitation;
|
|
1823
1868
|
/**
|
|
1824
1869
|
* Create a login for a person record. The branch is intentional and is the
|
|
1825
1870
|
* single rule callers rely on:
|
|
@@ -1910,6 +1955,55 @@ declare class AuthService {
|
|
|
1910
1955
|
resetPassword(token: string, newPassword: string): Promise<{
|
|
1911
1956
|
message: string;
|
|
1912
1957
|
}>;
|
|
1958
|
+
/**
|
|
1959
|
+
* Replace an existing account's stored credential with a system-issued
|
|
1960
|
+
* temporary one and durably require the holder to replace it at their next
|
|
1961
|
+
* login.
|
|
1962
|
+
*
|
|
1963
|
+
* The hash write and the durable requirement commit together, so no failure
|
|
1964
|
+
* can leave the temporary credential accepted with nothing forcing its
|
|
1965
|
+
* replacement, nor the requirement standing over an unchanged password.
|
|
1966
|
+
* Session revocation runs inside that same transaction: a cache or session
|
|
1967
|
+
* failure rolls the credential back rather than reporting a reset that a
|
|
1968
|
+
* still-live browser could sail past. No session is issued.
|
|
1969
|
+
*
|
|
1970
|
+
* Strength validation is deliberately skipped — the value is issued by the
|
|
1971
|
+
* system, not chosen by the user — but bcrypt's 72-byte boundary is not.
|
|
1972
|
+
*/
|
|
1973
|
+
resetToTemporaryCredential(userId: string, credential: TemporaryCredentialInput): Promise<TemporaryCredentialReset>;
|
|
1974
|
+
/**
|
|
1975
|
+
* Send one password-reset link to an account selected by id. The recipient is
|
|
1976
|
+
* read from that account at command time, so neither an administrator nor a
|
|
1977
|
+
* stale client can redirect the link by supplying an address.
|
|
1978
|
+
*
|
|
1979
|
+
* Delivery is reported truthfully: unlike `forgotPassword` there is no email
|
|
1980
|
+
* enumeration to protect against, because the caller already knows the
|
|
1981
|
+
* account exists. Account status and email verification are left exactly as
|
|
1982
|
+
* they were, and requesting the link does not end the user's current session
|
|
1983
|
+
* — `resetPassword` revokes it when the new password is actually saved.
|
|
1984
|
+
*
|
|
1985
|
+
* Minting supersedes any earlier link for this user. A failed send discards
|
|
1986
|
+
* the fresh token too, so a failure never leaves a live link nobody received.
|
|
1987
|
+
*/
|
|
1988
|
+
sendPasswordReset(userId: string): Promise<AdministrativeDelivery>;
|
|
1989
|
+
/**
|
|
1990
|
+
* Re-send the activation link for an account that is still pending.
|
|
1991
|
+
*
|
|
1992
|
+
* It creates no second user and no second profile — that is the whole reason
|
|
1993
|
+
* it exists beside `inviteUser`, which does create one. Only a `pending`
|
|
1994
|
+
* account qualifies: an active or inactive account is reset or reactivated,
|
|
1995
|
+
* never re-invited. Whether a given pending account is genuinely an invited
|
|
1996
|
+
* one rather than an application awaiting a decision is the caller's to
|
|
1997
|
+
* decide; this package cannot see an application.
|
|
1998
|
+
*/
|
|
1999
|
+
resendInvitation(userId: string): Promise<AdministrativeDelivery>;
|
|
2000
|
+
/**
|
|
2001
|
+
* Discard a link that was minted but never delivered. A cache that cannot
|
|
2002
|
+
* consume atomically is reported rather than pretended away; the caller has
|
|
2003
|
+
* already been told the mail did not leave, so the truthful result stands
|
|
2004
|
+
* either way.
|
|
2005
|
+
*/
|
|
2006
|
+
private discardUndeliveredToken;
|
|
1913
2007
|
}
|
|
1914
2008
|
|
|
1915
2009
|
/**
|
|
@@ -3073,4 +3167,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
|
|
|
3073
3167
|
*/
|
|
3074
3168
|
declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
|
|
3075
3169
|
|
|
3076
|
-
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 };
|
|
3170
|
+
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 AdministrativeDelivery, 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 SetPasswordToken, type SetPasswordTokenType, TOKEN_STATUS, TOKEN_TYPE, TemporaryCredentialInput, type TemporaryCredentialReset, 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
|
@@ -1916,12 +1916,22 @@ var UserService = class UserService2 {
|
|
|
1916
1916
|
const newUser = await this.userRepository.create(userDetails);
|
|
1917
1917
|
return this.sanitizeUser(newUser);
|
|
1918
1918
|
}
|
|
1919
|
-
|
|
1919
|
+
/**
|
|
1920
|
+
* `options.validatePasswordStrength: false` is for a system-issued temporary
|
|
1921
|
+
* credential the user is durably required to replace — the same opt-out
|
|
1922
|
+
* `create()` already offers provisioning. A user-chosen password never takes
|
|
1923
|
+
* it.
|
|
1924
|
+
*/
|
|
1925
|
+
async update(id, data, options = {}) {
|
|
1920
1926
|
const { password, image } = data;
|
|
1921
1927
|
await this.userValidator.checkEmailUnique(data.email, id);
|
|
1922
1928
|
let hashedPassword;
|
|
1923
1929
|
if (password) {
|
|
1924
|
-
|
|
1930
|
+
if (options.validatePasswordStrength === false) {
|
|
1931
|
+
this.userValidator.validatePasswordLength(password);
|
|
1932
|
+
} else {
|
|
1933
|
+
this.userValidator.validatePasswordStrength(password);
|
|
1934
|
+
}
|
|
1925
1935
|
hashedPassword = await this.encryptionService.hashPassword(password);
|
|
1926
1936
|
}
|
|
1927
1937
|
const updateData = {
|
|
@@ -2711,7 +2721,7 @@ var TokenService = class TokenService2 {
|
|
|
2711
2721
|
expiresIn
|
|
2712
2722
|
});
|
|
2713
2723
|
await this.cache.set(`${this.resetTokenPrefix}${userId}`, jti, timestring3(expiresIn, "ms"));
|
|
2714
|
-
return { token, userId };
|
|
2724
|
+
return { token, userId, jti };
|
|
2715
2725
|
}
|
|
2716
2726
|
/**
|
|
2717
2727
|
* Generate secure password reset token
|
|
@@ -2728,6 +2738,23 @@ var TokenService = class TokenService2 {
|
|
|
2728
2738
|
async generateInviteToken(userId) {
|
|
2729
2739
|
return this.generateSetPasswordToken(userId, "invite", "3d");
|
|
2730
2740
|
}
|
|
2741
|
+
/**
|
|
2742
|
+
* Discard a set-password token this process just minted, identified by the
|
|
2743
|
+
* `jti` its generator returned. For the caller whose email send failed:
|
|
2744
|
+
* minting already superseded any earlier link for that user, so leaving the
|
|
2745
|
+
* fresh one live would keep a link alive that nobody received.
|
|
2746
|
+
*
|
|
2747
|
+
* Compare-and-delete, never a blind delete — a newer link minted in the
|
|
2748
|
+
* meantime must survive a late failure from an older send. Returns whether
|
|
2749
|
+
* this exact token was still the live one.
|
|
2750
|
+
*/
|
|
2751
|
+
async discardSetPasswordToken(userId, jti) {
|
|
2752
|
+
const consume = this.cache.compareAndDelete;
|
|
2753
|
+
if (typeof consume !== "function") {
|
|
2754
|
+
Err7.invalidOperation("Discarding a set-password token requires a cache with atomic compare-and-delete");
|
|
2755
|
+
}
|
|
2756
|
+
return consume.call(this.cache, `${this.resetTokenPrefix}${userId}`, jti);
|
|
2757
|
+
}
|
|
2731
2758
|
/**
|
|
2732
2759
|
* Verify and CONSUME a password reset or invite token.
|
|
2733
2760
|
*
|
|
@@ -3444,6 +3471,7 @@ var _h2;
|
|
|
3444
3471
|
var _j2;
|
|
3445
3472
|
var _k;
|
|
3446
3473
|
var _l;
|
|
3474
|
+
var _m;
|
|
3447
3475
|
var AuthService = class AuthService2 {
|
|
3448
3476
|
static {
|
|
3449
3477
|
__name(this, "AuthService");
|
|
@@ -3522,16 +3550,27 @@ var AuthService = class AuthService2 {
|
|
|
3522
3550
|
status: body.status ?? "active",
|
|
3523
3551
|
emailVerified: false
|
|
3524
3552
|
});
|
|
3525
|
-
const {
|
|
3553
|
+
const { emailSent } = await this.deliverInvitation(user.id, body.email, user.name, body.role);
|
|
3554
|
+
return { ...user, emailSent };
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Mint an invite token and send the activation mail. Shared by first-time
|
|
3558
|
+
* invitation and re-invitation, so both rest on one token contract and one
|
|
3559
|
+
* template and neither can drift into an ad hoc message.
|
|
3560
|
+
*
|
|
3561
|
+
* Nothing here logs the token, the link, the message body, or the recipient.
|
|
3562
|
+
*/
|
|
3563
|
+
async deliverInvitation(userId, email2, userName, role) {
|
|
3564
|
+
const { token, jti } = await this.tokenService.generateInviteToken(userId);
|
|
3526
3565
|
const inviteLink = `${this.config.frontendUrl}/reset-password?token=${token}`;
|
|
3527
|
-
const accountType =
|
|
3566
|
+
const accountType = role?.trim().toLowerCase() || void 0;
|
|
3528
3567
|
const accountLabel = accountType ? `${accountType} account` : "account";
|
|
3529
3568
|
let emailSent = false;
|
|
3530
3569
|
try {
|
|
3531
3570
|
const logo = this.config.accountInviteLogo;
|
|
3532
3571
|
const logoCid = logo ? "najm-account-invite-logo" : void 0;
|
|
3533
3572
|
const result = await this.emailService.send({
|
|
3534
|
-
to:
|
|
3573
|
+
to: email2,
|
|
3535
3574
|
subject: this.t("emails.accountInvite.subject", {
|
|
3536
3575
|
accountLabel,
|
|
3537
3576
|
appName: this.config.appName
|
|
@@ -3542,7 +3581,7 @@ var AuthService = class AuthService2 {
|
|
|
3542
3581
|
inviteLink,
|
|
3543
3582
|
logoAlt: logo?.alt,
|
|
3544
3583
|
logoSrc: logoCid ? `cid:${logoCid}` : void 0,
|
|
3545
|
-
userName:
|
|
3584
|
+
userName: userName || email2
|
|
3546
3585
|
}),
|
|
3547
3586
|
attachments: logo ? [{
|
|
3548
3587
|
filename: logo.filename,
|
|
@@ -3555,9 +3594,9 @@ var AuthService = class AuthService2 {
|
|
|
3555
3594
|
});
|
|
3556
3595
|
emailSent = result.success;
|
|
3557
3596
|
} catch (error) {
|
|
3558
|
-
this.logger.warn("Account invite email failed", {
|
|
3597
|
+
this.logger.warn("Account invite email failed", { userId, error });
|
|
3559
3598
|
}
|
|
3560
|
-
return {
|
|
3599
|
+
return { emailSent, jti };
|
|
3561
3600
|
}
|
|
3562
3601
|
/**
|
|
3563
3602
|
* Create a login for a person record. The branch is intentional and is the
|
|
@@ -3904,6 +3943,130 @@ var AuthService = class AuthService2 {
|
|
|
3904
3943
|
this.cookieManager.clearSessionCookie();
|
|
3905
3944
|
return { message: this.t("success.passwordReset") };
|
|
3906
3945
|
}
|
|
3946
|
+
// ==========================================================================
|
|
3947
|
+
// Administrative recovery for an account that already exists
|
|
3948
|
+
//
|
|
3949
|
+
// Three operations an application's own admin surface composes. Each is
|
|
3950
|
+
// bound to a user id, never to a submitted email; none creates a user,
|
|
3951
|
+
// issues a session, or returns a credential, token, or link. Who may call
|
|
3952
|
+
// them, which targets are eligible, how often, and what is audited belong to
|
|
3953
|
+
// the application — this package owns only the credential, token, and
|
|
3954
|
+
// session mechanics underneath.
|
|
3955
|
+
// ==========================================================================
|
|
3956
|
+
/**
|
|
3957
|
+
* Replace an existing account's stored credential with a system-issued
|
|
3958
|
+
* temporary one and durably require the holder to replace it at their next
|
|
3959
|
+
* login.
|
|
3960
|
+
*
|
|
3961
|
+
* The hash write and the durable requirement commit together, so no failure
|
|
3962
|
+
* can leave the temporary credential accepted with nothing forcing its
|
|
3963
|
+
* replacement, nor the requirement standing over an unchanged password.
|
|
3964
|
+
* Session revocation runs inside that same transaction: a cache or session
|
|
3965
|
+
* failure rolls the credential back rather than reporting a reset that a
|
|
3966
|
+
* still-live browser could sail past. No session is issued.
|
|
3967
|
+
*
|
|
3968
|
+
* Strength validation is deliberately skipped — the value is issued by the
|
|
3969
|
+
* system, not chosen by the user — but bcrypt's 72-byte boundary is not.
|
|
3970
|
+
*/
|
|
3971
|
+
async resetToTemporaryCredential(userId, credential) {
|
|
3972
|
+
if (!this.credentialSetupRequirements) {
|
|
3973
|
+
Err12.invalidOperation("Credential setup is unavailable: CredentialSetupRequirementService is not registered");
|
|
3974
|
+
}
|
|
3975
|
+
const user = await this.userService.getById(userId);
|
|
3976
|
+
const temporary = toTemporaryCredential(credential);
|
|
3977
|
+
const kind = resolveTemporaryCredentialKind(temporary.kind);
|
|
3978
|
+
if (kind.isTemporaryShape && !kind.isTemporaryShape(temporary.value)) {
|
|
3979
|
+
Err12(`Invalid temporary credential for kind '${kind.name}'`, 400);
|
|
3980
|
+
}
|
|
3981
|
+
const password = kind.normalize(temporary.value);
|
|
3982
|
+
if (!password?.trim()) {
|
|
3983
|
+
Err12("resetToTemporaryCredential requires a non-empty temporaryCredential", 400);
|
|
3984
|
+
}
|
|
3985
|
+
await this.userService.update(user.id, { password }, { validatePasswordStrength: false });
|
|
3986
|
+
await this.credentialSetupRequirements.markRequired(user.id, PASSWORD_SETUP_PURPOSE, {
|
|
3987
|
+
temporaryCredentialKind: kind.name
|
|
3988
|
+
});
|
|
3989
|
+
return {
|
|
3990
|
+
userId: user.id,
|
|
3991
|
+
purpose: PASSWORD_SETUP_PURPOSE,
|
|
3992
|
+
temporaryCredentialKind: kind.name
|
|
3993
|
+
};
|
|
3994
|
+
}
|
|
3995
|
+
/**
|
|
3996
|
+
* Send one password-reset link to an account selected by id. The recipient is
|
|
3997
|
+
* read from that account at command time, so neither an administrator nor a
|
|
3998
|
+
* stale client can redirect the link by supplying an address.
|
|
3999
|
+
*
|
|
4000
|
+
* Delivery is reported truthfully: unlike `forgotPassword` there is no email
|
|
4001
|
+
* enumeration to protect against, because the caller already knows the
|
|
4002
|
+
* account exists. Account status and email verification are left exactly as
|
|
4003
|
+
* they were, and requesting the link does not end the user's current session
|
|
4004
|
+
* — `resetPassword` revokes it when the new password is actually saved.
|
|
4005
|
+
*
|
|
4006
|
+
* Minting supersedes any earlier link for this user. A failed send discards
|
|
4007
|
+
* the fresh token too, so a failure never leaves a live link nobody received.
|
|
4008
|
+
*/
|
|
4009
|
+
async sendPasswordReset(userId) {
|
|
4010
|
+
const user = await this.userService.getById(userId);
|
|
4011
|
+
const email2 = typeof user.email === "string" ? user.email.trim() : "";
|
|
4012
|
+
if (!email2) {
|
|
4013
|
+
Err12("This account has no email address to send a password reset to", 409);
|
|
4014
|
+
}
|
|
4015
|
+
const { token, jti } = await this.tokenService.generateResetToken(user.id);
|
|
4016
|
+
const resetLink = `${this.config.frontendUrl}/reset-password?token=${token}`;
|
|
4017
|
+
let emailSent = false;
|
|
4018
|
+
try {
|
|
4019
|
+
const result = await this.emailService.sendHtml(email2, this.t("emails.passwordReset.subject"), passwordResetTemplate({
|
|
4020
|
+
resetLink,
|
|
4021
|
+
userName: user.name || email2
|
|
4022
|
+
}));
|
|
4023
|
+
emailSent = result.success;
|
|
4024
|
+
} catch (error) {
|
|
4025
|
+
this.logger.warn("Administrative password reset email failed", { userId: user.id, error });
|
|
4026
|
+
}
|
|
4027
|
+
if (!emailSent) {
|
|
4028
|
+
await this.discardUndeliveredToken(user.id, jti);
|
|
4029
|
+
}
|
|
4030
|
+
return { userId: user.id, emailSent };
|
|
4031
|
+
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Re-send the activation link for an account that is still pending.
|
|
4034
|
+
*
|
|
4035
|
+
* It creates no second user and no second profile — that is the whole reason
|
|
4036
|
+
* it exists beside `inviteUser`, which does create one. Only a `pending`
|
|
4037
|
+
* account qualifies: an active or inactive account is reset or reactivated,
|
|
4038
|
+
* never re-invited. Whether a given pending account is genuinely an invited
|
|
4039
|
+
* one rather than an application awaiting a decision is the caller's to
|
|
4040
|
+
* decide; this package cannot see an application.
|
|
4041
|
+
*/
|
|
4042
|
+
async resendInvitation(userId) {
|
|
4043
|
+
const user = await this.userService.getById(userId);
|
|
4044
|
+
if (user.status !== "pending") {
|
|
4045
|
+
Err12("Only a pending account can be re-invited", 409);
|
|
4046
|
+
}
|
|
4047
|
+
const email2 = typeof user.email === "string" ? user.email.trim() : "";
|
|
4048
|
+
if (!email2) {
|
|
4049
|
+
Err12("This account has no email address to send an invitation to", 409);
|
|
4050
|
+
}
|
|
4051
|
+
const { emailSent, jti } = await this.deliverInvitation(user.id, email2, user.name, user.role);
|
|
4052
|
+
if (!emailSent) {
|
|
4053
|
+
await this.discardUndeliveredToken(user.id, jti);
|
|
4054
|
+
}
|
|
4055
|
+
return { userId: user.id, emailSent };
|
|
4056
|
+
}
|
|
4057
|
+
/**
|
|
4058
|
+
* Discard a link that was minted but never delivered. A cache that cannot
|
|
4059
|
+
* consume atomically is reported rather than pretended away; the caller has
|
|
4060
|
+
* already been told the mail did not leave, so the truthful result stands
|
|
4061
|
+
* either way.
|
|
4062
|
+
*/
|
|
4063
|
+
async discardUndeliveredToken(userId, jti) {
|
|
4064
|
+
try {
|
|
4065
|
+
await this.tokenService.discardSetPasswordToken(userId, jti);
|
|
4066
|
+
} catch (error) {
|
|
4067
|
+
this.logger.warn("Undelivered set-password token could not be discarded", { userId, error });
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
3907
4070
|
};
|
|
3908
4071
|
__decorate18([
|
|
3909
4072
|
Inject12(AUTH_CONFIG),
|
|
@@ -3923,6 +4086,12 @@ __decorate18([
|
|
|
3923
4086
|
__metadata18("design:paramtypes", [Object]),
|
|
3924
4087
|
__metadata18("design:returntype", typeof (_l = typeof Promise !== "undefined" && Promise) === "function" ? _l : Object)
|
|
3925
4088
|
], AuthService.prototype, "provisionWithCredentialSetup", null);
|
|
4089
|
+
__decorate18([
|
|
4090
|
+
Transaction4(),
|
|
4091
|
+
__metadata18("design:type", Function),
|
|
4092
|
+
__metadata18("design:paramtypes", [String, Object]),
|
|
4093
|
+
__metadata18("design:returntype", typeof (_m = typeof Promise !== "undefined" && Promise) === "function" ? _m : Object)
|
|
4094
|
+
], AuthService.prototype, "resetToTemporaryCredential", null);
|
|
3926
4095
|
AuthService = __decorate18([
|
|
3927
4096
|
Injectable12(),
|
|
3928
4097
|
__metadata18("design:paramtypes", [typeof (_a12 = typeof TokenService !== "undefined" && TokenService) === "function" ? _a12 : Object, typeof (_b10 = typeof UserService !== "undefined" && UserService) === "function" ? _b10 : Object, typeof (_c7 = typeof UserValidator !== "undefined" && UserValidator) === "function" ? _c7 : Object, typeof (_d6 = typeof EncryptionService !== "undefined" && EncryptionService) === "function" ? _d6 : Object, typeof (_e5 = typeof CookieManager !== "undefined" && CookieManager) === "function" ? _e5 : Object, typeof (_f4 = typeof I18nService2 !== "undefined" && I18nService2) === "function" ? _f4 : Object, typeof (_g3 = typeof EmailService !== "undefined" && EmailService) === "function" ? _g3 : Object, typeof (_h2 = typeof AuthSessionService !== "undefined" && AuthSessionService) === "function" ? _h2 : Object, typeof (_j2 = typeof CredentialSetupRequirementService !== "undefined" && CredentialSetupRequirementService) === "function" ? _j2 : Object, typeof (_k = typeof PasswordSetupService !== "undefined" && PasswordSetupService) === "function" ? _k : Object])
|