@technomoron/api-server-base 2.0.0-beta.2 → 2.0.0-beta.20

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.
Files changed (115) hide show
  1. package/README.txt +81 -28
  2. package/dist/cjs/api-module.cjs +9 -0
  3. package/dist/cjs/api-module.d.ts +7 -4
  4. package/dist/cjs/api-server-base.cjs +607 -99
  5. package/dist/cjs/api-server-base.d.ts +80 -23
  6. package/dist/cjs/auth-api/auth-module.d.ts +23 -3
  7. package/dist/cjs/auth-api/auth-module.js +320 -124
  8. package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
  9. package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
  10. package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
  11. package/dist/cjs/auth-api/mem-auth-store.js +14 -28
  12. package/dist/cjs/auth-api/module.d.ts +1 -1
  13. package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
  14. package/dist/cjs/auth-api/sql-auth-store.js +43 -30
  15. package/dist/cjs/auth-api/storage.d.ts +6 -4
  16. package/dist/cjs/auth-api/storage.js +15 -5
  17. package/dist/cjs/auth-api/types.d.ts +7 -2
  18. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  19. package/dist/cjs/auth-api/user-id.js +38 -0
  20. package/dist/cjs/auth-cookie-options.d.ts +11 -0
  21. package/dist/cjs/auth-cookie-options.js +66 -0
  22. package/dist/cjs/index.cjs +4 -14
  23. package/dist/cjs/index.d.ts +4 -9
  24. package/dist/cjs/oauth/memory.d.ts +6 -0
  25. package/dist/cjs/oauth/memory.js +44 -11
  26. package/dist/cjs/oauth/models.d.ts +7 -2
  27. package/dist/cjs/oauth/models.js +10 -21
  28. package/dist/cjs/oauth/sequelize.d.ts +10 -48
  29. package/dist/cjs/oauth/sequelize.js +44 -99
  30. package/dist/cjs/oauth/types.d.ts +1 -0
  31. package/dist/cjs/passkey/base.d.ts +2 -0
  32. package/dist/cjs/passkey/config.d.ts +2 -0
  33. package/dist/cjs/passkey/config.js +26 -0
  34. package/dist/cjs/passkey/memory.d.ts +8 -0
  35. package/dist/cjs/passkey/memory.js +57 -16
  36. package/dist/cjs/passkey/models.d.ts +13 -4
  37. package/dist/cjs/passkey/models.js +41 -14
  38. package/dist/cjs/passkey/sequelize.d.ts +13 -25
  39. package/dist/cjs/passkey/sequelize.js +68 -153
  40. package/dist/cjs/passkey/service.d.ts +6 -2
  41. package/dist/cjs/passkey/service.js +205 -27
  42. package/dist/cjs/passkey/types.d.ts +18 -9
  43. package/dist/cjs/sequelize-utils.d.ts +8 -0
  44. package/dist/cjs/sequelize-utils.js +57 -0
  45. package/dist/cjs/token/base.d.ts +2 -1
  46. package/dist/cjs/token/base.js +3 -1
  47. package/dist/cjs/token/memory.d.ts +10 -0
  48. package/dist/cjs/token/memory.js +122 -32
  49. package/dist/cjs/token/sequelize.d.ts +4 -4
  50. package/dist/cjs/token/sequelize.js +67 -85
  51. package/dist/cjs/token/types.d.ts +8 -1
  52. package/dist/cjs/user/base.d.ts +1 -0
  53. package/dist/cjs/user/base.js +11 -4
  54. package/dist/cjs/user/memory.d.ts +2 -0
  55. package/dist/cjs/user/memory.js +9 -10
  56. package/dist/cjs/user/sequelize.d.ts +7 -2
  57. package/dist/cjs/user/sequelize.js +19 -32
  58. package/dist/esm/api-module.d.ts +7 -4
  59. package/dist/esm/api-module.js +9 -0
  60. package/dist/esm/api-server-base.d.ts +80 -23
  61. package/dist/esm/api-server-base.js +608 -100
  62. package/dist/esm/auth-api/auth-module.d.ts +23 -3
  63. package/dist/esm/auth-api/auth-module.js +321 -125
  64. package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
  65. package/dist/esm/auth-api/compat-auth-storage.js +13 -1
  66. package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
  67. package/dist/esm/auth-api/mem-auth-store.js +14 -28
  68. package/dist/esm/auth-api/module.d.ts +1 -1
  69. package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
  70. package/dist/esm/auth-api/sql-auth-store.js +43 -30
  71. package/dist/esm/auth-api/storage.d.ts +6 -4
  72. package/dist/esm/auth-api/storage.js +13 -3
  73. package/dist/esm/auth-api/types.d.ts +7 -2
  74. package/dist/esm/auth-api/user-id.d.ts +5 -0
  75. package/dist/esm/auth-api/user-id.js +32 -0
  76. package/dist/esm/auth-cookie-options.d.ts +11 -0
  77. package/dist/esm/auth-cookie-options.js +63 -0
  78. package/dist/esm/index.d.ts +4 -9
  79. package/dist/esm/index.js +2 -7
  80. package/dist/esm/oauth/memory.d.ts +6 -0
  81. package/dist/esm/oauth/memory.js +44 -11
  82. package/dist/esm/oauth/models.d.ts +7 -2
  83. package/dist/esm/oauth/models.js +6 -19
  84. package/dist/esm/oauth/sequelize.d.ts +10 -48
  85. package/dist/esm/oauth/sequelize.js +32 -87
  86. package/dist/esm/oauth/types.d.ts +1 -0
  87. package/dist/esm/passkey/base.d.ts +2 -0
  88. package/dist/esm/passkey/config.d.ts +2 -0
  89. package/dist/esm/passkey/config.js +23 -0
  90. package/dist/esm/passkey/memory.d.ts +8 -0
  91. package/dist/esm/passkey/memory.js +57 -16
  92. package/dist/esm/passkey/models.d.ts +13 -4
  93. package/dist/esm/passkey/models.js +39 -12
  94. package/dist/esm/passkey/sequelize.d.ts +13 -25
  95. package/dist/esm/passkey/sequelize.js +69 -154
  96. package/dist/esm/passkey/service.d.ts +6 -2
  97. package/dist/esm/passkey/service.js +173 -28
  98. package/dist/esm/passkey/types.d.ts +18 -9
  99. package/dist/esm/sequelize-utils.d.ts +8 -0
  100. package/dist/esm/sequelize-utils.js +48 -0
  101. package/dist/esm/token/base.d.ts +2 -1
  102. package/dist/esm/token/base.js +3 -1
  103. package/dist/esm/token/memory.d.ts +10 -0
  104. package/dist/esm/token/memory.js +122 -32
  105. package/dist/esm/token/sequelize.d.ts +4 -4
  106. package/dist/esm/token/sequelize.js +67 -85
  107. package/dist/esm/token/types.d.ts +8 -1
  108. package/dist/esm/user/base.d.ts +1 -0
  109. package/dist/esm/user/base.js +11 -4
  110. package/dist/esm/user/memory.d.ts +2 -0
  111. package/dist/esm/user/memory.js +9 -10
  112. package/dist/esm/user/sequelize.d.ts +7 -2
  113. package/dist/esm/user/sequelize.js +19 -32
  114. package/docs/swagger/openapi.json +1876 -0
  115. package/package.json +81 -32
@@ -1,9 +1,9 @@
1
1
  import { PasskeyService } from '../passkey/service.js';
2
- import type { AuthIdentifier, AuthStorage } from './types.js';
2
+ import type { AuthAdapter, AuthIdentifier } from './types.js';
3
3
  import type { OAuthStore } from '../oauth/base.js';
4
4
  import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
5
5
  import type { PasskeyStore } from '../passkey/base.js';
6
- import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
6
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
7
7
  import type { TokenStore } from '../token/base.js';
8
8
  import type { Token } from '../token/types.js';
9
9
  import type { UserStore } from '../user/base.js';
@@ -11,7 +11,7 @@ interface PasskeyAdapterOptions {
11
11
  store: PasskeyStore;
12
12
  config: PasskeyServiceConfig;
13
13
  }
14
- export interface AuthStorageAdapterOptions<UserRow, PublicUser> {
14
+ export interface AuthAdapterOptions<UserRow, PublicUser> {
15
15
  userStore: UserStore<UserRow, PublicUser>;
16
16
  tokenStore: TokenStore;
17
17
  passkeys?: PasskeyAdapterOptions | PasskeyService;
@@ -21,13 +21,13 @@ export interface AuthStorageAdapterOptions<UserRow, PublicUser> {
21
21
  effectiveUserId: AuthIdentifier;
22
22
  }) => boolean | Promise<boolean>;
23
23
  }
24
- export declare class AuthStorageAdapter<UserRow, PublicUser> implements AuthStorage<UserRow, PublicUser> {
24
+ export declare class CompositeAuthAdapter<UserRow, PublicUser> implements AuthAdapter<UserRow, PublicUser> {
25
25
  private readonly userStore;
26
26
  private readonly tokenStore;
27
27
  private readonly oauthStore?;
28
28
  private readonly passkeyService?;
29
29
  private readonly canImpersonateFn?;
30
- constructor(options: AuthStorageAdapterOptions<UserRow, PublicUser>);
30
+ constructor(options: AuthAdapterOptions<UserRow, PublicUser>);
31
31
  getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
32
32
  getUserPasswordHash(user: UserRow): string;
33
33
  getUserId(user: UserRow): AuthIdentifier;
@@ -43,6 +43,8 @@ export declare class AuthStorageAdapter<UserRow, PublicUser> implements AuthStor
43
43
  }): Promise<boolean>;
44
44
  createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
45
45
  verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
46
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
47
+ deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
46
48
  getClient(clientId: string): Promise<OAuthClient | null>;
47
49
  verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
48
50
  createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { PasskeyService } from '../passkey/service.js';
3
- export class AuthStorageAdapter {
3
+ export class CompositeAuthAdapter {
4
4
  constructor(options) {
5
5
  this.userStore = options.userStore;
6
6
  this.tokenStore = options.tokenStore;
@@ -52,6 +52,18 @@ export class AuthStorageAdapter {
52
52
  }
53
53
  return this.passkeyService.verifyResponse(params);
54
54
  }
55
+ async listUserCredentials(userId) {
56
+ if (!this.passkeyService) {
57
+ throw new Error('Passkey storage is not configured');
58
+ }
59
+ return this.passkeyService.listUserCredentials(userId);
60
+ }
61
+ async deletePasskeyCredential(credentialId) {
62
+ if (!this.passkeyService) {
63
+ throw new Error('Passkey storage is not configured');
64
+ }
65
+ return this.passkeyService.deleteCredential(credentialId);
66
+ }
55
67
  async getClient(clientId) {
56
68
  if (!this.oauthStore) {
57
69
  return null;
@@ -2,11 +2,11 @@ import { MemoryOAuthStore } from '../oauth/memory.js';
2
2
  import { MemoryPasskeyStore } from '../passkey/memory.js';
3
3
  import { TokenStore } from '../token/base.js';
4
4
  import { MemoryUserStore } from '../user/memory.js';
5
- import type { AuthIdentifier, AuthStorage } from './types.js';
5
+ import type { AuthAdapter, AuthIdentifier } from './types.js';
6
6
  import type { MemoryOAuthStoreOptions } from '../oauth/memory.js';
7
7
  import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
8
8
  import type { MemoryPasskeyStoreOptions } from '../passkey/memory.js';
9
- import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
9
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
10
10
  import type { Token } from '../token/types.js';
11
11
  import type { MemoryUserAttributes, MemoryPublicUser, MemoryUserStoreOptions } from '../user/memory.js';
12
12
  interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
@@ -24,7 +24,7 @@ export interface MemAuthStoreParams<UserAttributes extends MemoryUserAttributes
24
24
  }) => boolean | Promise<boolean>;
25
25
  tokenStore?: TokenStore;
26
26
  }
27
- export declare class MemAuthStore<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> implements AuthStorage<UserAttributes, PublicUserShape> {
27
+ export declare class MemAuthStore<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> implements AuthAdapter<UserAttributes, PublicUserShape> {
28
28
  readonly userStore: MemoryUserStore<UserAttributes, PublicUserShape>;
29
29
  readonly tokenStore: TokenStore;
30
30
  readonly passkeyStore?: MemoryPasskeyStore;
@@ -54,6 +54,8 @@ export declare class MemAuthStore<UserAttributes extends MemoryUserAttributes =
54
54
  }): Promise<boolean>;
55
55
  createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
56
56
  verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
57
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
58
+ deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
57
59
  getClient(clientId: string): Promise<OAuthClient | null>;
58
60
  verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
59
61
  createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
@@ -1,30 +1,10 @@
1
1
  import { MemoryOAuthStore } from '../oauth/memory.js';
2
+ import { normalizePasskeyConfig } from '../passkey/config.js';
2
3
  import { MemoryPasskeyStore } from '../passkey/memory.js';
3
4
  import { MemoryTokenStore } from '../token/memory.js';
4
5
  import { MemoryUserStore } from '../user/memory.js';
5
- import { AuthStorageAdapter } from './compat-auth-storage.js';
6
- const DEFAULT_PASSKEY_CONFIG = {
7
- rpId: 'localhost',
8
- rpName: 'API Server',
9
- origins: ['http://localhost:5173'],
10
- timeoutMs: 5 * 60 * 1000,
11
- userVerification: 'preferred'
12
- };
13
- function isOriginString(origin) {
14
- return typeof origin === 'string' && origin.trim().length > 0;
15
- }
16
- function normalizePasskeyConfig(config = {}) {
17
- const candidateOrigins = Array.isArray(config.origins) && config.origins.length > 0 ? config.origins.filter(isOriginString) : null;
18
- return {
19
- rpId: config.rpId?.trim() || DEFAULT_PASSKEY_CONFIG.rpId,
20
- rpName: config.rpName?.trim() || DEFAULT_PASSKEY_CONFIG.rpName,
21
- origins: candidateOrigins ? candidateOrigins.map((origin) => origin.trim()) : DEFAULT_PASSKEY_CONFIG.origins,
22
- timeoutMs: typeof config.timeoutMs === 'number' && config.timeoutMs > 0
23
- ? config.timeoutMs
24
- : DEFAULT_PASSKEY_CONFIG.timeoutMs,
25
- userVerification: config.userVerification ?? DEFAULT_PASSKEY_CONFIG.userVerification
26
- };
27
- }
6
+ import { CompositeAuthAdapter } from './compat-auth-storage.js';
7
+ import { toOptionalStringId } from './user-id.js';
28
8
  export class MemAuthStore {
29
9
  constructor(params = {}) {
30
10
  this.userStore = new MemoryUserStore({
@@ -56,7 +36,7 @@ export class MemAuthStore {
56
36
  passkeyStore = new MemoryPasskeyStore({ resolveUser });
57
37
  this.passkeyStore = passkeyStore;
58
38
  }
59
- this.adapter = new AuthStorageAdapter({
39
+ this.adapter = new CompositeAuthAdapter({
60
40
  userStore: this.userStore,
61
41
  tokenStore: this.tokenStore,
62
42
  passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
@@ -91,16 +71,16 @@ export class MemAuthStore {
91
71
  async getToken(query, opts) {
92
72
  const normalized = {
93
73
  ...query,
94
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
95
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
74
+ userId: toOptionalStringId(query.userId),
75
+ ruid: toOptionalStringId(query.ruid)
96
76
  };
97
77
  return this.adapter.getToken(normalized, opts);
98
78
  }
99
79
  async deleteToken(query) {
100
80
  const normalized = {
101
81
  ...query,
102
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
103
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
82
+ userId: toOptionalStringId(query.userId),
83
+ ruid: toOptionalStringId(query.ruid)
104
84
  };
105
85
  return this.adapter.deleteToken(normalized);
106
86
  }
@@ -113,6 +93,12 @@ export class MemAuthStore {
113
93
  async verifyPasskeyResponse(params) {
114
94
  return this.adapter.verifyPasskeyResponse(params);
115
95
  }
96
+ async listUserCredentials(userId) {
97
+ return this.adapter.listUserCredentials(userId);
98
+ }
99
+ async deletePasskeyCredential(credentialId) {
100
+ return this.adapter.deletePasskeyCredential(credentialId);
101
+ }
116
102
  async getClient(clientId) {
117
103
  return this.adapter.getClient(clientId);
118
104
  }
@@ -8,7 +8,7 @@ export interface AuthProviderModule<UserRow = unknown> {
8
8
  expires?: Date;
9
9
  }): Promise<TokenPair>;
10
10
  }
11
- export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule<any> implements AuthProviderModule<UserRow> {
11
+ export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule implements AuthProviderModule<UserRow> {
12
12
  readonly moduleType: "auth";
13
13
  protected constructor(opts: {
14
14
  namespace: string;
@@ -3,21 +3,31 @@ import { SequelizeOAuthStore, type SequelizeOAuthStoreOptions } from '../oauth/s
3
3
  import { SequelizePasskeyStore } from '../passkey/sequelize.js';
4
4
  import { type SequelizeTokenStoreOptions } from '../token/sequelize.js';
5
5
  import { SequelizeUserStore, type AuthUserAttributes, GenericUserModel, GenericUserModelStatic } from '../user/sequelize.js';
6
- import type { AuthIdentifier, AuthStorage } from './types.js';
6
+ import type { AuthAdapter, AuthIdentifier } from './types.js';
7
7
  import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
8
- import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
8
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
9
9
  import type { TokenStore } from '../token/base.js';
10
10
  import type { Token } from '../token/types.js';
11
11
  interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
12
12
  enabled?: boolean;
13
13
  }
14
+ export interface SqlAuthStoreTablePrefixes {
15
+ user?: string;
16
+ token?: string;
17
+ passkey?: string;
18
+ oauth?: string;
19
+ }
14
20
  export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
15
21
  sequelize: Sequelize;
16
22
  syncOptions?: SyncOptions;
17
23
  bcryptRounds?: number;
18
24
  passwordPepper?: string;
25
+ tablePrefix?: string;
26
+ tablePrefixes?: SqlAuthStoreTablePrefixes;
19
27
  userModel?: GenericUserModelStatic;
20
- userModelFactory?: (sequelize: Sequelize) => GenericUserModelStatic;
28
+ userModelFactory?: (sequelize: Sequelize, options?: {
29
+ tablePrefix?: string;
30
+ }) => GenericUserModelStatic;
21
31
  userRecordMapper?: (model: GenericUserModel) => UserAttributes;
22
32
  publicUserMapper?: (user: UserAttributes) => PublicUserShape;
23
33
  passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
@@ -30,7 +40,7 @@ export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes =
30
40
  tokenStoreOptions?: Omit<SequelizeTokenStoreOptions, 'sequelize'>;
31
41
  oauthStoreOptions?: Omit<SequelizeOAuthStoreOptions, 'sequelize'>;
32
42
  }
33
- export declare class SqlAuthStore<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> implements AuthStorage<UserAttributes, PublicUserShape> {
43
+ export declare class SqlAuthStore<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> implements AuthAdapter<UserAttributes, PublicUserShape> {
34
44
  readonly userStore: SequelizeUserStore<UserAttributes, PublicUserShape>;
35
45
  readonly tokenStore: TokenStore;
36
46
  readonly passkeyStore?: SequelizePasskeyStore;
@@ -63,6 +73,8 @@ export declare class SqlAuthStore<UserAttributes extends AuthUserAttributes = Au
63
73
  }): Promise<boolean>;
64
74
  createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
65
75
  verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
76
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
77
+ deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
66
78
  getClient(clientId: string): Promise<OAuthClient | null>;
67
79
  verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
68
80
  createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
@@ -1,29 +1,19 @@
1
1
  import { SequelizeOAuthStore } from '../oauth/sequelize.js';
2
+ import { normalizePasskeyConfig } from '../passkey/config.js';
2
3
  import { SequelizePasskeyStore } from '../passkey/sequelize.js';
4
+ import { normalizeTablePrefix } from '../sequelize-utils.js';
3
5
  import { SequelizeTokenStore } from '../token/sequelize.js';
4
6
  import { SequelizeUserStore } from '../user/sequelize.js';
5
- import { AuthStorageAdapter } from './compat-auth-storage.js';
6
- const DEFAULT_PASSKEY_CONFIG = {
7
- rpId: 'localhost',
8
- rpName: 'API Server',
9
- origins: ['http://localhost:5173'],
10
- timeoutMs: 5 * 60 * 1000,
11
- userVerification: 'preferred'
12
- };
13
- function isOriginString(origin) {
14
- return typeof origin === 'string' && origin.trim().length > 0;
15
- }
16
- function normalizePasskeyConfig(config = {}) {
17
- const candidateOrigins = Array.isArray(config.origins) && config.origins.length > 0 ? config.origins.filter(isOriginString) : null;
18
- return {
19
- rpId: config.rpId?.trim() || DEFAULT_PASSKEY_CONFIG.rpId,
20
- rpName: config.rpName?.trim() || DEFAULT_PASSKEY_CONFIG.rpName,
21
- origins: candidateOrigins ? candidateOrigins.map((origin) => origin.trim()) : DEFAULT_PASSKEY_CONFIG.origins,
22
- timeoutMs: typeof config.timeoutMs === 'number' && config.timeoutMs > 0
23
- ? config.timeoutMs
24
- : DEFAULT_PASSKEY_CONFIG.timeoutMs,
25
- userVerification: config.userVerification ?? DEFAULT_PASSKEY_CONFIG.userVerification
26
- };
7
+ import { CompositeAuthAdapter } from './compat-auth-storage.js';
8
+ import { toOptionalStringId } from './user-id.js';
9
+ function resolveTablePrefix(...prefixes) {
10
+ for (const prefix of prefixes) {
11
+ const normalized = normalizeTablePrefix(prefix);
12
+ if (normalized) {
13
+ return normalized;
14
+ }
15
+ }
16
+ return undefined;
27
17
  }
28
18
  export class SqlAuthStore {
29
19
  constructor(params) {
@@ -33,6 +23,8 @@ export class SqlAuthStore {
33
23
  }
34
24
  this.sequelize = params.sequelize;
35
25
  this.syncOptions = params.syncOptions;
26
+ const moduleTablePrefixes = params.tablePrefixes ?? {};
27
+ const userTablePrefix = resolveTablePrefix(moduleTablePrefixes.user, params.tablePrefix);
36
28
  this.userStore = new SequelizeUserStore({
37
29
  sequelize: this.sequelize,
38
30
  userModel: params.userModel,
@@ -40,18 +32,28 @@ export class SqlAuthStore {
40
32
  recordMapper: params.userRecordMapper,
41
33
  toPublic: params.publicUserMapper,
42
34
  bcryptRounds: params.bcryptRounds,
43
- bcryptPepper: params.passwordPepper
35
+ bcryptPepper: params.passwordPepper,
36
+ tablePrefix: userTablePrefix
44
37
  });
38
+ const tokenTablePrefix = resolveTablePrefix(params.tokenStoreOptions?.tablePrefix, moduleTablePrefixes.token, params.tablePrefix);
45
39
  this.tokenStore =
46
- params.tokenStore ?? new SequelizeTokenStore({ sequelize: this.sequelize, ...params.tokenStoreOptions });
40
+ params.tokenStore ??
41
+ new SequelizeTokenStore({
42
+ sequelize: this.sequelize,
43
+ ...params.tokenStoreOptions,
44
+ tablePrefix: tokenTablePrefix
45
+ });
46
+ const oauthTablePrefix = resolveTablePrefix(params.oauthStoreOptions?.tablePrefix, moduleTablePrefixes.oauth, params.tablePrefix);
47
47
  this.oauthStore = new SequelizeOAuthStore({
48
48
  sequelize: this.sequelize,
49
49
  ...params.oauthStoreOptions,
50
+ tablePrefix: oauthTablePrefix,
50
51
  bcryptRounds: params.bcryptRounds
51
52
  });
52
53
  let passkeyStore;
53
54
  let passkeyConfig;
54
55
  if (params.passkeys !== false) {
56
+ const passkeyTablePrefix = resolveTablePrefix(moduleTablePrefixes.passkey, params.tablePrefix);
55
57
  passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
56
58
  const resolveUser = async (lookup) => {
57
59
  const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
@@ -66,10 +68,14 @@ export class SqlAuthStore {
66
68
  }));
67
69
  return mapper(found);
68
70
  };
69
- passkeyStore = new SequelizePasskeyStore({ sequelize: this.sequelize, resolveUser });
71
+ passkeyStore = new SequelizePasskeyStore({
72
+ sequelize: this.sequelize,
73
+ resolveUser,
74
+ tablePrefix: passkeyTablePrefix
75
+ });
70
76
  this.passkeyStore = passkeyStore;
71
77
  }
72
- this.adapter = new AuthStorageAdapter({
78
+ this.adapter = new CompositeAuthAdapter({
73
79
  userStore: this.userStore,
74
80
  tokenStore: this.tokenStore,
75
81
  passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
@@ -98,6 +104,7 @@ export class SqlAuthStore {
98
104
  }
99
105
  }
100
106
  finally {
107
+ // Prevent double-close errors when the same Sequelize instance is shared with other code.
101
108
  this.sequelize.close = async () => { };
102
109
  }
103
110
  }
@@ -122,16 +129,16 @@ export class SqlAuthStore {
122
129
  async getToken(query, opts) {
123
130
  const normalized = {
124
131
  ...query,
125
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
126
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
132
+ userId: toOptionalStringId(query.userId),
133
+ ruid: toOptionalStringId(query.ruid)
127
134
  };
128
135
  return this.adapter.getToken(normalized, opts);
129
136
  }
130
137
  async deleteToken(query) {
131
138
  const normalized = {
132
139
  ...query,
133
- userId: query.userId !== undefined && query.userId !== null ? String(query.userId) : undefined,
134
- ruid: query.ruid !== undefined && query.ruid !== null ? String(query.ruid) : undefined
140
+ userId: toOptionalStringId(query.userId),
141
+ ruid: toOptionalStringId(query.ruid)
135
142
  };
136
143
  return this.adapter.deleteToken(normalized);
137
144
  }
@@ -144,6 +151,12 @@ export class SqlAuthStore {
144
151
  async verifyPasskeyResponse(params) {
145
152
  return this.adapter.verifyPasskeyResponse(params);
146
153
  }
154
+ async listUserCredentials(userId) {
155
+ return this.adapter.listUserCredentials(userId);
156
+ }
157
+ async deletePasskeyCredential(credentialId) {
158
+ return this.adapter.deletePasskeyCredential(credentialId);
159
+ }
147
160
  async getClient(clientId) {
148
161
  return this.adapter.getClient(clientId);
149
162
  }
@@ -1,8 +1,8 @@
1
- import type { AuthIdentifier, AuthStorage } from './types.js';
1
+ import type { AuthAdapter, AuthIdentifier } from './types.js';
2
2
  import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
3
- import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
3
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult, StoredPasskeyCredential } from '../passkey/types.js';
4
4
  import type { Token } from '../token/types.js';
5
- export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> implements AuthStorage<UserRow, SafeUser> {
5
+ export declare class BaseAuthAdapter<UserRow = unknown, SafeUser = unknown> implements AuthAdapter<UserRow, SafeUser> {
6
6
  getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
7
7
  getUserPasswordHash(user: UserRow): string;
8
8
  getUserId(user: UserRow): AuthIdentifier;
@@ -24,6 +24,8 @@ export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> impl
24
24
  }): Promise<boolean>;
25
25
  createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
26
26
  verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
27
+ listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
28
+ deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
27
29
  getClient(clientId: string): Promise<OAuthClient | null>;
28
30
  verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
29
31
  createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
@@ -33,4 +35,4 @@ export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> impl
33
35
  effectiveUserId: AuthIdentifier;
34
36
  }): Promise<boolean>;
35
37
  }
36
- export declare const nullAuthStorage: AuthStorage<unknown, unknown>;
38
+ export declare const nullAuthAdapter: AuthAdapter<unknown, unknown>;
@@ -1,6 +1,6 @@
1
- // Handy base you can extend when wiring a real storage adapter. Every method
1
+ // Handy base you can extend when wiring a real auth adapter. Every method
2
2
  // throws by default so unimplemented hooks fail loudly.
3
- export class BaseAuthStorage {
3
+ export class BaseAuthAdapter {
4
4
  // Override to load a user record by identifier
5
5
  async getUser(identifier) {
6
6
  void identifier;
@@ -57,6 +57,16 @@ export class BaseAuthStorage {
57
57
  void params;
58
58
  throw new Error('Auth storage not configured');
59
59
  }
60
+ // Override to list passkey credentials for a user
61
+ async listUserCredentials(userId) {
62
+ void userId;
63
+ throw new Error('Auth storage not configured');
64
+ }
65
+ // Override to delete a passkey credential
66
+ async deletePasskeyCredential(credentialId) {
67
+ void credentialId;
68
+ throw new Error('Auth storage not configured');
69
+ }
60
70
  // Override to fetch an OAuth client by identifier
61
71
  async getClient(clientId) {
62
72
  void clientId;
@@ -85,4 +95,4 @@ export class BaseAuthStorage {
85
95
  return false;
86
96
  }
87
97
  }
88
- export const nullAuthStorage = new BaseAuthStorage();
98
+ export const nullAuthAdapter = new BaseAuthAdapter();
@@ -1,8 +1,9 @@
1
1
  import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
2
- import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
2
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult, StoredPasskeyCredential } from '../passkey/types.js';
3
3
  import type { Token } from '../token/types.js';
4
4
  export type AuthIdentifier = string | number;
5
- export interface AuthStorage<UserRow, SafeUser> {
5
+ /** @internal */
6
+ export interface AuthAdapter<UserRow, SafeUser> {
6
7
  getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
7
8
  getUserPasswordHash(user: UserRow): string;
8
9
  getUserId(user: UserRow): AuthIdentifier;
@@ -18,6 +19,8 @@ export interface AuthStorage<UserRow, SafeUser> {
18
19
  }): Promise<boolean>;
19
20
  createPasskeyChallenge?(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
20
21
  verifyPasskeyResponse?(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
22
+ listUserCredentials?(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
23
+ deletePasskeyCredential?(credentialId: Buffer | string): Promise<boolean>;
21
24
  getClient?(clientId: string): Promise<OAuthClient | null>;
22
25
  verifyClientSecret?(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
23
26
  createAuthCode?(request: AuthCodeRequest): Promise<AuthCodeData>;
@@ -27,3 +30,5 @@ export interface AuthStorage<UserRow, SafeUser> {
27
30
  effectiveUserId: AuthIdentifier;
28
31
  }): Promise<boolean>;
29
32
  }
33
+ /** @internal */
34
+ export type AuthStorage<UserRow, SafeUser> = AuthAdapter<UserRow, SafeUser>;
@@ -0,0 +1,5 @@
1
+ import type { AuthIdentifier } from './types.js';
2
+ export declare function normalizeComparableUserId(identifier: AuthIdentifier): string;
3
+ export declare function normalizeNumericUserId(identifier: AuthIdentifier): number;
4
+ export declare function normalizeStringUserId(identifier: AuthIdentifier): string;
5
+ export declare function toOptionalStringId(value: AuthIdentifier | undefined | null): string | undefined;
@@ -0,0 +1,32 @@
1
+ export function normalizeComparableUserId(identifier) {
2
+ if (typeof identifier === 'number' && Number.isFinite(identifier)) {
3
+ return String(identifier);
4
+ }
5
+ if (typeof identifier === 'string') {
6
+ const trimmed = identifier.trim();
7
+ if (trimmed.length === 0) {
8
+ throw new Error(`Unable to normalise user identifier: ${identifier}`);
9
+ }
10
+ if (/^\d+$/.test(trimmed)) {
11
+ return String(Number(trimmed));
12
+ }
13
+ return trimmed;
14
+ }
15
+ throw new Error(`Unable to normalise user identifier: ${identifier}`);
16
+ }
17
+ export function normalizeNumericUserId(identifier) {
18
+ const normalized = normalizeComparableUserId(identifier);
19
+ if (/^\d+$/.test(normalized)) {
20
+ return Number(normalized);
21
+ }
22
+ throw new Error(`Unable to normalise user identifier: ${identifier}`);
23
+ }
24
+ export function normalizeStringUserId(identifier) {
25
+ return normalizeComparableUserId(identifier);
26
+ }
27
+ export function toOptionalStringId(value) {
28
+ if (value === undefined || value === null) {
29
+ return undefined;
30
+ }
31
+ return String(value);
32
+ }
@@ -0,0 +1,11 @@
1
+ import type { Request } from 'express';
2
+ import type { CookieOptions } from 'express-serve-static-core';
3
+ export interface AuthCookieConfig {
4
+ cookieSecure?: boolean | 'auto';
5
+ cookieSameSite?: 'lax' | 'strict' | 'none';
6
+ cookieHttpOnly?: boolean;
7
+ cookieDomain?: string;
8
+ cookiePath?: string;
9
+ devMode?: boolean;
10
+ }
11
+ export declare function buildAuthCookieOptions(config: AuthCookieConfig, req: Pick<Request, 'headers' | 'protocol'>): CookieOptions;
@@ -0,0 +1,63 @@
1
+ function firstHeaderValue(value) {
2
+ if (typeof value === 'string') {
3
+ return value;
4
+ }
5
+ if (Array.isArray(value)) {
6
+ return value[0] ?? '';
7
+ }
8
+ return '';
9
+ }
10
+ function resolveOriginHostname(origin) {
11
+ try {
12
+ const url = new URL(origin);
13
+ const hostname = url.hostname.trim().toLowerCase();
14
+ return hostname.length > 0 ? hostname : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ function isLocalhostOrigin(origin) {
21
+ const hostname = resolveOriginHostname(origin);
22
+ if (!hostname) {
23
+ return false;
24
+ }
25
+ return hostname === 'localhost' || hostname.endsWith('.localhost');
26
+ }
27
+ export function buildAuthCookieOptions(config, req) {
28
+ const forwardedProto = firstHeaderValue(req.headers['x-forwarded-proto']).split(',')[0].trim().toLowerCase();
29
+ const isHttps = forwardedProto === 'https' || req.protocol === 'https';
30
+ const origin = firstHeaderValue(req.headers.origin ?? req.headers.referer);
31
+ let secure;
32
+ if (config.cookieSecure === true) {
33
+ secure = true;
34
+ }
35
+ else if (config.cookieSecure === false) {
36
+ secure = false;
37
+ }
38
+ else {
39
+ secure = isHttps;
40
+ }
41
+ let sameSite = config.cookieSameSite ?? 'lax';
42
+ if (sameSite !== 'lax' && sameSite !== 'strict' && sameSite !== 'none') {
43
+ sameSite = 'lax';
44
+ }
45
+ let resolvedSecure = secure;
46
+ if (sameSite === 'none' && resolvedSecure !== true) {
47
+ // Modern browsers reject SameSite=None cookies unless Secure is set.
48
+ resolvedSecure = true;
49
+ }
50
+ const options = {
51
+ httpOnly: config.cookieHttpOnly ?? true,
52
+ secure: resolvedSecure,
53
+ sameSite,
54
+ domain: config.cookieDomain || undefined,
55
+ path: config.cookiePath || '/',
56
+ maxAge: undefined
57
+ };
58
+ if (config.devMode && isLocalhostOrigin(origin)) {
59
+ // Domain cookies do not work on localhost; avoid breaking local development when cookieDomain is set.
60
+ options.domain = undefined;
61
+ }
62
+ return options;
63
+ }
@@ -1,32 +1,27 @@
1
1
  export { default as ApiServer } from './api-server-base.js';
2
2
  export { ApiError } from './api-server-base.js';
3
3
  export { ApiModule } from './api-module.js';
4
- export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, ExtendedReq } from './api-server-base.js';
5
- export type { AuthIdentifier, AuthStorage } from './auth-api/types.js';
4
+ export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, ExtendedReq, ExpressApiRequest, ExpressApiLocals } from './api-server-base.js';
5
+ export type { AuthIdentifier } from './auth-api/types.js';
6
6
  export type { Token, TokenPair, TokenStatus } from './token/types.js';
7
7
  export type { JwtSignResult, JwtVerifyResult, JwtDecodeResult } from './token/base.js';
8
8
  export type { OAuthClient, AuthCodeData, AuthCodeRequest } from './oauth/types.js';
9
9
  export type { AuthProviderModule } from './auth-api/module.js';
10
- export { nullAuthStorage, BaseAuthStorage } from './auth-api/storage.js';
10
+ export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
11
11
  export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
12
- export { AuthStorageAdapter } from './auth-api/compat-auth-storage.js';
12
+ export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
13
13
  export { MemAuthStore } from './auth-api/mem-auth-store.js';
14
- export { SqlAuthStore } from './auth-api/sql-auth-store.js';
15
14
  export { default as AuthModule } from './auth-api/auth-module.js';
16
15
  export type { OAuthStartParams, OAuthStartResult, OAuthCallbackParams, OAuthCallbackResult } from './oauth/types.js';
17
16
  export type { BcryptHasherOptions, CreateUserInput, UpdateUserInput, PublicUserMapper } from './user/types.js';
18
17
  export { UserStore } from './user/base.js';
19
18
  export { MemoryUserStore } from './user/memory.js';
20
- export { SequelizeUserStore } from './user/sequelize.js';
21
19
  export type { MemoryUserAttributes, MemoryUserStoreOptions } from './user/memory.js';
22
20
  export { TokenStore } from './token/base.js';
23
21
  export { MemoryTokenStore } from './token/memory.js';
24
- export { SequelizeTokenStore } from './token/sequelize.js';
25
22
  export { PasskeyService } from './passkey/service.js';
26
23
  export { PasskeyStore } from './passkey/base.js';
27
24
  export { MemoryPasskeyStore } from './passkey/memory.js';
28
- export { SequelizePasskeyStore } from './passkey/sequelize.js';
29
25
  export type { PasskeyServiceConfig, PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential, PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
30
26
  export { OAuthStore } from './oauth/base.js';
31
27
  export { MemoryOAuthStore } from './oauth/memory.js';
32
- export { SequelizeOAuthStore } from './oauth/sequelize.js';