@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.
- package/dist/cjs/api-server-base.cjs +181 -74
- package/dist/cjs/api-server-base.d.ts +66 -29
- package/dist/cjs/auth-api/auth-module.d.ts +96 -0
- package/dist/cjs/auth-api/auth-module.js +1032 -0
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +55 -0
- package/dist/cjs/auth-api/compat-auth-storage.js +116 -0
- package/dist/cjs/auth-api/mem-auth-store.d.ts +66 -0
- package/dist/cjs/auth-api/mem-auth-store.js +135 -0
- package/dist/cjs/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
- package/dist/cjs/{auth-module.cjs → auth-api/module.js} +1 -1
- package/dist/cjs/auth-api/sql-auth-store.d.ts +75 -0
- package/dist/cjs/auth-api/sql-auth-store.js +166 -0
- package/dist/cjs/auth-api/storage.d.ts +36 -0
- package/dist/cjs/{auth-storage.cjs → auth-api/storage.js} +2 -2
- package/dist/cjs/auth-api/types.d.ts +29 -0
- package/dist/cjs/auth-api/types.js +2 -0
- package/dist/cjs/index.cjs +41 -7
- package/dist/cjs/index.d.ts +29 -5
- package/dist/cjs/oauth/base.d.ts +10 -0
- package/dist/cjs/oauth/base.js +6 -0
- package/dist/cjs/oauth/memory.d.ts +16 -0
- package/dist/cjs/oauth/memory.js +99 -0
- package/dist/cjs/oauth/models.d.ts +45 -0
- package/dist/cjs/oauth/models.js +58 -0
- package/dist/cjs/oauth/sequelize.d.ts +68 -0
- package/dist/cjs/oauth/sequelize.js +210 -0
- package/dist/cjs/oauth/types.d.ts +50 -0
- package/dist/cjs/oauth/types.js +3 -0
- package/dist/cjs/passkey/base.d.ts +15 -0
- package/dist/cjs/passkey/base.js +6 -0
- package/dist/cjs/passkey/memory.d.ts +26 -0
- package/dist/cjs/passkey/memory.js +82 -0
- package/dist/cjs/passkey/models.d.ts +25 -0
- package/dist/cjs/passkey/models.js +115 -0
- package/dist/cjs/passkey/sequelize.d.ts +54 -0
- package/dist/cjs/passkey/sequelize.js +211 -0
- package/dist/cjs/passkey/service.d.ts +17 -0
- package/dist/cjs/passkey/service.js +221 -0
- package/dist/cjs/passkey/types.d.ts +75 -0
- package/dist/cjs/passkey/types.js +2 -0
- package/dist/cjs/token/base.d.ts +38 -0
- package/dist/cjs/token/base.js +114 -0
- package/dist/cjs/token/memory.d.ts +19 -0
- package/dist/cjs/token/memory.js +149 -0
- package/dist/cjs/token/sequelize.d.ts +58 -0
- package/dist/cjs/token/sequelize.js +404 -0
- package/dist/cjs/token/types.d.ts +27 -0
- package/dist/cjs/token/types.js +2 -0
- package/dist/cjs/user/base.d.ts +26 -0
- package/dist/cjs/user/base.js +45 -0
- package/dist/cjs/user/memory.d.ts +35 -0
- package/dist/cjs/user/memory.js +173 -0
- package/dist/cjs/user/sequelize.d.ts +41 -0
- package/dist/cjs/user/sequelize.js +182 -0
- package/dist/cjs/user/types.d.ts +11 -0
- package/dist/cjs/user/types.js +2 -0
- package/dist/esm/api-server-base.d.ts +66 -29
- package/dist/esm/api-server-base.js +179 -72
- package/dist/esm/auth-api/auth-module.d.ts +96 -0
- package/dist/esm/auth-api/auth-module.js +1030 -0
- package/dist/esm/auth-api/compat-auth-storage.d.ts +55 -0
- package/dist/esm/auth-api/compat-auth-storage.js +112 -0
- package/dist/esm/auth-api/mem-auth-store.d.ts +66 -0
- package/dist/esm/auth-api/mem-auth-store.js +131 -0
- package/dist/esm/{auth-module.d.ts → auth-api/module.d.ts} +7 -7
- package/dist/esm/{auth-module.js → auth-api/module.js} +1 -1
- package/dist/esm/auth-api/sql-auth-store.d.ts +75 -0
- package/dist/esm/auth-api/sql-auth-store.js +162 -0
- package/dist/esm/auth-api/storage.d.ts +36 -0
- package/dist/esm/{auth-storage.js → auth-api/storage.js} +2 -2
- package/dist/esm/auth-api/types.d.ts +29 -0
- package/dist/esm/auth-api/types.js +1 -0
- package/dist/esm/index.d.ts +29 -5
- package/dist/esm/index.js +19 -2
- package/dist/esm/oauth/base.d.ts +10 -0
- package/dist/esm/oauth/base.js +2 -0
- package/dist/esm/oauth/memory.d.ts +16 -0
- package/dist/esm/oauth/memory.js +92 -0
- package/dist/esm/oauth/models.d.ts +45 -0
- package/dist/esm/oauth/models.js +51 -0
- package/dist/esm/oauth/sequelize.d.ts +68 -0
- package/dist/esm/oauth/sequelize.js +199 -0
- package/dist/esm/oauth/types.d.ts +50 -0
- package/dist/esm/oauth/types.js +2 -0
- package/dist/esm/passkey/base.d.ts +15 -0
- package/dist/esm/passkey/base.js +2 -0
- package/dist/esm/passkey/memory.d.ts +26 -0
- package/dist/esm/passkey/memory.js +78 -0
- package/dist/esm/passkey/models.d.ts +25 -0
- package/dist/esm/passkey/models.js +108 -0
- package/dist/esm/passkey/sequelize.d.ts +54 -0
- package/dist/esm/passkey/sequelize.js +207 -0
- package/dist/esm/passkey/service.d.ts +17 -0
- package/dist/esm/passkey/service.js +217 -0
- package/dist/esm/passkey/types.d.ts +75 -0
- package/dist/esm/passkey/types.js +1 -0
- package/dist/esm/token/base.d.ts +38 -0
- package/dist/esm/token/base.js +107 -0
- package/dist/esm/token/memory.d.ts +19 -0
- package/dist/esm/token/memory.js +145 -0
- package/dist/esm/token/sequelize.d.ts +58 -0
- package/dist/esm/token/sequelize.js +400 -0
- package/dist/esm/token/types.d.ts +27 -0
- package/dist/esm/token/types.js +1 -0
- package/dist/esm/user/base.d.ts +26 -0
- package/dist/esm/user/base.js +38 -0
- package/dist/esm/user/memory.d.ts +35 -0
- package/dist/esm/user/memory.js +169 -0
- package/dist/esm/user/sequelize.d.ts +41 -0
- package/dist/esm/user/sequelize.js +176 -0
- package/dist/esm/user/types.d.ts +11 -0
- package/dist/esm/user/types.js +1 -0
- package/package.json +11 -3
- package/dist/cjs/auth-storage.d.ts +0 -133
- package/dist/esm/auth-storage.d.ts +0 -133
|
@@ -0,0 +1,210 @@
|
|
|
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.OAuthCodeModel = exports.OAuthClientModel = void 0;
|
|
7
|
+
exports.initOAuthClientModel = initOAuthClientModel;
|
|
8
|
+
exports.initOAuthCodeModel = initOAuthCodeModel;
|
|
9
|
+
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
10
|
+
const sequelize_1 = require("sequelize");
|
|
11
|
+
const base_js_1 = require("./base.js");
|
|
12
|
+
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
13
|
+
function integerIdType(sequelize) {
|
|
14
|
+
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
15
|
+
}
|
|
16
|
+
function tableOptions(sequelize, tableName, extra) {
|
|
17
|
+
const opts = { sequelize, tableName };
|
|
18
|
+
if (extra) {
|
|
19
|
+
Object.assign(opts, extra);
|
|
20
|
+
}
|
|
21
|
+
if (DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
22
|
+
opts.charset = 'utf8mb4';
|
|
23
|
+
opts.collate = 'utf8mb4_unicode_ci';
|
|
24
|
+
}
|
|
25
|
+
return opts;
|
|
26
|
+
}
|
|
27
|
+
class OAuthClientModel extends sequelize_1.Model {
|
|
28
|
+
}
|
|
29
|
+
exports.OAuthClientModel = OAuthClientModel;
|
|
30
|
+
function initOAuthClientModel(sequelize) {
|
|
31
|
+
OAuthClientModel.init({
|
|
32
|
+
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
33
|
+
client_secret: { type: sequelize_1.DataTypes.STRING(255), allowNull: false, defaultValue: '' },
|
|
34
|
+
name: { type: sequelize_1.DataTypes.STRING(128), allowNull: true, defaultValue: null },
|
|
35
|
+
redirect_uris: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
36
|
+
scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
37
|
+
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null },
|
|
38
|
+
first_party: { type: sequelize_1.DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
|
|
39
|
+
}, tableOptions(sequelize, 'oauth_clients', { timestamps: false }));
|
|
40
|
+
return OAuthClientModel;
|
|
41
|
+
}
|
|
42
|
+
class OAuthCodeModel extends sequelize_1.Model {
|
|
43
|
+
}
|
|
44
|
+
exports.OAuthCodeModel = OAuthCodeModel;
|
|
45
|
+
function initOAuthCodeModel(sequelize) {
|
|
46
|
+
const idType = integerIdType(sequelize);
|
|
47
|
+
OAuthCodeModel.init({
|
|
48
|
+
code: { type: sequelize_1.DataTypes.STRING(128), allowNull: false, primaryKey: true },
|
|
49
|
+
client_id: { type: sequelize_1.DataTypes.STRING(128), allowNull: false },
|
|
50
|
+
user_id: { type: idType, allowNull: false },
|
|
51
|
+
redirect_uri: { type: sequelize_1.DataTypes.TEXT, allowNull: false },
|
|
52
|
+
scope: { type: sequelize_1.DataTypes.TEXT, allowNull: false, defaultValue: '[]' },
|
|
53
|
+
code_challenge: { type: sequelize_1.DataTypes.STRING(255), allowNull: true, defaultValue: null },
|
|
54
|
+
code_challenge_method: { type: sequelize_1.DataTypes.STRING(10), allowNull: true, defaultValue: null },
|
|
55
|
+
expires: { type: sequelize_1.DataTypes.DATE, allowNull: false },
|
|
56
|
+
metadata: { type: sequelize_1.DataTypes.TEXT, allowNull: true, defaultValue: null }
|
|
57
|
+
}, tableOptions(sequelize, 'oauth_codes', { timestamps: false }));
|
|
58
|
+
return OAuthCodeModel;
|
|
59
|
+
}
|
|
60
|
+
function encodeStringArray(values) {
|
|
61
|
+
return JSON.stringify(values ?? []);
|
|
62
|
+
}
|
|
63
|
+
function decodeStringArray(raw) {
|
|
64
|
+
if (!raw) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (Array.isArray(parsed)) {
|
|
70
|
+
return parsed.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// ignore malformed values
|
|
75
|
+
}
|
|
76
|
+
return raw
|
|
77
|
+
.split(/\s+/)
|
|
78
|
+
.map((entry) => entry.trim())
|
|
79
|
+
.filter((entry) => entry.length > 0);
|
|
80
|
+
}
|
|
81
|
+
function serializeMetadata(metadata) {
|
|
82
|
+
if (!metadata) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify(metadata);
|
|
86
|
+
}
|
|
87
|
+
function parseMetadata(raw) {
|
|
88
|
+
if (!raw) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(raw);
|
|
93
|
+
if (parsed && typeof parsed === 'object') {
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function normalizeUserId(identifier) {
|
|
103
|
+
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
104
|
+
return identifier;
|
|
105
|
+
}
|
|
106
|
+
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
107
|
+
return Number(identifier);
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
110
|
+
}
|
|
111
|
+
class SequelizeOAuthStore extends base_js_1.OAuthStore {
|
|
112
|
+
constructor(options) {
|
|
113
|
+
super();
|
|
114
|
+
if (!options?.sequelize) {
|
|
115
|
+
throw new Error('SequelizeOAuthStore requires an initialised Sequelize instance');
|
|
116
|
+
}
|
|
117
|
+
this.clients = options.clientModel ?? (options.clientModelFactory ?? initOAuthClientModel)(options.sequelize);
|
|
118
|
+
this.codes = options.codeModel ?? (options.codeModelFactory ?? initOAuthCodeModel)(options.sequelize);
|
|
119
|
+
this.bcryptRounds = options.bcryptRounds ?? 12;
|
|
120
|
+
}
|
|
121
|
+
async getClient(clientId) {
|
|
122
|
+
const model = await this.clients.findByPk(clientId);
|
|
123
|
+
return model ? this.toOAuthClient(model) : null;
|
|
124
|
+
}
|
|
125
|
+
async createClient(input) {
|
|
126
|
+
const existing = await this.clients.findByPk(input.clientId);
|
|
127
|
+
const hashedSecret = input.clientSecret !== undefined && input.clientSecret !== null
|
|
128
|
+
? await bcryptjs_1.default.hash(input.clientSecret, this.bcryptRounds)
|
|
129
|
+
: (existing?.client_secret ?? '');
|
|
130
|
+
const redirectUris = input.redirectUris ?? (existing ? decodeStringArray(existing.redirect_uris) : undefined);
|
|
131
|
+
const scope = input.scope ?? (existing ? decodeStringArray(existing.scope) : undefined);
|
|
132
|
+
const metadata = input.metadata ?? (existing ? parseMetadata(existing.metadata) : undefined);
|
|
133
|
+
await this.clients.upsert({
|
|
134
|
+
client_id: input.clientId,
|
|
135
|
+
client_secret: hashedSecret,
|
|
136
|
+
name: input.name ?? existing?.name ?? null,
|
|
137
|
+
redirect_uris: encodeStringArray(redirectUris),
|
|
138
|
+
scope: encodeStringArray(scope),
|
|
139
|
+
metadata: serializeMetadata(metadata),
|
|
140
|
+
first_party: input.firstParty ?? existing?.first_party ?? false
|
|
141
|
+
});
|
|
142
|
+
const model = await this.clients.findByPk(input.clientId);
|
|
143
|
+
if (!model) {
|
|
144
|
+
throw new Error(`Unable to persist OAuth client ${input.clientId}`);
|
|
145
|
+
}
|
|
146
|
+
return this.toOAuthClient(model);
|
|
147
|
+
}
|
|
148
|
+
async verifyClientSecret(clientId, clientSecret) {
|
|
149
|
+
const model = await this.clients.findByPk(clientId);
|
|
150
|
+
if (!model) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
if (!model.client_secret) {
|
|
154
|
+
return !clientSecret || clientSecret.length === 0;
|
|
155
|
+
}
|
|
156
|
+
if (!clientSecret) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return bcryptjs_1.default.compare(clientSecret, model.client_secret);
|
|
160
|
+
}
|
|
161
|
+
async createAuthCode(code) {
|
|
162
|
+
await this.codes.create({
|
|
163
|
+
code: code.code,
|
|
164
|
+
client_id: code.clientId,
|
|
165
|
+
user_id: normalizeUserId(code.userId),
|
|
166
|
+
redirect_uri: code.redirectUri ?? '',
|
|
167
|
+
scope: encodeStringArray(code.scope),
|
|
168
|
+
code_challenge: code.codeChallenge ?? null,
|
|
169
|
+
code_challenge_method: code.codeChallengeMethod ?? null,
|
|
170
|
+
expires: code.expiresAt,
|
|
171
|
+
metadata: serializeMetadata(code.metadata)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
async consumeAuthCode(code) {
|
|
175
|
+
const model = await this.codes.findByPk(code);
|
|
176
|
+
if (!model) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
await model.destroy();
|
|
180
|
+
return this.toAuthCode(model);
|
|
181
|
+
}
|
|
182
|
+
async close() {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
toOAuthClient(model) {
|
|
186
|
+
return {
|
|
187
|
+
clientId: model.client_id,
|
|
188
|
+
clientSecret: model.client_secret,
|
|
189
|
+
name: model.name ?? undefined,
|
|
190
|
+
redirectUris: decodeStringArray(model.redirect_uris),
|
|
191
|
+
scope: decodeStringArray(model.scope),
|
|
192
|
+
metadata: parseMetadata(model.metadata),
|
|
193
|
+
firstParty: model.first_party ?? false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
toAuthCode(model) {
|
|
197
|
+
return {
|
|
198
|
+
code: model.code,
|
|
199
|
+
clientId: model.client_id,
|
|
200
|
+
userId: model.user_id,
|
|
201
|
+
redirectUri: model.redirect_uri,
|
|
202
|
+
scope: decodeStringArray(model.scope),
|
|
203
|
+
codeChallenge: model.code_challenge ?? undefined,
|
|
204
|
+
codeChallengeMethod: model.code_challenge_method ?? undefined,
|
|
205
|
+
expiresAt: model.expires,
|
|
206
|
+
metadata: parseMetadata(model.metadata)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.SequelizeOAuthStore = SequelizeOAuthStore;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AuthIdentifier } from '../auth-api/types.js';
|
|
2
|
+
export interface OAuthClient {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret?: string;
|
|
5
|
+
firstParty?: boolean;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
name?: string;
|
|
8
|
+
redirectUris: string[];
|
|
9
|
+
scope?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface AuthCodeData {
|
|
12
|
+
code: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
codeChallenge?: string;
|
|
15
|
+
codeChallengeMethod?: 'plain' | 'S256';
|
|
16
|
+
expiresAt: Date;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
redirectUri?: string;
|
|
19
|
+
scope?: string[];
|
|
20
|
+
userId: AuthIdentifier;
|
|
21
|
+
}
|
|
22
|
+
export type AuthCode = AuthCodeData;
|
|
23
|
+
export type AuthCodeRequest = Omit<AuthCodeData, 'code' | 'expiresAt'> & {
|
|
24
|
+
code?: string;
|
|
25
|
+
expiresInSeconds?: number;
|
|
26
|
+
};
|
|
27
|
+
export interface OAuthStartParams {
|
|
28
|
+
provider: string;
|
|
29
|
+
redirectUri?: string;
|
|
30
|
+
scope?: string | string[];
|
|
31
|
+
state?: string;
|
|
32
|
+
extras?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export interface OAuthStartResult extends Record<string, unknown> {
|
|
35
|
+
url: string;
|
|
36
|
+
state?: string;
|
|
37
|
+
codeVerifier?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface OAuthCallbackParams {
|
|
40
|
+
provider: string;
|
|
41
|
+
query: Record<string, string | string[]>;
|
|
42
|
+
body: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
export interface OAuthCallbackResult<PublicUser> extends Record<string, unknown> {
|
|
45
|
+
user: PublicUser;
|
|
46
|
+
tokens?: {
|
|
47
|
+
accessToken: string;
|
|
48
|
+
refreshToken: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PasskeyChallengeRecord, PasskeyStorageAdapter, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
|
|
2
|
+
import type { AuthIdentifier } from '../auth-api/types.js';
|
|
3
|
+
export declare abstract class PasskeyStore implements PasskeyStorageAdapter {
|
|
4
|
+
abstract resolveUser(params: {
|
|
5
|
+
userId?: AuthIdentifier;
|
|
6
|
+
login?: string;
|
|
7
|
+
}): Promise<PasskeyUserDescriptor | null>;
|
|
8
|
+
abstract listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
9
|
+
abstract findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
10
|
+
abstract saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
11
|
+
abstract updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
12
|
+
abstract saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
13
|
+
abstract consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
14
|
+
abstract cleanupChallenges(now: Date): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PasskeyStore } from './base.js';
|
|
2
|
+
import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
|
|
3
|
+
import type { AuthIdentifier } from '../auth-api/types.js';
|
|
4
|
+
export interface MemoryPasskeyStoreOptions {
|
|
5
|
+
resolveUser: (params: {
|
|
6
|
+
userId?: AuthIdentifier;
|
|
7
|
+
login?: string;
|
|
8
|
+
}) => Promise<PasskeyUserDescriptor | null>;
|
|
9
|
+
}
|
|
10
|
+
export declare class MemoryPasskeyStore extends PasskeyStore {
|
|
11
|
+
private readonly resolveUserFn;
|
|
12
|
+
private readonly credentials;
|
|
13
|
+
private readonly challenges;
|
|
14
|
+
constructor(options: MemoryPasskeyStoreOptions);
|
|
15
|
+
resolveUser(params: {
|
|
16
|
+
userId?: AuthIdentifier;
|
|
17
|
+
login?: string;
|
|
18
|
+
}): Promise<PasskeyUserDescriptor | null>;
|
|
19
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
20
|
+
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
21
|
+
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
22
|
+
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
23
|
+
saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
24
|
+
consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
25
|
+
cleanupChallenges(now: Date): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryPasskeyStore = void 0;
|
|
4
|
+
const base_js_1 = require("./base.js");
|
|
5
|
+
function encodeCredentialId(value) {
|
|
6
|
+
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
7
|
+
}
|
|
8
|
+
function normalizeUserId(identifier) {
|
|
9
|
+
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
10
|
+
return identifier;
|
|
11
|
+
}
|
|
12
|
+
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
13
|
+
return Number(identifier);
|
|
14
|
+
}
|
|
15
|
+
return identifier;
|
|
16
|
+
}
|
|
17
|
+
function cloneCredential(record) {
|
|
18
|
+
return {
|
|
19
|
+
...record,
|
|
20
|
+
credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
|
|
21
|
+
transports: record.transports ? [...record.transports] : undefined
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
super();
|
|
27
|
+
this.credentials = new Map();
|
|
28
|
+
this.challenges = new Map();
|
|
29
|
+
this.resolveUserFn = options.resolveUser;
|
|
30
|
+
}
|
|
31
|
+
async resolveUser(params) {
|
|
32
|
+
return this.resolveUserFn(params);
|
|
33
|
+
}
|
|
34
|
+
async listUserCredentials(userId) {
|
|
35
|
+
const normalizedUserId = normalizeUserId(userId);
|
|
36
|
+
return [...this.credentials.values()]
|
|
37
|
+
.filter((record) => normalizeUserId(record.userId) === normalizedUserId)
|
|
38
|
+
.map((record) => cloneCredential(record));
|
|
39
|
+
}
|
|
40
|
+
async findCredentialById(credentialId) {
|
|
41
|
+
const record = this.credentials.get(encodeCredentialId(credentialId));
|
|
42
|
+
return record ? cloneCredential(record) : null;
|
|
43
|
+
}
|
|
44
|
+
async saveCredential(record) {
|
|
45
|
+
this.credentials.set(encodeCredentialId(record.credentialId), {
|
|
46
|
+
...record,
|
|
47
|
+
userId: normalizeUserId(record.userId),
|
|
48
|
+
credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
|
|
49
|
+
transports: record.transports ? [...record.transports] : undefined
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async updateCredentialCounter(credentialId, counter) {
|
|
53
|
+
const key = encodeCredentialId(credentialId);
|
|
54
|
+
const existing = this.credentials.get(key);
|
|
55
|
+
if (existing) {
|
|
56
|
+
existing.counter = counter;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async saveChallenge(record) {
|
|
60
|
+
this.challenges.set(record.challenge, {
|
|
61
|
+
...record,
|
|
62
|
+
userId: record.userId !== undefined ? normalizeUserId(record.userId) : undefined,
|
|
63
|
+
metadata: record.metadata ? { ...record.metadata } : {}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async consumeChallenge(challenge) {
|
|
67
|
+
const record = this.challenges.get(challenge);
|
|
68
|
+
if (!record) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
this.challenges.delete(challenge);
|
|
72
|
+
return { ...record, metadata: record.metadata ? { ...record.metadata } : {} };
|
|
73
|
+
}
|
|
74
|
+
async cleanupChallenges(now) {
|
|
75
|
+
for (const [challenge, record] of this.challenges.entries()) {
|
|
76
|
+
if (record.expiresAt && new Date(record.expiresAt) <= now) {
|
|
77
|
+
this.challenges.delete(challenge);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.MemoryPasskeyStore = MemoryPasskeyStore;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
|
|
2
|
+
import type { PasskeyChallengeMetadata } from './service.js';
|
|
3
|
+
export declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
|
|
4
|
+
credentialId: Buffer;
|
|
5
|
+
userId: number;
|
|
6
|
+
publicKey: Buffer;
|
|
7
|
+
counter: number;
|
|
8
|
+
transports: string[] | null;
|
|
9
|
+
backedUp: boolean;
|
|
10
|
+
deviceType: string;
|
|
11
|
+
createdAt?: Date;
|
|
12
|
+
updatedAt?: Date;
|
|
13
|
+
}
|
|
14
|
+
export declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallengeModel>, InferCreationAttributes<PasskeyChallengeModel>> {
|
|
15
|
+
challenge: string;
|
|
16
|
+
action: 'register' | 'authenticate';
|
|
17
|
+
userId: number | null;
|
|
18
|
+
login: string | null;
|
|
19
|
+
metadata: PasskeyChallengeMetadata | null;
|
|
20
|
+
expiresAt: Date;
|
|
21
|
+
createdAt?: Date;
|
|
22
|
+
updatedAt?: Date;
|
|
23
|
+
}
|
|
24
|
+
export declare function initPasskeyCredentialModel(sequelize: Sequelize): ModelStatic<PasskeyCredentialModel>;
|
|
25
|
+
export declare function initPasskeyChallengeModel(sequelize: Sequelize): ModelStatic<PasskeyChallengeModel>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PasskeyChallengeModel = exports.PasskeyCredentialModel = void 0;
|
|
4
|
+
exports.initPasskeyCredentialModel = initPasskeyCredentialModel;
|
|
5
|
+
exports.initPasskeyChallengeModel = initPasskeyChallengeModel;
|
|
6
|
+
const sequelize_1 = require("sequelize");
|
|
7
|
+
const DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
8
|
+
function integerIdType(sequelize) {
|
|
9
|
+
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
|
+
}
|
|
11
|
+
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
12
|
+
}
|
|
13
|
+
exports.PasskeyCredentialModel = PasskeyCredentialModel;
|
|
14
|
+
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
15
|
+
}
|
|
16
|
+
exports.PasskeyChallengeModel = PasskeyChallengeModel;
|
|
17
|
+
function initPasskeyCredentialModel(sequelize) {
|
|
18
|
+
const idType = integerIdType(sequelize);
|
|
19
|
+
return PasskeyCredentialModel.init({
|
|
20
|
+
credentialId: {
|
|
21
|
+
field: 'credential_id',
|
|
22
|
+
type: sequelize_1.DataTypes.STRING(768),
|
|
23
|
+
primaryKey: true,
|
|
24
|
+
allowNull: false,
|
|
25
|
+
get() {
|
|
26
|
+
const raw = this.getDataValue('credentialId');
|
|
27
|
+
if (!raw) {
|
|
28
|
+
return raw;
|
|
29
|
+
}
|
|
30
|
+
if (Buffer.isBuffer(raw)) {
|
|
31
|
+
return raw;
|
|
32
|
+
}
|
|
33
|
+
return Buffer.from(raw, 'base64');
|
|
34
|
+
},
|
|
35
|
+
set(value) {
|
|
36
|
+
const encoded = typeof value === 'string' ? value : value.toString('base64');
|
|
37
|
+
this.setDataValue('credentialId', encoded);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
userId: {
|
|
41
|
+
field: 'user_id',
|
|
42
|
+
type: idType,
|
|
43
|
+
allowNull: false
|
|
44
|
+
},
|
|
45
|
+
publicKey: {
|
|
46
|
+
field: 'public_key',
|
|
47
|
+
type: sequelize_1.DataTypes.BLOB,
|
|
48
|
+
allowNull: false
|
|
49
|
+
},
|
|
50
|
+
counter: {
|
|
51
|
+
type: sequelize_1.DataTypes.INTEGER,
|
|
52
|
+
allowNull: false,
|
|
53
|
+
defaultValue: 0
|
|
54
|
+
},
|
|
55
|
+
transports: {
|
|
56
|
+
type: sequelize_1.DataTypes.JSON,
|
|
57
|
+
allowNull: true
|
|
58
|
+
},
|
|
59
|
+
backedUp: {
|
|
60
|
+
field: 'backed_up',
|
|
61
|
+
type: sequelize_1.DataTypes.BOOLEAN,
|
|
62
|
+
allowNull: false,
|
|
63
|
+
defaultValue: false
|
|
64
|
+
},
|
|
65
|
+
deviceType: {
|
|
66
|
+
field: 'device_type',
|
|
67
|
+
type: sequelize_1.DataTypes.STRING(32),
|
|
68
|
+
allowNull: false,
|
|
69
|
+
defaultValue: 'multiDevice'
|
|
70
|
+
}
|
|
71
|
+
}, {
|
|
72
|
+
sequelize,
|
|
73
|
+
tableName: 'passkey_credentials',
|
|
74
|
+
timestamps: true,
|
|
75
|
+
underscored: true
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function initPasskeyChallengeModel(sequelize) {
|
|
79
|
+
const idType = integerIdType(sequelize);
|
|
80
|
+
return PasskeyChallengeModel.init({
|
|
81
|
+
challenge: {
|
|
82
|
+
type: sequelize_1.DataTypes.STRING(255),
|
|
83
|
+
primaryKey: true,
|
|
84
|
+
allowNull: false
|
|
85
|
+
},
|
|
86
|
+
action: {
|
|
87
|
+
type: sequelize_1.DataTypes.STRING(16),
|
|
88
|
+
allowNull: false
|
|
89
|
+
},
|
|
90
|
+
userId: {
|
|
91
|
+
field: 'user_id',
|
|
92
|
+
type: idType,
|
|
93
|
+
allowNull: true
|
|
94
|
+
},
|
|
95
|
+
login: {
|
|
96
|
+
type: sequelize_1.DataTypes.STRING(128),
|
|
97
|
+
allowNull: true
|
|
98
|
+
},
|
|
99
|
+
metadata: {
|
|
100
|
+
type: sequelize_1.DataTypes.JSON,
|
|
101
|
+
allowNull: true
|
|
102
|
+
},
|
|
103
|
+
expiresAt: {
|
|
104
|
+
field: 'expires_at',
|
|
105
|
+
type: sequelize_1.DataTypes.DATE,
|
|
106
|
+
allowNull: false
|
|
107
|
+
}
|
|
108
|
+
}, {
|
|
109
|
+
sequelize,
|
|
110
|
+
tableName: 'passkey_challenges',
|
|
111
|
+
timestamps: true,
|
|
112
|
+
underscored: true,
|
|
113
|
+
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
|
|
2
|
+
import { PasskeyStore } from './base.js';
|
|
3
|
+
import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
|
|
4
|
+
import type { AuthIdentifier } from '../auth-api/types.js';
|
|
5
|
+
declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
|
|
6
|
+
credentialId: Buffer;
|
|
7
|
+
userId: number;
|
|
8
|
+
publicKey: Buffer;
|
|
9
|
+
counter: number;
|
|
10
|
+
transports: string[] | null;
|
|
11
|
+
backedUp: boolean;
|
|
12
|
+
deviceType: string;
|
|
13
|
+
createdAt?: Date;
|
|
14
|
+
updatedAt?: Date;
|
|
15
|
+
}
|
|
16
|
+
declare class PasskeyChallengeModel extends Model<InferAttributes<PasskeyChallengeModel>, InferCreationAttributes<PasskeyChallengeModel>> {
|
|
17
|
+
challenge: string;
|
|
18
|
+
action: 'register' | 'authenticate';
|
|
19
|
+
userId: number | null;
|
|
20
|
+
login: string | null;
|
|
21
|
+
metadata: Record<string, unknown> | null;
|
|
22
|
+
expiresAt: Date;
|
|
23
|
+
createdAt?: Date;
|
|
24
|
+
updatedAt?: Date;
|
|
25
|
+
}
|
|
26
|
+
export interface SequelizePasskeyStoreOptions {
|
|
27
|
+
sequelize: Sequelize;
|
|
28
|
+
credentialModel?: ModelStatic<PasskeyCredentialModel>;
|
|
29
|
+
challengeModel?: ModelStatic<PasskeyChallengeModel>;
|
|
30
|
+
credentialModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyCredentialModel>;
|
|
31
|
+
challengeModelFactory?: (sequelize: Sequelize) => ModelStatic<PasskeyChallengeModel>;
|
|
32
|
+
resolveUser: (params: {
|
|
33
|
+
userId?: AuthIdentifier;
|
|
34
|
+
login?: string;
|
|
35
|
+
}) => Promise<PasskeyUserDescriptor | null>;
|
|
36
|
+
}
|
|
37
|
+
export declare class SequelizePasskeyStore extends PasskeyStore {
|
|
38
|
+
private readonly resolveUserFn;
|
|
39
|
+
private readonly credentials;
|
|
40
|
+
private readonly challenges;
|
|
41
|
+
constructor(options: SequelizePasskeyStoreOptions);
|
|
42
|
+
resolveUser(params: {
|
|
43
|
+
userId?: AuthIdentifier;
|
|
44
|
+
login?: string;
|
|
45
|
+
}): Promise<PasskeyUserDescriptor | null>;
|
|
46
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
47
|
+
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
48
|
+
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
49
|
+
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
50
|
+
saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
51
|
+
consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
52
|
+
cleanupChallenges(now: Date): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|