najm-auth 2.0.11 → 2.0.13

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,52 @@ 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
+ // Or narrowly accept only an unverified pending account with one exact role.
750
+ const pendingSponsor = await authService.verifyPendingCredentials(
751
+ { identifier, password },
752
+ 'sponsor',
753
+ );
754
+
755
+ if (await appRequiresPasswordSetup(user.id)) {
756
+ // Revokes normal sessions and writes only an HttpOnly, SameSite=Strict,
757
+ // browser-session cookie. The database stores only its SHA-256 hash.
758
+ return credentialSetup.begin(user.id, options);
759
+ }
760
+
761
+ return authService.establishSession(user);
762
+
763
+ // Complete an app-owned mutation in the same transaction as one-time
764
+ // consumption. If the callback fails, token consumption rolls back.
765
+ await credentialSetup.consume(options, async ({ userId }) => {
766
+ await replaceApplicationCredential(userId, newCredential);
767
+ });
768
+ ```
769
+
770
+ Setup tokens are bound to a server-owned purpose, expire automatically, are
771
+ replaced when the same user starts that purpose again, and can be cancelled or
772
+ consumed exactly once. `require()` validates the current setup cookie without
773
+ consuming it; `cancel()` revokes it and clears the cookie.
774
+
775
+ ### Session Management
731
776
 
732
777
  - 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
778
  - 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;
@@ -1263,6 +1266,23 @@ 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
+ /**
1276
+ * Verify a pending, unverified account for one exact application role.
1277
+ * This deliberately does not establish a normal auth session. Applications
1278
+ * should exchange the result for a short-lived, purpose-bound setup session.
1279
+ */
1280
+ verifyPendingCredentials(body: LoginDto, expectedRole: string): Promise<SanitizedUser>;
1281
+ private verifyCredentialsForPolicy;
1282
+ /** Establish a complete normal auth session for an already verified user. */
1283
+ establishSession(user: SanitizedUser): Promise<TokenPair & {
1284
+ user: SanitizedUser;
1285
+ }>;
1266
1286
  refreshTokens(): Promise<TokenPair>;
1267
1287
  /**
1268
1288
  * Reissue the short-lived signed session snapshot from a fully validated
@@ -2195,6 +2215,74 @@ declare class UserController {
2195
2215
  removeRole(params: UserIdInParam): Promise<SanitizedUser>;
2196
2216
  }
2197
2217
 
2218
+ type NewSetupSession = {
2219
+ userId: string;
2220
+ purpose: string;
2221
+ tokenHash: string;
2222
+ expiresAt: string;
2223
+ };
2224
+ declare class CredentialSetupRepository {
2225
+ private db;
2226
+ private schema;
2227
+ private get sessions();
2228
+ replaceActive(data: NewSetupSession): Promise<any>;
2229
+ findActive(tokenHash: string, purpose: string): Promise<any>;
2230
+ consume(tokenHash: string, purpose: string): Promise<any>;
2231
+ revoke(tokenHash: string, purpose: string): Promise<any>;
2232
+ deleteExpired(): Promise<any>;
2233
+ }
2234
+
2235
+ interface CredentialSetupOptions {
2236
+ /** Stable server-owned purpose, for example `password-setup`. */
2237
+ purpose: string;
2238
+ /** HttpOnly browser-session cookie name (default: `najm.credential-setup`). */
2239
+ cookieName?: string;
2240
+ /** Session lifetime in milliseconds (default: 10 minutes, maximum: 24 hours). */
2241
+ ttlMs?: number;
2242
+ /** Cookie path (default: `/`). */
2243
+ cookiePath?: string;
2244
+ }
2245
+ interface CredentialSetupSessionInfo {
2246
+ userId: string;
2247
+ purpose: string;
2248
+ expiresAt: string;
2249
+ }
2250
+ interface CredentialSetupStarted {
2251
+ purpose: string;
2252
+ expiresAt: string;
2253
+ }
2254
+
2255
+ declare class CredentialSetupService {
2256
+ private readonly repository;
2257
+ private readonly tokens;
2258
+ private readonly authCookies;
2259
+ private readonly cookies;
2260
+ constructor(repository: CredentialSetupRepository, tokens: TokenService, authCookies: CookieManager, cookies: CookieService);
2261
+ /**
2262
+ * Revoke normal auth sessions and issue a single-purpose, short-lived,
2263
+ * browser-session cookie. No access or refresh token is minted.
2264
+ */
2265
+ begin(userId: string, options: CredentialSetupOptions): Promise<CredentialSetupStarted>;
2266
+ /** Validate and return the current active setup session without consuming it. */
2267
+ require(options: CredentialSetupOptions): Promise<CredentialSetupSessionInfo>;
2268
+ /**
2269
+ * Atomically consume the setup session and execute the application-owned
2270
+ * credential mutation in the same database transaction. If the callback
2271
+ * fails, consumption rolls back and the browser may safely retry.
2272
+ */
2273
+ consume<T>(options: CredentialSetupOptions, complete: (session: CredentialSetupSessionInfo) => Promise<T> | T): Promise<T>;
2274
+ cancel(options: CredentialSetupOptions): Promise<{
2275
+ cancelled: true;
2276
+ }>;
2277
+ pruneExpired(): Promise<void>;
2278
+ private resolveOptions;
2279
+ private hashToken;
2280
+ private setCookie;
2281
+ private clearCookie;
2282
+ }
2283
+
2284
+ declare const CREDENTIAL_SETUP_MODULE: readonly [typeof CredentialSetupRepository, typeof CredentialSetupService];
2285
+
2198
2286
  declare const USER_STATUS: readonly ["active", "inactive", "pending"];
2199
2287
  declare const TOKEN_STATUS: readonly ["active", "revoked", "expired"];
2200
2288
  declare const TOKEN_TYPE: readonly ["access", "refresh"];
@@ -2336,4 +2424,4 @@ declare const authSeed: (config: AuthSeedConfig) => Record<string, SeedEntry>;
2336
2424
  */
2337
2425
  declare function seedAuthData(config: SeedAuthDataConfig): Promise<SeedAuthDataResult>;
2338
2426
 
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 };
2427
+ 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 };