najm-auth 2.0.10 → 2.0.12

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 CHANGED
@@ -45,7 +45,7 @@ export const products = sqliteTable('products', {
45
45
 
46
46
  // Combined schema (always include authSchema)
47
47
  export const schema = {
48
- ...authSchema, // users, roles, permissions, tokens, rolePermissions
48
+ ...authSchema, // includes users, tokens, and credentialSetupSessions
49
49
  products,
50
50
  };
51
51
 
@@ -727,7 +727,46 @@ async resetPassword(token: string, newPassword: string) {
727
727
  }
728
728
  ```
729
729
 
730
- ### Session Management
730
+ ### Purpose-Bound Credential Setup
731
+
732
+ Use `CredentialSetupService` when valid credentials should open only a
733
+ short-lived setup flow, not a complete application session. The default auth
734
+ schema includes the durable `credential_setup_sessions` table for PostgreSQL
735
+ and SQLite; generate and apply a consumer migration after upgrading.
736
+
737
+ ```typescript
738
+ import { AuthService, CredentialSetupService } from 'najm-auth';
739
+
740
+ const options = {
741
+ purpose: 'password-setup',
742
+ cookieName: 'my-app.password-setup',
743
+ ttlMs: 10 * 60 * 1000,
744
+ };
745
+
746
+ // Verify the password without minting access/refresh tokens.
747
+ const user = await authService.verifyCredentials({ identifier, password });
748
+
749
+ if (await appRequiresPasswordSetup(user.id)) {
750
+ // Revokes normal sessions and writes only an HttpOnly, SameSite=Strict,
751
+ // browser-session cookie. The database stores only its SHA-256 hash.
752
+ return credentialSetup.begin(user.id, options);
753
+ }
754
+
755
+ return authService.establishSession(user);
756
+
757
+ // Complete an app-owned mutation in the same transaction as one-time
758
+ // consumption. If the callback fails, token consumption rolls back.
759
+ await credentialSetup.consume(options, async ({ userId }) => {
760
+ await replaceApplicationCredential(userId, newCredential);
761
+ });
762
+ ```
763
+
764
+ Setup tokens are bound to a server-owned purpose, expire automatically, are
765
+ replaced when the same user starts that purpose again, and can be cancelled or
766
+ consumed exactly once. `require()` validates the current setup cookie without
767
+ consuming it; `cancel()` revokes it and clears the cookie.
768
+
769
+ ### Session Management
731
770
 
732
771
  - Sessions are multi-device: the token table stores one refresh row per login session (keyed by a unique `tokenFamily`), so a user can stay logged in on several devices at once. Logout and rotation are scoped to the current session; password change/reset revoke every session
733
772
  - A stale refresh token presented after the 120-second rotation grace window revokes only that session's family as reuse protection
package/dist/index.d.ts CHANGED
@@ -6,11 +6,12 @@ import { EmailPluginConfig, EmailService } from 'najm-email';
6
6
  import { I18nService } from 'najm-i18n';
7
7
  import { TDb, SeedEntry } from 'najm-database';
8
8
  import { User, NewUser, RoleEntity, NewRoleEntity, Permission, NewPermission, RolePermission } from './schema/pg.js';
9
- export { NewOAuthAccount, NewRolePermission, NewToken, OAuthAccount, Token, authSchema, baseFields, oauthAccountsTable, permissionsTable, rolePermissionsTable, rolesTable, tokenStatusEnum, tokenTypeEnum, tokensTable, userStatusEnum, usersTable } from './schema/pg.js';
9
+ export { CredentialSetupSession, NewCredentialSetupSession, NewOAuthAccount, NewRolePermission, NewToken, OAuthAccount, Token, authSchema, baseFields, credentialSetupSessionsTable, oauthAccountsTable, permissionsTable, rolePermissionsTable, rolesTable, tokenStatusEnum, tokenTypeEnum, tokensTable, userStatusEnum, usersTable } from './schema/pg.js';
10
10
  import { CacheService } from 'najm-cache';
11
11
  import { z } from 'zod';
12
12
  import { Context } from 'hono';
13
13
  import { GuardResult } from 'najm-guard';
14
+ import { CookieService } from 'najm-cookies';
14
15
  import 'drizzle-orm';
15
16
  import 'drizzle-orm/pg-core';
16
17
 
@@ -130,6 +131,8 @@ interface AuthConfig {
130
131
  interface AuthSchema {
131
132
  users: any;
132
133
  tokens: any;
134
+ /** Durable one-time browser sessions used by CredentialSetupService. */
135
+ credentialSetupSessions?: any;
133
136
  roles: any;
134
137
  permissions: any;
135
138
  rolePermissions: any;
@@ -545,7 +548,7 @@ declare class UserRepository {
545
548
  private schema;
546
549
  private get users();
547
550
  private get roles();
548
- /** Shared query helper */
551
+ /** Shared query helper, scoped to the current database/transaction identity. */
549
552
  private queryHelper?;
550
553
  private get q();
551
554
  getAll(limit?: number, offset?: number): Promise<UserWithPermissions[]>;
@@ -847,7 +850,7 @@ declare class TokenRepository {
847
850
  private schema;
848
851
  private get tokens();
849
852
  private get users();
850
- /** Shared query helper */
853
+ /** Shared query helper, scoped to the current database/transaction identity. */
851
854
  private queryHelper?;
852
855
  private get q();
853
856
  /**
@@ -1263,6 +1266,16 @@ declare class AuthService {
1263
1266
  loginUser(body: LoginDto): Promise<TokenPair & {
1264
1267
  user: SanitizedUser;
1265
1268
  }>;
1269
+ /**
1270
+ * Verify credentials and account policy without minting access/refresh
1271
+ * tokens or writing normal auth cookies. Sensitive onboarding flows can use
1272
+ * this before issuing a purpose-bound CredentialSetupService session.
1273
+ */
1274
+ verifyCredentials(body: LoginDto): Promise<SanitizedUser>;
1275
+ /** Establish a complete normal auth session for an already verified user. */
1276
+ establishSession(user: SanitizedUser): Promise<TokenPair & {
1277
+ user: SanitizedUser;
1278
+ }>;
1266
1279
  refreshTokens(): Promise<TokenPair>;
1267
1280
  /**
1268
1281
  * Reissue the short-lived signed session snapshot from a fully validated
@@ -2195,6 +2208,74 @@ declare class UserController {
2195
2208
  removeRole(params: UserIdInParam): Promise<SanitizedUser>;
2196
2209
  }
2197
2210
 
2211
+ type NewSetupSession = {
2212
+ userId: string;
2213
+ purpose: string;
2214
+ tokenHash: string;
2215
+ expiresAt: string;
2216
+ };
2217
+ declare class CredentialSetupRepository {
2218
+ private db;
2219
+ private schema;
2220
+ private get sessions();
2221
+ replaceActive(data: NewSetupSession): Promise<any>;
2222
+ findActive(tokenHash: string, purpose: string): Promise<any>;
2223
+ consume(tokenHash: string, purpose: string): Promise<any>;
2224
+ revoke(tokenHash: string, purpose: string): Promise<any>;
2225
+ deleteExpired(): Promise<any>;
2226
+ }
2227
+
2228
+ interface CredentialSetupOptions {
2229
+ /** Stable server-owned purpose, for example `password-setup`. */
2230
+ purpose: string;
2231
+ /** HttpOnly browser-session cookie name (default: `najm.credential-setup`). */
2232
+ cookieName?: string;
2233
+ /** Session lifetime in milliseconds (default: 10 minutes, maximum: 24 hours). */
2234
+ ttlMs?: number;
2235
+ /** Cookie path (default: `/`). */
2236
+ cookiePath?: string;
2237
+ }
2238
+ interface CredentialSetupSessionInfo {
2239
+ userId: string;
2240
+ purpose: string;
2241
+ expiresAt: string;
2242
+ }
2243
+ interface CredentialSetupStarted {
2244
+ purpose: string;
2245
+ expiresAt: string;
2246
+ }
2247
+
2248
+ declare class CredentialSetupService {
2249
+ private readonly repository;
2250
+ private readonly tokens;
2251
+ private readonly authCookies;
2252
+ private readonly cookies;
2253
+ constructor(repository: CredentialSetupRepository, tokens: TokenService, authCookies: CookieManager, cookies: CookieService);
2254
+ /**
2255
+ * Revoke normal auth sessions and issue a single-purpose, short-lived,
2256
+ * browser-session cookie. No access or refresh token is minted.
2257
+ */
2258
+ begin(userId: string, options: CredentialSetupOptions): Promise<CredentialSetupStarted>;
2259
+ /** Validate and return the current active setup session without consuming it. */
2260
+ require(options: CredentialSetupOptions): Promise<CredentialSetupSessionInfo>;
2261
+ /**
2262
+ * Atomically consume the setup session and execute the application-owned
2263
+ * credential mutation in the same database transaction. If the callback
2264
+ * fails, consumption rolls back and the browser may safely retry.
2265
+ */
2266
+ consume<T>(options: CredentialSetupOptions, complete: (session: CredentialSetupSessionInfo) => Promise<T> | T): Promise<T>;
2267
+ cancel(options: CredentialSetupOptions): Promise<{
2268
+ cancelled: true;
2269
+ }>;
2270
+ pruneExpired(): Promise<void>;
2271
+ private resolveOptions;
2272
+ private hashToken;
2273
+ private setCookie;
2274
+ private clearCookie;
2275
+ }
2276
+
2277
+ declare const CREDENTIAL_SETUP_MODULE: readonly [typeof CredentialSetupRepository, typeof CredentialSetupService];
2278
+
2198
2279
  declare const USER_STATUS: readonly ["active", "inactive", "pending"];
2199
2280
  declare const TOKEN_STATUS: readonly ["active", "revoked", "expired"];
2200
2281
  declare const TOKEN_TYPE: readonly ["access", "refresh"];
@@ -2336,4 +2417,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
2336
2417
  */
2337
2418
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
2338
2419
 
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 };
2420
+ 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, 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 CredentialSetupOptions, CredentialSetupRepository, CredentialSetupService, type CredentialSetupSessionInfo, type CredentialSetupStarted, 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 };