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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/cjs/api-server-base.cjs +181 -74
  2. package/dist/cjs/api-server-base.d.ts +66 -29
  3. package/dist/cjs/auth-api/auth-module.d.ts +96 -0
  4. package/dist/cjs/auth-api/auth-module.js +1032 -0
  5. package/dist/cjs/auth-api/compat-auth-storage.d.ts +55 -0
  6. package/dist/cjs/auth-api/compat-auth-storage.js +116 -0
  7. package/dist/cjs/auth-api/mem-auth-store.d.ts +66 -0
  8. package/dist/cjs/auth-api/mem-auth-store.js +135 -0
  9. package/dist/cjs/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  10. package/dist/cjs/{auth-module.cjs → auth-api/module.js} +1 -1
  11. package/dist/cjs/auth-api/sql-auth-store.d.ts +75 -0
  12. package/dist/cjs/auth-api/sql-auth-store.js +166 -0
  13. package/dist/cjs/auth-api/storage.d.ts +36 -0
  14. package/dist/cjs/{auth-storage.cjs → auth-api/storage.js} +2 -2
  15. package/dist/cjs/auth-api/types.d.ts +29 -0
  16. package/dist/cjs/auth-api/types.js +2 -0
  17. package/dist/cjs/index.cjs +41 -7
  18. package/dist/cjs/index.d.ts +29 -5
  19. package/dist/cjs/oauth/base.d.ts +10 -0
  20. package/dist/cjs/oauth/base.js +6 -0
  21. package/dist/cjs/oauth/memory.d.ts +16 -0
  22. package/dist/cjs/oauth/memory.js +99 -0
  23. package/dist/cjs/oauth/models.d.ts +45 -0
  24. package/dist/cjs/oauth/models.js +58 -0
  25. package/dist/cjs/oauth/sequelize.d.ts +68 -0
  26. package/dist/cjs/oauth/sequelize.js +210 -0
  27. package/dist/cjs/oauth/types.d.ts +50 -0
  28. package/dist/cjs/oauth/types.js +3 -0
  29. package/dist/cjs/passkey/base.d.ts +15 -0
  30. package/dist/cjs/passkey/base.js +6 -0
  31. package/dist/cjs/passkey/memory.d.ts +26 -0
  32. package/dist/cjs/passkey/memory.js +82 -0
  33. package/dist/cjs/passkey/models.d.ts +25 -0
  34. package/dist/cjs/passkey/models.js +115 -0
  35. package/dist/cjs/passkey/sequelize.d.ts +54 -0
  36. package/dist/cjs/passkey/sequelize.js +211 -0
  37. package/dist/cjs/passkey/service.d.ts +17 -0
  38. package/dist/cjs/passkey/service.js +221 -0
  39. package/dist/cjs/passkey/types.d.ts +75 -0
  40. package/dist/cjs/passkey/types.js +2 -0
  41. package/dist/cjs/token/base.d.ts +38 -0
  42. package/dist/cjs/token/base.js +114 -0
  43. package/dist/cjs/token/memory.d.ts +19 -0
  44. package/dist/cjs/token/memory.js +149 -0
  45. package/dist/cjs/token/sequelize.d.ts +58 -0
  46. package/dist/cjs/token/sequelize.js +404 -0
  47. package/dist/cjs/token/types.d.ts +27 -0
  48. package/dist/cjs/token/types.js +2 -0
  49. package/dist/cjs/user/base.d.ts +26 -0
  50. package/dist/cjs/user/base.js +45 -0
  51. package/dist/cjs/user/memory.d.ts +35 -0
  52. package/dist/cjs/user/memory.js +173 -0
  53. package/dist/cjs/user/sequelize.d.ts +41 -0
  54. package/dist/cjs/user/sequelize.js +182 -0
  55. package/dist/cjs/user/types.d.ts +11 -0
  56. package/dist/cjs/user/types.js +2 -0
  57. package/dist/esm/api-server-base.d.ts +66 -29
  58. package/dist/esm/api-server-base.js +179 -72
  59. package/dist/esm/auth-api/auth-module.d.ts +96 -0
  60. package/dist/esm/auth-api/auth-module.js +1030 -0
  61. package/dist/esm/auth-api/compat-auth-storage.d.ts +55 -0
  62. package/dist/esm/auth-api/compat-auth-storage.js +112 -0
  63. package/dist/esm/auth-api/mem-auth-store.d.ts +66 -0
  64. package/dist/esm/auth-api/mem-auth-store.js +131 -0
  65. package/dist/esm/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
  66. package/dist/esm/{auth-module.js → auth-api/module.js} +1 -1
  67. package/dist/esm/auth-api/sql-auth-store.d.ts +75 -0
  68. package/dist/esm/auth-api/sql-auth-store.js +162 -0
  69. package/dist/esm/auth-api/storage.d.ts +36 -0
  70. package/dist/esm/{auth-storage.js → auth-api/storage.js} +2 -2
  71. package/dist/esm/auth-api/types.d.ts +29 -0
  72. package/dist/esm/auth-api/types.js +1 -0
  73. package/dist/esm/index.d.ts +29 -5
  74. package/dist/esm/index.js +19 -2
  75. package/dist/esm/oauth/base.d.ts +10 -0
  76. package/dist/esm/oauth/base.js +2 -0
  77. package/dist/esm/oauth/memory.d.ts +16 -0
  78. package/dist/esm/oauth/memory.js +92 -0
  79. package/dist/esm/oauth/models.d.ts +45 -0
  80. package/dist/esm/oauth/models.js +51 -0
  81. package/dist/esm/oauth/sequelize.d.ts +68 -0
  82. package/dist/esm/oauth/sequelize.js +199 -0
  83. package/dist/esm/oauth/types.d.ts +50 -0
  84. package/dist/esm/oauth/types.js +2 -0
  85. package/dist/esm/passkey/base.d.ts +15 -0
  86. package/dist/esm/passkey/base.js +2 -0
  87. package/dist/esm/passkey/memory.d.ts +26 -0
  88. package/dist/esm/passkey/memory.js +78 -0
  89. package/dist/esm/passkey/models.d.ts +25 -0
  90. package/dist/esm/passkey/models.js +108 -0
  91. package/dist/esm/passkey/sequelize.d.ts +54 -0
  92. package/dist/esm/passkey/sequelize.js +207 -0
  93. package/dist/esm/passkey/service.d.ts +17 -0
  94. package/dist/esm/passkey/service.js +217 -0
  95. package/dist/esm/passkey/types.d.ts +75 -0
  96. package/dist/esm/passkey/types.js +1 -0
  97. package/dist/esm/token/base.d.ts +38 -0
  98. package/dist/esm/token/base.js +107 -0
  99. package/dist/esm/token/memory.d.ts +19 -0
  100. package/dist/esm/token/memory.js +145 -0
  101. package/dist/esm/token/sequelize.d.ts +58 -0
  102. package/dist/esm/token/sequelize.js +400 -0
  103. package/dist/esm/token/types.d.ts +27 -0
  104. package/dist/esm/token/types.js +1 -0
  105. package/dist/esm/user/base.d.ts +26 -0
  106. package/dist/esm/user/base.js +38 -0
  107. package/dist/esm/user/memory.d.ts +35 -0
  108. package/dist/esm/user/memory.js +169 -0
  109. package/dist/esm/user/sequelize.d.ts +41 -0
  110. package/dist/esm/user/sequelize.js +176 -0
  111. package/dist/esm/user/types.d.ts +11 -0
  112. package/dist/esm/user/types.js +1 -0
  113. package/package.json +11 -3
  114. package/dist/cjs/auth-storage.d.ts +0 -133
  115. package/dist/esm/auth-storage.d.ts +0 -133
@@ -0,0 +1,55 @@
1
+ import { PasskeyService } from '../passkey/service.js';
2
+ import type { AuthIdentifier, AuthStorage } from './types.js';
3
+ import type { OAuthStore } from '../oauth/base.js';
4
+ import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
5
+ import type { PasskeyStore } from '../passkey/base.js';
6
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
7
+ import type { TokenStore } from '../token/base.js';
8
+ import type { Token } from '../token/types.js';
9
+ import type { UserStore } from '../user/base.js';
10
+ interface PasskeyAdapterOptions {
11
+ store: PasskeyStore;
12
+ config: PasskeyServiceConfig;
13
+ }
14
+ export interface AuthStorageAdapterOptions<UserRow, PublicUser> {
15
+ userStore: UserStore<UserRow, PublicUser>;
16
+ tokenStore: TokenStore;
17
+ passkeys?: PasskeyAdapterOptions | PasskeyService;
18
+ oauthStore?: OAuthStore;
19
+ canImpersonate?: (params: {
20
+ realUserId: AuthIdentifier;
21
+ effectiveUserId: AuthIdentifier;
22
+ }) => boolean | Promise<boolean>;
23
+ }
24
+ export declare class AuthStorageAdapter<UserRow, PublicUser> implements AuthStorage<UserRow, PublicUser> {
25
+ private readonly userStore;
26
+ private readonly tokenStore;
27
+ private readonly oauthStore?;
28
+ private readonly passkeyService?;
29
+ private readonly canImpersonateFn?;
30
+ constructor(options: AuthStorageAdapterOptions<UserRow, PublicUser>);
31
+ getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
32
+ getUserPasswordHash(user: UserRow): string;
33
+ getUserId(user: UserRow): AuthIdentifier;
34
+ filterUser(user: UserRow): PublicUser;
35
+ verifyPassword(password: string, hash: string): Promise<boolean>;
36
+ storeToken(data: Token): Promise<void>;
37
+ getToken(query: Partial<Token>, opts?: {
38
+ includeExpired?: boolean;
39
+ }): Promise<Token | null>;
40
+ deleteToken(query: Partial<Token>): Promise<number>;
41
+ updateToken(updates: Partial<Token> & {
42
+ refreshToken: string;
43
+ }): Promise<boolean>;
44
+ createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
45
+ verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
46
+ getClient(clientId: string): Promise<OAuthClient | null>;
47
+ verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
48
+ createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
49
+ consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
50
+ canImpersonate(params: {
51
+ realUserId: AuthIdentifier;
52
+ effectiveUserId: AuthIdentifier;
53
+ }): Promise<boolean>;
54
+ }
55
+ export {};
@@ -0,0 +1,112 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { PasskeyService } from '../passkey/service.js';
3
+ export class AuthStorageAdapter {
4
+ constructor(options) {
5
+ this.userStore = options.userStore;
6
+ this.tokenStore = options.tokenStore;
7
+ this.oauthStore = options.oauthStore;
8
+ this.canImpersonateFn = options.canImpersonate;
9
+ if (options.passkeys instanceof PasskeyService) {
10
+ this.passkeyService = options.passkeys;
11
+ }
12
+ else if (options.passkeys) {
13
+ this.passkeyService = new PasskeyService(options.passkeys.config, options.passkeys.store);
14
+ }
15
+ }
16
+ async getUser(identifier) {
17
+ return this.userStore.findUser(identifier);
18
+ }
19
+ getUserPasswordHash(user) {
20
+ return this.userStore.getPasswordHash(user) ?? '';
21
+ }
22
+ getUserId(user) {
23
+ return this.userStore.getUserId(user);
24
+ }
25
+ filterUser(user) {
26
+ return this.userStore.toPublic(user);
27
+ }
28
+ async verifyPassword(password, hash) {
29
+ return this.userStore.verifyPassword(password, hash);
30
+ }
31
+ async storeToken(data) {
32
+ return this.tokenStore.save(data);
33
+ }
34
+ async getToken(query, opts) {
35
+ return this.tokenStore.get(query, opts);
36
+ }
37
+ async deleteToken(query) {
38
+ return this.tokenStore.delete(query);
39
+ }
40
+ async updateToken(updates) {
41
+ return this.tokenStore.update(updates);
42
+ }
43
+ async createPasskeyChallenge(params) {
44
+ if (!this.passkeyService) {
45
+ throw new Error('Passkey storage is not configured');
46
+ }
47
+ return this.passkeyService.createChallenge(params);
48
+ }
49
+ async verifyPasskeyResponse(params) {
50
+ if (!this.passkeyService) {
51
+ throw new Error('Passkey storage is not configured');
52
+ }
53
+ return this.passkeyService.verifyResponse(params);
54
+ }
55
+ async getClient(clientId) {
56
+ if (!this.oauthStore) {
57
+ return null;
58
+ }
59
+ return this.oauthStore.getClient(clientId);
60
+ }
61
+ async verifyClientSecret(client, clientSecret) {
62
+ if (!this.oauthStore) {
63
+ throw new Error('OAuth storage is not configured');
64
+ }
65
+ return this.oauthStore.verifyClientSecret(client.clientId, clientSecret);
66
+ }
67
+ async createAuthCode(request) {
68
+ if (!this.oauthStore) {
69
+ throw new Error('OAuth storage is not configured');
70
+ }
71
+ const expiresAt = new Date(Date.now() + (request.expiresInSeconds ?? 300) * 1000);
72
+ const code = request.code ?? randomUUID();
73
+ await this.oauthStore.createAuthCode({
74
+ code,
75
+ clientId: request.clientId,
76
+ userId: request.userId,
77
+ scope: request.scope,
78
+ redirectUri: request.redirectUri,
79
+ codeChallenge: request.codeChallenge,
80
+ codeChallengeMethod: request.codeChallengeMethod,
81
+ expiresAt,
82
+ metadata: request.metadata
83
+ });
84
+ return {
85
+ code,
86
+ clientId: request.clientId,
87
+ userId: request.userId,
88
+ redirectUri: request.redirectUri,
89
+ scope: request.scope ?? [],
90
+ codeChallenge: request.codeChallenge,
91
+ codeChallengeMethod: request.codeChallengeMethod,
92
+ expiresAt,
93
+ metadata: request.metadata
94
+ };
95
+ }
96
+ async consumeAuthCode(code, clientId) {
97
+ if (!this.oauthStore) {
98
+ return null;
99
+ }
100
+ const consumed = await this.oauthStore.consumeAuthCode(code);
101
+ if (!consumed || consumed.clientId !== clientId) {
102
+ return null;
103
+ }
104
+ return consumed;
105
+ }
106
+ async canImpersonate(params) {
107
+ if (this.canImpersonateFn) {
108
+ return !!(await this.canImpersonateFn(params));
109
+ }
110
+ return params.realUserId === params.effectiveUserId;
111
+ }
112
+ }
@@ -0,0 +1,66 @@
1
+ import { MemoryOAuthStore } from '../oauth/memory.js';
2
+ import { MemoryPasskeyStore } from '../passkey/memory.js';
3
+ import { TokenStore } from '../token/base.js';
4
+ import { MemoryUserStore } from '../user/memory.js';
5
+ import type { AuthIdentifier, AuthStorage } from './types.js';
6
+ import type { MemoryOAuthStoreOptions } from '../oauth/memory.js';
7
+ import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
8
+ import type { MemoryPasskeyStoreOptions } from '../passkey/memory.js';
9
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
10
+ import type { Token } from '../token/types.js';
11
+ import type { MemoryUserAttributes, MemoryPublicUser, MemoryUserStoreOptions } from '../user/memory.js';
12
+ interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
13
+ enabled?: boolean;
14
+ }
15
+ export interface MemAuthStoreParams<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> extends Omit<MemoryUserStoreOptions<UserAttributes, PublicUserShape>, 'hasher'>, MemoryOAuthStoreOptions, Partial<MemoryPasskeyStoreOptions> {
16
+ bcryptRounds?: number;
17
+ passwordPepper?: string;
18
+ publicUserMapper?: (user: UserAttributes) => PublicUserShape;
19
+ passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
20
+ passkeys?: false | PasskeyOptions;
21
+ canImpersonate?: (params: {
22
+ realUserId: AuthIdentifier;
23
+ effectiveUserId: AuthIdentifier;
24
+ }) => boolean | Promise<boolean>;
25
+ tokenStore?: TokenStore;
26
+ }
27
+ export declare class MemAuthStore<UserAttributes extends MemoryUserAttributes = MemoryUserAttributes, PublicUserShape extends MemoryPublicUser<UserAttributes> = MemoryPublicUser<UserAttributes>> implements AuthStorage<UserAttributes, PublicUserShape> {
28
+ readonly userStore: MemoryUserStore<UserAttributes, PublicUserShape>;
29
+ readonly tokenStore: TokenStore;
30
+ readonly passkeyStore?: MemoryPasskeyStore;
31
+ readonly oauthStore: MemoryOAuthStore;
32
+ private readonly adapter;
33
+ constructor(params?: MemAuthStoreParams<UserAttributes, PublicUserShape>);
34
+ initialise(): Promise<void>;
35
+ close(): Promise<void>;
36
+ getUser(identifier: AuthIdentifier): Promise<UserAttributes | null>;
37
+ getUserPasswordHash(user: UserAttributes): string;
38
+ getUserId(user: UserAttributes): AuthIdentifier;
39
+ filterUser(user: UserAttributes): PublicUserShape;
40
+ verifyPassword(password: string, hash: string): Promise<boolean>;
41
+ storeToken(data: Token): Promise<void>;
42
+ getToken(query: Partial<Token> & {
43
+ userId?: AuthIdentifier;
44
+ ruid?: AuthIdentifier;
45
+ }, opts?: {
46
+ includeExpired?: boolean;
47
+ }): Promise<Token | null>;
48
+ deleteToken(query: Partial<Token> & {
49
+ userId?: AuthIdentifier;
50
+ ruid?: AuthIdentifier;
51
+ }): Promise<number>;
52
+ updateToken(updates: Partial<Token> & {
53
+ refreshToken: string;
54
+ }): Promise<boolean>;
55
+ createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
56
+ verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
57
+ getClient(clientId: string): Promise<OAuthClient | null>;
58
+ verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
59
+ createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
60
+ consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
61
+ canImpersonate(params: {
62
+ realUserId: AuthIdentifier;
63
+ effectiveUserId: AuthIdentifier;
64
+ }): Promise<boolean>;
65
+ }
66
+ export {};
@@ -0,0 +1,131 @@
1
+ import { MemoryOAuthStore } from '../oauth/memory.js';
2
+ import { MemoryPasskeyStore } from '../passkey/memory.js';
3
+ import { MemoryTokenStore } from '../token/memory.js';
4
+ 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
+ }
28
+ export class MemAuthStore {
29
+ constructor(params = {}) {
30
+ this.userStore = new MemoryUserStore({
31
+ toPublic: params.publicUserMapper ?? params.toPublic,
32
+ userIdFactory: params.userIdFactory,
33
+ startingUserId: params.startingUserId,
34
+ bcryptRounds: params.bcryptRounds,
35
+ bcryptPepper: params.passwordPepper
36
+ });
37
+ this.tokenStore = params.tokenStore ?? new MemoryTokenStore();
38
+ this.oauthStore = new MemoryOAuthStore({ bcryptRounds: params.bcryptRounds });
39
+ let passkeyStore;
40
+ let passkeyConfig;
41
+ if (params.passkeys !== false) {
42
+ passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
43
+ const resolveUser = async (lookup) => {
44
+ const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
45
+ if (!found) {
46
+ return null;
47
+ }
48
+ const mapper = params.passkeyUserMapper ??
49
+ ((user) => ({
50
+ id: user.user_id,
51
+ login: user.login,
52
+ displayName: user.login
53
+ }));
54
+ return mapper(found);
55
+ };
56
+ passkeyStore = new MemoryPasskeyStore({ resolveUser });
57
+ this.passkeyStore = passkeyStore;
58
+ }
59
+ this.adapter = new AuthStorageAdapter({
60
+ userStore: this.userStore,
61
+ tokenStore: this.tokenStore,
62
+ passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
63
+ oauthStore: this.oauthStore,
64
+ canImpersonate: params.canImpersonate
65
+ });
66
+ }
67
+ async initialise() {
68
+ return;
69
+ }
70
+ async close() {
71
+ return;
72
+ }
73
+ async getUser(identifier) {
74
+ return this.adapter.getUser(identifier);
75
+ }
76
+ getUserPasswordHash(user) {
77
+ return this.adapter.getUserPasswordHash(user);
78
+ }
79
+ getUserId(user) {
80
+ return this.adapter.getUserId(user);
81
+ }
82
+ filterUser(user) {
83
+ return this.adapter.filterUser(user);
84
+ }
85
+ async verifyPassword(password, hash) {
86
+ return this.adapter.verifyPassword(password, hash);
87
+ }
88
+ async storeToken(data) {
89
+ return this.adapter.storeToken(data);
90
+ }
91
+ async getToken(query, opts) {
92
+ const normalized = {
93
+ ...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
96
+ };
97
+ return this.adapter.getToken(normalized, opts);
98
+ }
99
+ async deleteToken(query) {
100
+ const normalized = {
101
+ ...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
104
+ };
105
+ return this.adapter.deleteToken(normalized);
106
+ }
107
+ async updateToken(updates) {
108
+ return this.adapter.updateToken(updates);
109
+ }
110
+ async createPasskeyChallenge(params) {
111
+ return this.adapter.createPasskeyChallenge(params);
112
+ }
113
+ async verifyPasskeyResponse(params) {
114
+ return this.adapter.verifyPasskeyResponse(params);
115
+ }
116
+ async getClient(clientId) {
117
+ return this.adapter.getClient(clientId);
118
+ }
119
+ async verifyClientSecret(client, clientSecret) {
120
+ return this.adapter.verifyClientSecret(client, clientSecret);
121
+ }
122
+ async createAuthCode(request) {
123
+ return this.adapter.createAuthCode(request);
124
+ }
125
+ async consumeAuthCode(code, clientId) {
126
+ return this.adapter.consumeAuthCode(code, clientId);
127
+ }
128
+ async canImpersonate(params) {
129
+ return this.adapter.canImpersonate(params);
130
+ }
131
+ }
@@ -1,20 +1,20 @@
1
- import { ApiModule } from './api-module.js';
2
- import type { ApiRequest } from './api-server-base.js';
3
- import type { AuthTokenMetadata, AuthTokenPair } from './auth-storage.js';
1
+ import { ApiModule } from '../api-module.js';
2
+ import type { ApiRequest } from '../api-server-base.js';
3
+ import type { Token, TokenPair } from '../token/types.js';
4
4
  export interface AuthProviderModule<UserRow = unknown> {
5
5
  readonly moduleType: 'auth';
6
6
  readonly namespace: string;
7
- issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: AuthTokenMetadata & {
7
+ issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: Partial<Token> & {
8
8
  expires?: Date;
9
- }): Promise<AuthTokenPair>;
9
+ }): Promise<TokenPair>;
10
10
  }
11
11
  export declare abstract class BaseAuthModule<UserRow = unknown> extends ApiModule<any> implements AuthProviderModule<UserRow> {
12
12
  readonly moduleType: "auth";
13
13
  protected constructor(opts: {
14
14
  namespace: string;
15
15
  });
16
- abstract issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: AuthTokenMetadata & {
16
+ abstract issueTokens(apiReq: ApiRequest, user: UserRow, metadata?: Partial<Token> & {
17
17
  expires?: Date;
18
- }): Promise<AuthTokenPair>;
18
+ }): Promise<TokenPair>;
19
19
  }
20
20
  export declare const nullAuthModule: AuthProviderModule<unknown>;
@@ -1,4 +1,4 @@
1
- import { ApiModule } from './api-module.js';
1
+ import { ApiModule } from '../api-module.js';
2
2
  // Handy base that you can extend when wiring a real auth module. Subclasses
3
3
  // must supply a namespace via the constructor and implement token issuance.
4
4
  export class BaseAuthModule extends ApiModule {
@@ -0,0 +1,75 @@
1
+ import { Sequelize, type SyncOptions } from 'sequelize';
2
+ import { SequelizeOAuthStore, type SequelizeOAuthStoreOptions } from '../oauth/sequelize.js';
3
+ import { SequelizePasskeyStore } from '../passkey/sequelize.js';
4
+ import { type SequelizeTokenStoreOptions } from '../token/sequelize.js';
5
+ import { SequelizeUserStore, type AuthUserAttributes, GenericUserModel, GenericUserModelStatic } from '../user/sequelize.js';
6
+ import type { AuthIdentifier, AuthStorage } from './types.js';
7
+ import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
8
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyServiceConfig, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
9
+ import type { TokenStore } from '../token/base.js';
10
+ import type { Token } from '../token/types.js';
11
+ interface PasskeyOptions extends Partial<PasskeyServiceConfig> {
12
+ enabled?: boolean;
13
+ }
14
+ export interface SqlAuthStoreParams<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> {
15
+ sequelize: Sequelize;
16
+ syncOptions?: SyncOptions;
17
+ bcryptRounds?: number;
18
+ passwordPepper?: string;
19
+ userModel?: GenericUserModelStatic;
20
+ userModelFactory?: (sequelize: Sequelize) => GenericUserModelStatic;
21
+ userRecordMapper?: (model: GenericUserModel) => UserAttributes;
22
+ publicUserMapper?: (user: UserAttributes) => PublicUserShape;
23
+ passkeyUserMapper?: (user: UserAttributes) => PasskeyUserDescriptor;
24
+ passkeys?: false | PasskeyOptions;
25
+ canImpersonate?: (params: {
26
+ realUserId: AuthIdentifier;
27
+ effectiveUserId: AuthIdentifier;
28
+ }) => boolean | Promise<boolean>;
29
+ tokenStore?: TokenStore;
30
+ tokenStoreOptions?: Omit<SequelizeTokenStoreOptions, 'sequelize'>;
31
+ oauthStoreOptions?: Omit<SequelizeOAuthStoreOptions, 'sequelize'>;
32
+ }
33
+ export declare class SqlAuthStore<UserAttributes extends AuthUserAttributes = AuthUserAttributes, PublicUserShape extends Omit<UserAttributes, 'password'> = Omit<UserAttributes, 'password'>> implements AuthStorage<UserAttributes, PublicUserShape> {
34
+ readonly userStore: SequelizeUserStore<UserAttributes, PublicUserShape>;
35
+ readonly tokenStore: TokenStore;
36
+ readonly passkeyStore?: SequelizePasskeyStore;
37
+ readonly oauthStore: SequelizeOAuthStore;
38
+ private readonly adapter;
39
+ private readonly sequelize;
40
+ private closed;
41
+ private readonly syncOptions?;
42
+ constructor(params: SqlAuthStoreParams<UserAttributes, PublicUserShape>);
43
+ initialise(withSync?: boolean): Promise<void>;
44
+ close(): Promise<void>;
45
+ getUser(identifier: AuthIdentifier): Promise<UserAttributes | null>;
46
+ getUserPasswordHash(user: UserAttributes): string;
47
+ getUserId(user: UserAttributes): AuthIdentifier;
48
+ filterUser(user: UserAttributes): PublicUserShape;
49
+ verifyPassword(password: string, hash: string): Promise<boolean>;
50
+ storeToken(data: Token): Promise<void>;
51
+ getToken(query: Partial<Token> & {
52
+ userId?: AuthIdentifier;
53
+ ruid?: AuthIdentifier;
54
+ }, opts?: {
55
+ includeExpired?: boolean;
56
+ }): Promise<Token | null>;
57
+ deleteToken(query: Partial<Token> & {
58
+ userId?: AuthIdentifier;
59
+ ruid?: AuthIdentifier;
60
+ }): Promise<number>;
61
+ updateToken(updates: Partial<Token> & {
62
+ refreshToken: string;
63
+ }): Promise<boolean>;
64
+ createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
65
+ verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
66
+ getClient(clientId: string): Promise<OAuthClient | null>;
67
+ verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
68
+ createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
69
+ consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
70
+ canImpersonate(params: {
71
+ realUserId: AuthIdentifier;
72
+ effectiveUserId: AuthIdentifier;
73
+ }): Promise<boolean>;
74
+ }
75
+ export {};
@@ -0,0 +1,162 @@
1
+ import { SequelizeOAuthStore } from '../oauth/sequelize.js';
2
+ import { SequelizePasskeyStore } from '../passkey/sequelize.js';
3
+ import { SequelizeTokenStore } from '../token/sequelize.js';
4
+ 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
+ };
27
+ }
28
+ export class SqlAuthStore {
29
+ constructor(params) {
30
+ this.closed = false;
31
+ if (!params?.sequelize) {
32
+ throw new Error('SqlAuthStore requires an initialised Sequelize instance');
33
+ }
34
+ this.sequelize = params.sequelize;
35
+ this.syncOptions = params.syncOptions;
36
+ this.userStore = new SequelizeUserStore({
37
+ sequelize: this.sequelize,
38
+ userModel: params.userModel,
39
+ userModelFactory: params.userModelFactory,
40
+ recordMapper: params.userRecordMapper,
41
+ toPublic: params.publicUserMapper,
42
+ bcryptRounds: params.bcryptRounds,
43
+ bcryptPepper: params.passwordPepper
44
+ });
45
+ this.tokenStore =
46
+ params.tokenStore ?? new SequelizeTokenStore({ sequelize: this.sequelize, ...params.tokenStoreOptions });
47
+ this.oauthStore = new SequelizeOAuthStore({
48
+ sequelize: this.sequelize,
49
+ ...params.oauthStoreOptions,
50
+ bcryptRounds: params.bcryptRounds
51
+ });
52
+ let passkeyStore;
53
+ let passkeyConfig;
54
+ if (params.passkeys !== false) {
55
+ passkeyConfig = normalizePasskeyConfig(params.passkeys ?? {});
56
+ const resolveUser = async (lookup) => {
57
+ const found = await this.userStore.findUser(lookup.userId ?? lookup.login ?? '');
58
+ if (!found) {
59
+ return null;
60
+ }
61
+ const mapper = params.passkeyUserMapper ??
62
+ ((user) => ({
63
+ id: (this.userStore.getUserId(user) ?? user['user_id']),
64
+ login: user.login ?? String(this.userStore.getUserId(user)),
65
+ displayName: user.login ?? String(this.userStore.getUserId(user))
66
+ }));
67
+ return mapper(found);
68
+ };
69
+ passkeyStore = new SequelizePasskeyStore({ sequelize: this.sequelize, resolveUser });
70
+ this.passkeyStore = passkeyStore;
71
+ }
72
+ this.adapter = new AuthStorageAdapter({
73
+ userStore: this.userStore,
74
+ tokenStore: this.tokenStore,
75
+ passkeys: passkeyStore && passkeyConfig ? { store: passkeyStore, config: passkeyConfig } : undefined,
76
+ oauthStore: this.oauthStore,
77
+ canImpersonate: params.canImpersonate
78
+ });
79
+ }
80
+ async initialise(withSync = false) {
81
+ await this.sequelize.authenticate();
82
+ if (withSync) {
83
+ await this.sequelize.sync(this.syncOptions);
84
+ }
85
+ }
86
+ async close() {
87
+ if (this.closed) {
88
+ return;
89
+ }
90
+ this.closed = true;
91
+ try {
92
+ await this.sequelize.close();
93
+ }
94
+ catch (error) {
95
+ const message = error?.message ?? '';
96
+ if (!/closed/i.test(message)) {
97
+ throw error;
98
+ }
99
+ }
100
+ finally {
101
+ this.sequelize.close = async () => { };
102
+ }
103
+ }
104
+ async getUser(identifier) {
105
+ return this.adapter.getUser(identifier);
106
+ }
107
+ getUserPasswordHash(user) {
108
+ return this.adapter.getUserPasswordHash(user);
109
+ }
110
+ getUserId(user) {
111
+ return this.adapter.getUserId(user);
112
+ }
113
+ filterUser(user) {
114
+ return this.adapter.filterUser(user);
115
+ }
116
+ async verifyPassword(password, hash) {
117
+ return this.adapter.verifyPassword(password, hash);
118
+ }
119
+ async storeToken(data) {
120
+ return this.adapter.storeToken(data);
121
+ }
122
+ async getToken(query, opts) {
123
+ const normalized = {
124
+ ...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
127
+ };
128
+ return this.adapter.getToken(normalized, opts);
129
+ }
130
+ async deleteToken(query) {
131
+ const normalized = {
132
+ ...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
135
+ };
136
+ return this.adapter.deleteToken(normalized);
137
+ }
138
+ async updateToken(updates) {
139
+ return this.adapter.updateToken(updates);
140
+ }
141
+ async createPasskeyChallenge(params) {
142
+ return this.adapter.createPasskeyChallenge(params);
143
+ }
144
+ async verifyPasskeyResponse(params) {
145
+ return this.adapter.verifyPasskeyResponse(params);
146
+ }
147
+ async getClient(clientId) {
148
+ return this.adapter.getClient(clientId);
149
+ }
150
+ async verifyClientSecret(client, clientSecret) {
151
+ return this.adapter.verifyClientSecret(client, clientSecret);
152
+ }
153
+ async createAuthCode(request) {
154
+ return this.adapter.createAuthCode(request);
155
+ }
156
+ async consumeAuthCode(code, clientId) {
157
+ return this.adapter.consumeAuthCode(code, clientId);
158
+ }
159
+ async canImpersonate(params) {
160
+ return this.adapter.canImpersonate(params);
161
+ }
162
+ }
@@ -0,0 +1,36 @@
1
+ import type { AuthIdentifier, AuthStorage } from './types.js';
2
+ import type { AuthCodeData, AuthCodeRequest, OAuthClient } from '../oauth/types.js';
3
+ import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from '../passkey/types.js';
4
+ import type { Token } from '../token/types.js';
5
+ export declare class BaseAuthStorage<UserRow = unknown, SafeUser = unknown> implements AuthStorage<UserRow, SafeUser> {
6
+ getUser(identifier: AuthIdentifier): Promise<UserRow | null>;
7
+ getUserPasswordHash(user: UserRow): string;
8
+ getUserId(user: UserRow): AuthIdentifier;
9
+ filterUser(user: UserRow): SafeUser;
10
+ verifyPassword(password: string, hash: string): Promise<boolean>;
11
+ storeToken(data: Token): Promise<void>;
12
+ getToken(query: Partial<Omit<Token, 'userId' | 'ruid'>> & {
13
+ userId?: string | number;
14
+ ruid?: string | number;
15
+ }, opts?: {
16
+ includeExpired?: boolean;
17
+ }): Promise<Token | null>;
18
+ deleteToken(query: Partial<Omit<Token, 'userId' | 'ruid'>> & {
19
+ userId?: string | number;
20
+ ruid?: string | number;
21
+ }): Promise<number>;
22
+ updateToken(updates: Partial<Token> & {
23
+ refreshToken: string;
24
+ }): Promise<boolean>;
25
+ createPasskeyChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
26
+ verifyPasskeyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
27
+ getClient(clientId: string): Promise<OAuthClient | null>;
28
+ verifyClientSecret(client: OAuthClient, clientSecret: string | null): Promise<boolean>;
29
+ createAuthCode(request: AuthCodeRequest): Promise<AuthCodeData>;
30
+ consumeAuthCode(code: string, clientId: string): Promise<AuthCodeData | null>;
31
+ canImpersonate(params: {
32
+ realUserId: AuthIdentifier;
33
+ effectiveUserId: AuthIdentifier;
34
+ }): Promise<boolean>;
35
+ }
36
+ export declare const nullAuthStorage: AuthStorage<unknown, unknown>;