@technomoron/apicore-server 1.0.0-beta.1

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/api-module.cjs +34 -0
  3. package/dist/cjs/api-module.d.ts +45 -0
  4. package/dist/cjs/apicore-server.cjs +1561 -0
  5. package/dist/cjs/apicore-server.d.ts +288 -0
  6. package/dist/cjs/auth-api/auth-module.cjs +1248 -0
  7. package/dist/cjs/auth-api/auth-module.d.ts +116 -0
  8. package/dist/cjs/auth-api/compat-auth-storage.cjs +128 -0
  9. package/dist/cjs/auth-api/compat-auth-storage.d.ts +57 -0
  10. package/dist/cjs/auth-api/mem-auth-store.cjs +121 -0
  11. package/dist/cjs/auth-api/mem-auth-store.d.ts +68 -0
  12. package/dist/cjs/auth-api/module.cjs +25 -0
  13. package/dist/cjs/auth-api/module.d.ts +20 -0
  14. package/dist/cjs/auth-api/schemas.cjs +171 -0
  15. package/dist/cjs/auth-api/schemas.d.ts +21 -0
  16. package/dist/cjs/auth-api/sql-auth-store.cjs +179 -0
  17. package/dist/cjs/auth-api/sql-auth-store.d.ts +87 -0
  18. package/dist/cjs/auth-api/storage.cjs +102 -0
  19. package/dist/cjs/auth-api/storage.d.ts +38 -0
  20. package/dist/cjs/auth-api/types.cjs +2 -0
  21. package/dist/cjs/auth-api/types.d.ts +34 -0
  22. package/dist/cjs/auth-api/user-id.cjs +47 -0
  23. package/dist/cjs/auth-api/user-id.d.ts +5 -0
  24. package/dist/cjs/auth-cookie-options.cjs +66 -0
  25. package/dist/cjs/auth-cookie-options.d.ts +13 -0
  26. package/dist/cjs/base/client-info.cjs +285 -0
  27. package/dist/cjs/base/client-info.d.ts +27 -0
  28. package/dist/cjs/base/error-utils.cjs +50 -0
  29. package/dist/cjs/base/error-utils.d.ts +16 -0
  30. package/dist/cjs/base/request-utils.cjs +27 -0
  31. package/dist/cjs/base/request-utils.d.ts +8 -0
  32. package/dist/cjs/index.cjs +51 -0
  33. package/dist/cjs/index.d.ts +34 -0
  34. package/dist/cjs/limiter/auth-rate-limiter.cjs +35 -0
  35. package/dist/cjs/limiter/auth-rate-limiter.d.ts +12 -0
  36. package/dist/cjs/limiter/fixed-window.cjs +41 -0
  37. package/dist/cjs/limiter/fixed-window.d.ts +11 -0
  38. package/dist/cjs/oauth/base.cjs +7 -0
  39. package/dist/cjs/oauth/base.d.ts +17 -0
  40. package/dist/cjs/oauth/memory.cjs +135 -0
  41. package/dist/cjs/oauth/memory.d.ts +22 -0
  42. package/dist/cjs/oauth/models.cjs +47 -0
  43. package/dist/cjs/oauth/models.d.ts +50 -0
  44. package/dist/cjs/oauth/sequelize.cjs +159 -0
  45. package/dist/cjs/oauth/sequelize.d.ts +30 -0
  46. package/dist/cjs/oauth/types.cjs +3 -0
  47. package/dist/cjs/oauth/types.d.ts +51 -0
  48. package/dist/cjs/passkey/base.cjs +7 -0
  49. package/dist/cjs/passkey/base.d.ts +28 -0
  50. package/dist/cjs/passkey/config.cjs +26 -0
  51. package/dist/cjs/passkey/config.d.ts +2 -0
  52. package/dist/cjs/passkey/memory.cjs +123 -0
  53. package/dist/cjs/passkey/memory.d.ts +34 -0
  54. package/dist/cjs/passkey/models.cjs +142 -0
  55. package/dist/cjs/passkey/models.d.ts +34 -0
  56. package/dist/cjs/passkey/sequelize.cjs +126 -0
  57. package/dist/cjs/passkey/sequelize.d.ts +42 -0
  58. package/dist/cjs/passkey/service.cjs +413 -0
  59. package/dist/cjs/passkey/service.d.ts +21 -0
  60. package/dist/cjs/passkey/types.cjs +2 -0
  61. package/dist/cjs/passkey/types.d.ts +84 -0
  62. package/dist/cjs/sequelize-utils.cjs +56 -0
  63. package/dist/cjs/sequelize-utils.d.ts +8 -0
  64. package/dist/cjs/token/base.cjs +120 -0
  65. package/dist/cjs/token/base.d.ts +46 -0
  66. package/dist/cjs/token/memory.cjs +234 -0
  67. package/dist/cjs/token/memory.d.ts +29 -0
  68. package/dist/cjs/token/sequelize.cjs +400 -0
  69. package/dist/cjs/token/sequelize.d.ts +58 -0
  70. package/dist/cjs/token/types.cjs +2 -0
  71. package/dist/cjs/token/types.d.ts +34 -0
  72. package/dist/cjs/upload/memory.cjs +92 -0
  73. package/dist/cjs/upload/memory.d.ts +17 -0
  74. package/dist/cjs/upload/tus-module.cjs +270 -0
  75. package/dist/cjs/upload/tus-module.d.ts +38 -0
  76. package/dist/cjs/upload/types.cjs +2 -0
  77. package/dist/cjs/upload/types.d.ts +28 -0
  78. package/dist/cjs/user/base.cjs +53 -0
  79. package/dist/cjs/user/base.d.ts +36 -0
  80. package/dist/cjs/user/memory.cjs +194 -0
  81. package/dist/cjs/user/memory.d.ts +37 -0
  82. package/dist/cjs/user/sequelize.cjs +194 -0
  83. package/dist/cjs/user/sequelize.d.ts +46 -0
  84. package/dist/cjs/user/types.cjs +2 -0
  85. package/dist/cjs/user/types.d.ts +11 -0
  86. package/dist/esm/api-module.d.ts +45 -0
  87. package/dist/esm/api-module.js +30 -0
  88. package/dist/esm/apicore-server.d.ts +288 -0
  89. package/dist/esm/apicore-server.js +1552 -0
  90. package/dist/esm/auth-api/auth-module.d.ts +116 -0
  91. package/dist/esm/auth-api/auth-module.js +1246 -0
  92. package/dist/esm/auth-api/compat-auth-storage.d.ts +57 -0
  93. package/dist/esm/auth-api/compat-auth-storage.js +124 -0
  94. package/dist/esm/auth-api/mem-auth-store.d.ts +68 -0
  95. package/dist/esm/auth-api/mem-auth-store.js +117 -0
  96. package/dist/esm/auth-api/module.d.ts +20 -0
  97. package/dist/esm/auth-api/module.js +21 -0
  98. package/dist/esm/auth-api/schemas.d.ts +21 -0
  99. package/dist/esm/auth-api/schemas.js +168 -0
  100. package/dist/esm/auth-api/sql-auth-store.d.ts +87 -0
  101. package/dist/esm/auth-api/sql-auth-store.js +175 -0
  102. package/dist/esm/auth-api/storage.d.ts +38 -0
  103. package/dist/esm/auth-api/storage.js +98 -0
  104. package/dist/esm/auth-api/types.d.ts +34 -0
  105. package/dist/esm/auth-api/types.js +1 -0
  106. package/dist/esm/auth-api/user-id.d.ts +5 -0
  107. package/dist/esm/auth-api/user-id.js +41 -0
  108. package/dist/esm/auth-cookie-options.d.ts +13 -0
  109. package/dist/esm/auth-cookie-options.js +63 -0
  110. package/dist/esm/base/client-info.d.ts +27 -0
  111. package/dist/esm/base/client-info.js +282 -0
  112. package/dist/esm/base/error-utils.d.ts +16 -0
  113. package/dist/esm/base/error-utils.js +44 -0
  114. package/dist/esm/base/request-utils.d.ts +8 -0
  115. package/dist/esm/base/request-utils.js +23 -0
  116. package/dist/esm/index.d.ts +34 -0
  117. package/dist/esm/index.js +21 -0
  118. package/dist/esm/limiter/auth-rate-limiter.d.ts +12 -0
  119. package/dist/esm/limiter/auth-rate-limiter.js +32 -0
  120. package/dist/esm/limiter/fixed-window.d.ts +11 -0
  121. package/dist/esm/limiter/fixed-window.js +37 -0
  122. package/dist/esm/oauth/base.d.ts +17 -0
  123. package/dist/esm/oauth/base.js +3 -0
  124. package/dist/esm/oauth/memory.d.ts +22 -0
  125. package/dist/esm/oauth/memory.js +128 -0
  126. package/dist/esm/oauth/models.d.ts +50 -0
  127. package/dist/esm/oauth/models.js +38 -0
  128. package/dist/esm/oauth/sequelize.d.ts +30 -0
  129. package/dist/esm/oauth/sequelize.js +148 -0
  130. package/dist/esm/oauth/types.d.ts +51 -0
  131. package/dist/esm/oauth/types.js +2 -0
  132. package/dist/esm/passkey/base.d.ts +28 -0
  133. package/dist/esm/passkey/base.js +3 -0
  134. package/dist/esm/passkey/config.d.ts +2 -0
  135. package/dist/esm/passkey/config.js +23 -0
  136. package/dist/esm/passkey/memory.d.ts +34 -0
  137. package/dist/esm/passkey/memory.js +119 -0
  138. package/dist/esm/passkey/models.d.ts +34 -0
  139. package/dist/esm/passkey/models.js +135 -0
  140. package/dist/esm/passkey/sequelize.d.ts +42 -0
  141. package/dist/esm/passkey/sequelize.js +122 -0
  142. package/dist/esm/passkey/service.d.ts +21 -0
  143. package/dist/esm/passkey/service.js +376 -0
  144. package/dist/esm/passkey/types.d.ts +84 -0
  145. package/dist/esm/passkey/types.js +1 -0
  146. package/dist/esm/sequelize-utils.d.ts +8 -0
  147. package/dist/esm/sequelize-utils.js +47 -0
  148. package/dist/esm/token/base.d.ts +46 -0
  149. package/dist/esm/token/base.js +113 -0
  150. package/dist/esm/token/memory.d.ts +29 -0
  151. package/dist/esm/token/memory.js +230 -0
  152. package/dist/esm/token/sequelize.d.ts +58 -0
  153. package/dist/esm/token/sequelize.js +396 -0
  154. package/dist/esm/token/types.d.ts +34 -0
  155. package/dist/esm/token/types.js +1 -0
  156. package/dist/esm/upload/memory.d.ts +17 -0
  157. package/dist/esm/upload/memory.js +86 -0
  158. package/dist/esm/upload/tus-module.d.ts +38 -0
  159. package/dist/esm/upload/tus-module.js +266 -0
  160. package/dist/esm/upload/types.d.ts +28 -0
  161. package/dist/esm/upload/types.js +1 -0
  162. package/dist/esm/user/base.d.ts +36 -0
  163. package/dist/esm/user/base.js +46 -0
  164. package/dist/esm/user/memory.d.ts +37 -0
  165. package/dist/esm/user/memory.js +190 -0
  166. package/dist/esm/user/sequelize.d.ts +46 -0
  167. package/dist/esm/user/sequelize.js +188 -0
  168. package/dist/esm/user/types.d.ts +11 -0
  169. package/dist/esm/user/types.js +1 -0
  170. package/docs/swagger/openapi.json +2162 -0
  171. package/package.json +131 -0
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthRateLimitHook = createAuthRateLimitHook;
4
+ const apicore_server_js_1 = require("../apicore-server.cjs");
5
+ const fixed_window_js_1 = require("./fixed-window.cjs");
6
+ function createAuthRateLimitHook(config, limiter) {
7
+ if (!config || Object.keys(config).length === 0) {
8
+ return () => { };
9
+ }
10
+ const instance = limiter ?? new fixed_window_js_1.FixedWindowRateLimiter();
11
+ return (ctx) => {
12
+ const endpointConfig = config[ctx.endpoint];
13
+ if (!endpointConfig) {
14
+ return;
15
+ }
16
+ const windowSec = endpointConfig.windowSec ?? 0;
17
+ const max = endpointConfig.max ?? 0;
18
+ if (windowSec <= 0 || max <= 0) {
19
+ return;
20
+ }
21
+ const clientIp = ctx.apiReq.getClientIp() ?? '';
22
+ if (!clientIp) {
23
+ return;
24
+ }
25
+ const key = `${ctx.endpoint}:${clientIp}`;
26
+ const decision = instance.check(key, max, windowSec * 1000);
27
+ if (!decision.allowed) {
28
+ throw new apicore_server_js_1.ApiError({
29
+ code: 429,
30
+ message: 'Too many requests; try again later',
31
+ data: { retryAfterSec: decision.retryAfterSec }
32
+ });
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,12 @@
1
+ import { type ApiRequest } from '../apicore-server.js';
2
+ import { FixedWindowRateLimiter } from './fixed-window.js';
3
+ type AuthRateLimitEndpoint = 'login' | 'passkey-challenge' | 'oauth-token' | 'oauth-authorize';
4
+ export interface AuthRateLimitConfig {
5
+ windowSec?: number;
6
+ max?: number;
7
+ }
8
+ export declare function createAuthRateLimitHook(config: Partial<Record<AuthRateLimitEndpoint, AuthRateLimitConfig>>, limiter?: FixedWindowRateLimiter): (ctx: {
9
+ apiReq: ApiRequest;
10
+ endpoint: string;
11
+ }) => void;
12
+ export {};
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FixedWindowRateLimiter = void 0;
4
+ class FixedWindowRateLimiter {
5
+ constructor(maxKeys = 10000) {
6
+ this.maxKeys = maxKeys;
7
+ this.buckets = new Map();
8
+ }
9
+ check(key, max, windowMs) {
10
+ if (!key || max <= 0 || windowMs <= 0) {
11
+ return { allowed: true, retryAfterSec: 0 };
12
+ }
13
+ const now = Date.now();
14
+ const bucket = this.buckets.get(key);
15
+ if (!bucket || now - bucket.windowStartMs >= windowMs) {
16
+ this.buckets.delete(key);
17
+ this.buckets.set(key, { windowStartMs: now, count: 1 });
18
+ this.prune();
19
+ return { allowed: true, retryAfterSec: 0 };
20
+ }
21
+ bucket.count += 1;
22
+ // Refresh insertion order to keep active entries at the end for pruning.
23
+ this.buckets.delete(key);
24
+ this.buckets.set(key, bucket);
25
+ if (bucket.count <= max) {
26
+ return { allowed: true, retryAfterSec: 0 };
27
+ }
28
+ const retryAfterSec = Math.max(1, Math.ceil((bucket.windowStartMs + windowMs - now) / 1000));
29
+ return { allowed: false, retryAfterSec };
30
+ }
31
+ prune() {
32
+ while (this.buckets.size > this.maxKeys) {
33
+ const oldest = this.buckets.keys().next().value;
34
+ if (!oldest) {
35
+ break;
36
+ }
37
+ this.buckets.delete(oldest);
38
+ }
39
+ }
40
+ }
41
+ exports.FixedWindowRateLimiter = FixedWindowRateLimiter;
@@ -0,0 +1,11 @@
1
+ export type RateLimitDecision = {
2
+ allowed: boolean;
3
+ retryAfterSec: number;
4
+ };
5
+ export declare class FixedWindowRateLimiter {
6
+ private readonly maxKeys;
7
+ private readonly buckets;
8
+ constructor(maxKeys?: number);
9
+ check(key: string, max: number, windowMs: number): RateLimitDecision;
10
+ private prune;
11
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthStore = void 0;
4
+ /** Base contract for OAuth client/auth-code persistence backends. */
5
+ class OAuthStore {
6
+ }
7
+ exports.OAuthStore = OAuthStore;
@@ -0,0 +1,17 @@
1
+ import type { AuthCode, OAuthClient } from './types.js';
2
+ /** Base contract for OAuth client/auth-code persistence backends. */
3
+ export declare abstract class OAuthStore {
4
+ /** Fetch an OAuth client by id. */
5
+ abstract getClient(clientId: string): Promise<OAuthClient | null>;
6
+ /** Create an OAuth client. */
7
+ abstract createClient(input: OAuthClient): Promise<OAuthClient>;
8
+ /** Verify an OAuth client secret. */
9
+ abstract verifyClientSecret(clientId: string, secret: string | null): Promise<boolean>;
10
+ /** Persist an authorization code. */
11
+ abstract createAuthCode(code: AuthCode): Promise<void>;
12
+ /** Consume an authorization code once. When clientId is provided, only consume if it matches. */
13
+ abstract consumeAuthCode(code: string, clientId?: string): Promise<AuthCode | null>;
14
+ /** Close underlying resources. */
15
+ abstract close(): Promise<void>;
16
+ }
17
+ export type { OAuthClient, AuthCode } from './types.js';
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MemoryOAuthStore = void 0;
7
+ const bcryptjs_1 = __importDefault(require("bcryptjs"));
8
+ const user_id_js_1 = require("../auth-api/user-id.cjs");
9
+ const base_js_1 = require("./base.cjs");
10
+ function cloneClient(client) {
11
+ if (!client) {
12
+ return null;
13
+ }
14
+ return {
15
+ clientId: client.clientId,
16
+ hasSecret: Boolean(client.clientSecret),
17
+ name: client.name,
18
+ redirectUris: [...client.redirectUris],
19
+ scope: client.scope ? [...client.scope] : undefined,
20
+ metadata: client.metadata ? JSON.parse(JSON.stringify(client.metadata)) : undefined,
21
+ firstParty: client.firstParty
22
+ };
23
+ }
24
+ function cloneCode(code) {
25
+ return {
26
+ ...code,
27
+ userId: (0, user_id_js_1.normalizeComparableUserId)(code.userId),
28
+ scope: code.scope ? [...code.scope] : undefined,
29
+ expiresAt: new Date(code.expiresAt),
30
+ metadata: code.metadata ? { ...code.metadata } : undefined
31
+ };
32
+ }
33
+ class MemoryOAuthStore extends base_js_1.OAuthStore {
34
+ constructor(options = {}) {
35
+ super();
36
+ this.clients = new Map();
37
+ this.codes = new Map();
38
+ this.bcryptRounds = options.bcryptRounds ?? 12;
39
+ this.maxClients =
40
+ typeof options.maxClients === 'number' && Number.isFinite(options.maxClients) && options.maxClients > 0
41
+ ? Math.floor(options.maxClients)
42
+ : undefined;
43
+ this.maxAuthCodes =
44
+ typeof options.maxAuthCodes === 'number' &&
45
+ Number.isFinite(options.maxAuthCodes) &&
46
+ options.maxAuthCodes > 0
47
+ ? Math.floor(options.maxAuthCodes)
48
+ : undefined;
49
+ }
50
+ async getClient(clientId) {
51
+ return cloneClient(this.clients.get(clientId));
52
+ }
53
+ async createClient(input) {
54
+ const clientSecret = input.clientSecret ? await bcryptjs_1.default.hash(input.clientSecret, this.bcryptRounds) : '';
55
+ const stored = {
56
+ clientId: input.clientId,
57
+ clientSecret,
58
+ name: input.name,
59
+ redirectUris: [...input.redirectUris],
60
+ scope: input.scope ? [...input.scope] : undefined,
61
+ metadata: input.metadata ? { ...input.metadata } : undefined,
62
+ firstParty: input.firstParty
63
+ };
64
+ this.clients.set(stored.clientId, stored);
65
+ this.enforceClientCapacity();
66
+ return cloneClient(stored);
67
+ }
68
+ async verifyClientSecret(clientId, secret) {
69
+ const client = this.clients.get(clientId);
70
+ if (!client) {
71
+ return false;
72
+ }
73
+ if (!client.clientSecret) {
74
+ return !secret || secret.length === 0;
75
+ }
76
+ if (!secret) {
77
+ return false;
78
+ }
79
+ return bcryptjs_1.default.compare(secret, client.clientSecret);
80
+ }
81
+ async createAuthCode(code) {
82
+ const record = {
83
+ ...code,
84
+ userId: (0, user_id_js_1.normalizeComparableUserId)(code.userId),
85
+ scope: code.scope ? [...code.scope] : undefined,
86
+ expiresAt: code.expiresAt,
87
+ metadata: code.metadata ? { ...code.metadata } : undefined
88
+ };
89
+ this.codes.set(record.code, record);
90
+ this.enforceCodeCapacity();
91
+ }
92
+ async consumeAuthCode(code, clientId) {
93
+ const record = this.codes.get(code);
94
+ if (!record) {
95
+ return null;
96
+ }
97
+ if (clientId && record.clientId !== clientId) {
98
+ return null;
99
+ }
100
+ if (record.expiresAt.getTime() <= Date.now()) {
101
+ this.codes.delete(code);
102
+ return null;
103
+ }
104
+ this.codes.delete(code);
105
+ return cloneCode(record);
106
+ }
107
+ async close() {
108
+ return;
109
+ }
110
+ enforceClientCapacity() {
111
+ if (!this.maxClients) {
112
+ return;
113
+ }
114
+ while (this.clients.size > this.maxClients) {
115
+ const oldest = this.clients.keys().next().value;
116
+ if (!oldest) {
117
+ return;
118
+ }
119
+ this.clients.delete(oldest);
120
+ }
121
+ }
122
+ enforceCodeCapacity() {
123
+ if (!this.maxAuthCodes) {
124
+ return;
125
+ }
126
+ while (this.codes.size > this.maxAuthCodes) {
127
+ const oldest = this.codes.keys().next().value;
128
+ if (!oldest) {
129
+ return;
130
+ }
131
+ this.codes.delete(oldest);
132
+ }
133
+ }
134
+ }
135
+ exports.MemoryOAuthStore = MemoryOAuthStore;
@@ -0,0 +1,22 @@
1
+ import { OAuthStore, type AuthCode, type OAuthClient } from './base.js';
2
+ export interface MemoryOAuthStoreOptions {
3
+ bcryptRounds?: number;
4
+ maxClients?: number;
5
+ maxAuthCodes?: number;
6
+ }
7
+ export declare class MemoryOAuthStore extends OAuthStore {
8
+ private readonly clients;
9
+ private readonly codes;
10
+ private readonly bcryptRounds;
11
+ private readonly maxClients?;
12
+ private readonly maxAuthCodes?;
13
+ constructor(options?: MemoryOAuthStoreOptions);
14
+ getClient(clientId: string): Promise<OAuthClient | null>;
15
+ createClient(input: OAuthClient): Promise<OAuthClient>;
16
+ verifyClientSecret(clientId: string, secret: string | null): Promise<boolean>;
17
+ createAuthCode(code: AuthCode): Promise<void>;
18
+ consumeAuthCode(code: string, clientId?: string): Promise<AuthCode | null>;
19
+ close(): Promise<void>;
20
+ private enforceClientCapacity;
21
+ private enforceCodeCapacity;
22
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthCodeModel = exports.OAuthClientModel = exports.tableOptions = exports.integerIdType = void 0;
4
+ exports.initOAuthClientModel = initOAuthClientModel;
5
+ exports.initOAuthCodeModel = initOAuthCodeModel;
6
+ const sequelize_1 = require("sequelize");
7
+ const sequelize_utils_js_1 = require("../sequelize-utils.cjs");
8
+ var sequelize_utils_js_2 = require("../sequelize-utils.cjs");
9
+ Object.defineProperty(exports, "integerIdType", { enumerable: true, get: function () { return sequelize_utils_js_2.integerIdType; } });
10
+ Object.defineProperty(exports, "tableOptions", { enumerable: true, get: function () { return sequelize_utils_js_2.tableOptions; } });
11
+ class OAuthClientModel extends sequelize_1.Model {
12
+ }
13
+ exports.OAuthClientModel = OAuthClientModel;
14
+ function initOAuthClientModel(sequelize, options = {}) {
15
+ OAuthClientModel.init({
16
+ client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
17
+ client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
18
+ name: { type: sequelize_1.DataTypes.STRING(128), allowNull: true, defaultValue: null },
19
+ redirect_uris: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
20
+ scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
21
+ metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
22
+ first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
23
+ }, {
24
+ ...(0, sequelize_utils_js_1.tableOptions)(sequelize, 'oauth_clients', options.tablePrefix, { timestamps: false })
25
+ });
26
+ return OAuthClientModel;
27
+ }
28
+ class OAuthCodeModel extends sequelize_1.Model {
29
+ }
30
+ exports.OAuthCodeModel = OAuthCodeModel;
31
+ function initOAuthCodeModel(sequelize, options = {}) {
32
+ const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
33
+ OAuthCodeModel.init({
34
+ code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
35
+ client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false },
36
+ user_id: { type: idType, allowNull: false },
37
+ redirect_uri: { type: sequelize_1.DataTypes.TEXT, allowNull: false },
38
+ scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
39
+ code_challenge: { type: sequelize_1.DataTypes.STRING(255), allowNull: true, defaultValue: null },
40
+ code_challenge_method: { type: sequelize_1.DataTypes.STRING(10), allowNull: true, defaultValue: null },
41
+ expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
42
+ metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
43
+ }, {
44
+ ...(0, sequelize_utils_js_1.tableOptions)(sequelize, 'oauth_codes', options.tablePrefix, { timestamps: false })
45
+ });
46
+ return OAuthCodeModel;
47
+ }
@@ -0,0 +1,50 @@
1
+ import { Model, type Optional, type Sequelize } from 'sequelize';
2
+ export { integerIdType, tableOptions } from '../sequelize-utils.js';
3
+ export interface OAuthClientAttributes {
4
+ client_id: string;
5
+ client_secret: string;
6
+ name: string | null;
7
+ redirect_uris: string;
8
+ scope: string;
9
+ metadata: string | null;
10
+ first_party: boolean;
11
+ }
12
+ export type OAuthClientCreationAttributes = Optional<OAuthClientAttributes, 'client_secret' | 'name' | 'scope' | 'metadata' | 'first_party'>;
13
+ export declare class OAuthClientModel extends Model<OAuthClientAttributes, OAuthClientCreationAttributes> implements OAuthClientAttributes {
14
+ client_id: string;
15
+ client_secret: string;
16
+ name: string | null;
17
+ redirect_uris: string;
18
+ scope: string;
19
+ metadata: string | null;
20
+ first_party: boolean;
21
+ }
22
+ export declare function initOAuthClientModel(sequelize: Sequelize, options?: {
23
+ tablePrefix?: string;
24
+ }): typeof OAuthClientModel;
25
+ export interface OAuthCodeAttributes {
26
+ code: string;
27
+ client_id: string;
28
+ user_id: number;
29
+ redirect_uri: string;
30
+ scope: string;
31
+ code_challenge: string | null;
32
+ code_challenge_method: 'plain' | 'S256' | null;
33
+ expires: Date;
34
+ metadata: string | null;
35
+ }
36
+ export type OAuthCodeCreationAttributes = Optional<OAuthCodeAttributes, 'code_challenge' | 'code_challenge_method' | 'metadata'>;
37
+ export declare class OAuthCodeModel extends Model<OAuthCodeAttributes, OAuthCodeCreationAttributes> implements OAuthCodeAttributes {
38
+ code: string;
39
+ client_id: string;
40
+ user_id: number;
41
+ redirect_uri: string;
42
+ scope: string;
43
+ code_challenge: string | null;
44
+ code_challenge_method: 'plain' | 'S256' | null;
45
+ expires: Date;
46
+ metadata: string | null;
47
+ }
48
+ export declare function initOAuthCodeModel(sequelize: Sequelize, options?: {
49
+ tablePrefix?: string;
50
+ }): typeof OAuthCodeModel;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SequelizeOAuthStore = exports.initOAuthCodeModel = exports.initOAuthClientModel = exports.OAuthCodeModel = exports.OAuthClientModel = void 0;
7
+ const bcryptjs_1 = __importDefault(require("bcryptjs"));
8
+ const sequelize_1 = require("sequelize");
9
+ const user_id_js_1 = require("../auth-api/user-id.cjs");
10
+ const sequelize_utils_js_1 = require("../sequelize-utils.cjs");
11
+ const base_js_1 = require("./base.cjs");
12
+ const models_js_1 = require("./models.cjs");
13
+ var models_js_2 = require("./models.cjs");
14
+ Object.defineProperty(exports, "OAuthClientModel", { enumerable: true, get: function () { return models_js_2.OAuthClientModel; } });
15
+ Object.defineProperty(exports, "OAuthCodeModel", { enumerable: true, get: function () { return models_js_2.OAuthCodeModel; } });
16
+ Object.defineProperty(exports, "initOAuthClientModel", { enumerable: true, get: function () { return models_js_2.initOAuthClientModel; } });
17
+ Object.defineProperty(exports, "initOAuthCodeModel", { enumerable: true, get: function () { return models_js_2.initOAuthCodeModel; } });
18
+ function serializeMetadata(metadata) {
19
+ if (!metadata) {
20
+ return null;
21
+ }
22
+ return JSON.stringify(metadata);
23
+ }
24
+ function parseMetadata(raw) {
25
+ if (!raw) {
26
+ return undefined;
27
+ }
28
+ try {
29
+ const parsed = JSON.parse(raw);
30
+ if (parsed && typeof parsed === 'object') {
31
+ return parsed;
32
+ }
33
+ }
34
+ catch {
35
+ // ignore
36
+ }
37
+ return undefined;
38
+ }
39
+ class SequelizeOAuthStore extends base_js_1.OAuthStore {
40
+ constructor(options) {
41
+ super();
42
+ if (!options?.sequelize) {
43
+ throw new Error('SequelizeOAuthStore requires an initialised Sequelize instance');
44
+ }
45
+ this.clients =
46
+ options.clientModel ??
47
+ (options.clientModelFactory ?? models_js_1.initOAuthClientModel)(options.sequelize, {
48
+ tablePrefix: options.tablePrefix
49
+ });
50
+ this.codes =
51
+ options.codeModel ??
52
+ (options.codeModelFactory ?? models_js_1.initOAuthCodeModel)(options.sequelize, {
53
+ tablePrefix: options.tablePrefix
54
+ });
55
+ this.bcryptRounds = options.bcryptRounds ?? 12;
56
+ }
57
+ async getClient(clientId) {
58
+ const model = await this.clients.findByPk(clientId);
59
+ return model ? this.toOAuthClient(model) : null;
60
+ }
61
+ async createClient(input) {
62
+ const existing = await this.clients.findByPk(input.clientId);
63
+ const hashedSecret = input.clientSecret !== undefined && input.clientSecret !== null
64
+ ? await bcryptjs_1.default.hash(input.clientSecret, this.bcryptRounds)
65
+ : (existing?.client_secret ?? '');
66
+ const redirectUris = input.redirectUris ?? (existing ? (0, sequelize_utils_js_1.decodeStringArray)(existing.redirect_uris) : undefined);
67
+ const scope = input.scope ?? (existing ? (0, sequelize_utils_js_1.decodeStringArray)(existing.scope) : undefined);
68
+ const metadata = input.metadata ?? (existing ? parseMetadata(existing.metadata) : undefined);
69
+ await this.clients.upsert({
70
+ client_id: input.clientId,
71
+ client_secret: hashedSecret,
72
+ name: input.name ?? existing?.name ?? null,
73
+ redirect_uris: (0, sequelize_utils_js_1.encodeStringArray)(redirectUris),
74
+ scope: (0, sequelize_utils_js_1.encodeStringArray)(scope),
75
+ metadata: serializeMetadata(metadata),
76
+ first_party: input.firstParty ?? existing?.first_party ?? false
77
+ });
78
+ const model = await this.clients.findByPk(input.clientId);
79
+ if (!model) {
80
+ throw new Error(`Unable to persist OAuth client ${input.clientId}`);
81
+ }
82
+ return this.toOAuthClient(model);
83
+ }
84
+ async verifyClientSecret(clientId, clientSecret) {
85
+ const model = await this.clients.findByPk(clientId);
86
+ if (!model) {
87
+ return false;
88
+ }
89
+ if (!model.client_secret) {
90
+ return !clientSecret || clientSecret.length === 0;
91
+ }
92
+ if (!clientSecret) {
93
+ return false;
94
+ }
95
+ return bcryptjs_1.default.compare(clientSecret, model.client_secret);
96
+ }
97
+ async createAuthCode(code) {
98
+ await this.codes.create({
99
+ code: code.code,
100
+ client_id: code.clientId,
101
+ user_id: (0, user_id_js_1.normalizeNumericUserId)(code.userId),
102
+ redirect_uri: code.redirectUri ?? '',
103
+ scope: (0, sequelize_utils_js_1.encodeStringArray)(code.scope),
104
+ code_challenge: code.codeChallenge ?? null,
105
+ code_challenge_method: code.codeChallengeMethod ?? null,
106
+ expires: code.expiresAt,
107
+ metadata: serializeMetadata(code.metadata)
108
+ });
109
+ }
110
+ async consumeAuthCode(code, clientId) {
111
+ const sequelize = this.codes.sequelize;
112
+ if (!sequelize) {
113
+ throw new Error('Code model is not bound to a Sequelize instance');
114
+ }
115
+ return sequelize.transaction({ isolationLevel: sequelize_1.Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async (transaction) => {
116
+ const where = { code };
117
+ if (clientId) {
118
+ where.client_id = clientId;
119
+ }
120
+ const model = await this.codes.findOne({ where, transaction, lock: true });
121
+ if (!model) {
122
+ return null;
123
+ }
124
+ await model.destroy({ transaction });
125
+ if (model.expires.getTime() <= Date.now()) {
126
+ return null;
127
+ }
128
+ return this.toAuthCode(model);
129
+ });
130
+ }
131
+ async close() {
132
+ return;
133
+ }
134
+ toOAuthClient(model) {
135
+ return {
136
+ clientId: model.client_id,
137
+ hasSecret: Boolean(model.client_secret),
138
+ name: model.name ?? undefined,
139
+ redirectUris: (0, sequelize_utils_js_1.decodeStringArray)(model.redirect_uris),
140
+ scope: (0, sequelize_utils_js_1.decodeStringArray)(model.scope),
141
+ metadata: parseMetadata(model.metadata),
142
+ firstParty: model.first_party ?? false
143
+ };
144
+ }
145
+ toAuthCode(model) {
146
+ return {
147
+ code: model.code,
148
+ clientId: model.client_id,
149
+ userId: String(model.user_id),
150
+ redirectUri: model.redirect_uri,
151
+ scope: (0, sequelize_utils_js_1.decodeStringArray)(model.scope),
152
+ codeChallenge: model.code_challenge ?? undefined,
153
+ codeChallengeMethod: model.code_challenge_method ?? undefined,
154
+ expiresAt: model.expires,
155
+ metadata: parseMetadata(model.metadata)
156
+ };
157
+ }
158
+ }
159
+ exports.SequelizeOAuthStore = SequelizeOAuthStore;
@@ -0,0 +1,30 @@
1
+ import { OAuthStore, type AuthCode, type OAuthClient } from './base.js';
2
+ import { OAuthClientModel, OAuthCodeModel } from './models.js';
3
+ export interface SequelizeOAuthStoreOptions {
4
+ sequelize: import('sequelize').Sequelize;
5
+ tablePrefix?: string;
6
+ clientModel?: typeof OAuthClientModel;
7
+ codeModel?: typeof OAuthCodeModel;
8
+ clientModelFactory?: (sequelize: import('sequelize').Sequelize, options?: {
9
+ tablePrefix?: string;
10
+ }) => typeof OAuthClientModel;
11
+ codeModelFactory?: (sequelize: import('sequelize').Sequelize, options?: {
12
+ tablePrefix?: string;
13
+ }) => typeof OAuthCodeModel;
14
+ bcryptRounds?: number;
15
+ }
16
+ export { OAuthClientModel, OAuthCodeModel, initOAuthClientModel, initOAuthCodeModel, type OAuthClientAttributes, type OAuthClientCreationAttributes, type OAuthCodeAttributes, type OAuthCodeCreationAttributes } from './models.js';
17
+ export declare class SequelizeOAuthStore extends OAuthStore {
18
+ private readonly clients;
19
+ private readonly codes;
20
+ private readonly bcryptRounds;
21
+ constructor(options: SequelizeOAuthStoreOptions);
22
+ getClient(clientId: string): Promise<OAuthClient | null>;
23
+ createClient(input: OAuthClient): Promise<OAuthClient>;
24
+ verifyClientSecret(clientId: string, clientSecret: string | null): Promise<boolean>;
25
+ createAuthCode(code: AuthCode): Promise<void>;
26
+ consumeAuthCode(code: string, clientId?: string): Promise<AuthCode | null>;
27
+ close(): Promise<void>;
28
+ private toOAuthClient;
29
+ private toAuthCode;
30
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // OAuth-specific contracts kept alongside the OAuth module.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,51 @@
1
+ import type { AuthIdentifier } from '../auth-api/types.js';
2
+ export interface OAuthClient {
3
+ clientId: string;
4
+ clientSecret?: string;
5
+ hasSecret?: boolean;
6
+ firstParty?: boolean;
7
+ metadata?: Record<string, unknown>;
8
+ name?: string;
9
+ redirectUris: string[];
10
+ scope?: string[];
11
+ }
12
+ export interface AuthCodeData {
13
+ code: string;
14
+ clientId: string;
15
+ codeChallenge?: string;
16
+ codeChallengeMethod?: 'plain' | 'S256';
17
+ expiresAt: Date;
18
+ metadata?: Record<string, unknown>;
19
+ redirectUri?: string;
20
+ scope?: string[];
21
+ userId: AuthIdentifier;
22
+ }
23
+ export type AuthCode = AuthCodeData;
24
+ export type AuthCodeRequest = Omit<AuthCodeData, 'code' | 'expiresAt'> & {
25
+ code?: string;
26
+ expiresInSeconds?: number;
27
+ };
28
+ export interface OAuthStartParams {
29
+ provider: string;
30
+ redirectUri?: string;
31
+ scope?: string | string[];
32
+ state?: string;
33
+ extras?: Record<string, unknown>;
34
+ }
35
+ export interface OAuthStartResult extends Record<string, unknown> {
36
+ url: string;
37
+ state?: string;
38
+ codeVerifier?: string;
39
+ }
40
+ export interface OAuthCallbackParams {
41
+ provider: string;
42
+ query: Record<string, string | string[]>;
43
+ body: Record<string, unknown>;
44
+ }
45
+ export interface OAuthCallbackResult<PublicUser> extends Record<string, unknown> {
46
+ user: PublicUser;
47
+ tokens?: {
48
+ accessToken: string;
49
+ refreshToken: string;
50
+ };
51
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PasskeyStore = void 0;
4
+ /** Base contract for passkey credential/challenge persistence backends. */
5
+ class PasskeyStore {
6
+ }
7
+ exports.PasskeyStore = PasskeyStore;
@@ -0,0 +1,28 @@
1
+ import type { PasskeyChallengeRecord, PasskeyStorageAdapter, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
2
+ import type { AuthIdentifier } from '../auth-api/types.js';
3
+ /** Base contract for passkey credential/challenge persistence backends. */
4
+ export declare abstract class PasskeyStore implements PasskeyStorageAdapter {
5
+ /** Resolve a passkey user descriptor by id/login. */
6
+ abstract resolveUser(params: {
7
+ userId?: AuthIdentifier;
8
+ login?: string;
9
+ }): Promise<PasskeyUserDescriptor | null>;
10
+ /** List passkey credentials for a user. */
11
+ abstract listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
12
+ /** Delete a credential by binary/base64url id. */
13
+ abstract deleteCredential(credentialId: Buffer | string): Promise<boolean>;
14
+ /** Find a credential by binary id. */
15
+ abstract findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
16
+ /** Save a credential record. */
17
+ abstract saveCredential(record: StoredPasskeyCredential): Promise<void>;
18
+ /** Update signature counter for a credential. */
19
+ abstract updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
20
+ /** Save a challenge record. */
21
+ abstract saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
22
+ /** Read a challenge without consuming it. */
23
+ abstract getChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
24
+ /** Consume and return a challenge. */
25
+ abstract consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
26
+ /** Cleanup expired challenges. */
27
+ abstract cleanupChallenges(now: Date): Promise<void>;
28
+ }