@technomoron/api-server-base 2.0.0-beta.2 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.txt +81 -28
- package/dist/cjs/api-module.cjs +9 -0
- package/dist/cjs/api-module.d.ts +7 -4
- package/dist/cjs/api-server-base.cjs +607 -99
- package/dist/cjs/api-server-base.d.ts +80 -23
- package/dist/cjs/auth-api/auth-module.d.ts +23 -3
- package/dist/cjs/auth-api/auth-module.js +320 -124
- package/dist/cjs/auth-api/compat-auth-storage.d.ts +7 -5
- package/dist/cjs/auth-api/compat-auth-storage.js +15 -3
- package/dist/cjs/auth-api/mem-auth-store.d.ts +5 -3
- package/dist/cjs/auth-api/mem-auth-store.js +14 -28
- package/dist/cjs/auth-api/module.d.ts +1 -1
- package/dist/cjs/auth-api/sql-auth-store.d.ts +16 -4
- package/dist/cjs/auth-api/sql-auth-store.js +43 -30
- package/dist/cjs/auth-api/storage.d.ts +6 -4
- package/dist/cjs/auth-api/storage.js +15 -5
- package/dist/cjs/auth-api/types.d.ts +7 -2
- package/dist/cjs/auth-api/user-id.d.ts +5 -0
- package/dist/cjs/auth-api/user-id.js +38 -0
- package/dist/cjs/auth-cookie-options.d.ts +11 -0
- package/dist/cjs/auth-cookie-options.js +66 -0
- package/dist/cjs/index.cjs +4 -14
- package/dist/cjs/index.d.ts +4 -9
- package/dist/cjs/oauth/memory.d.ts +6 -0
- package/dist/cjs/oauth/memory.js +44 -11
- package/dist/cjs/oauth/models.d.ts +7 -2
- package/dist/cjs/oauth/models.js +10 -21
- package/dist/cjs/oauth/sequelize.d.ts +10 -48
- package/dist/cjs/oauth/sequelize.js +44 -99
- package/dist/cjs/oauth/types.d.ts +1 -0
- package/dist/cjs/passkey/base.d.ts +2 -0
- package/dist/cjs/passkey/config.d.ts +2 -0
- package/dist/cjs/passkey/config.js +26 -0
- package/dist/cjs/passkey/memory.d.ts +8 -0
- package/dist/cjs/passkey/memory.js +57 -16
- package/dist/cjs/passkey/models.d.ts +13 -4
- package/dist/cjs/passkey/models.js +41 -14
- package/dist/cjs/passkey/sequelize.d.ts +13 -25
- package/dist/cjs/passkey/sequelize.js +68 -153
- package/dist/cjs/passkey/service.d.ts +6 -2
- package/dist/cjs/passkey/service.js +205 -27
- package/dist/cjs/passkey/types.d.ts +18 -9
- package/dist/cjs/sequelize-utils.d.ts +8 -0
- package/dist/cjs/sequelize-utils.js +57 -0
- package/dist/cjs/token/base.d.ts +2 -1
- package/dist/cjs/token/base.js +3 -1
- package/dist/cjs/token/memory.d.ts +10 -0
- package/dist/cjs/token/memory.js +122 -32
- package/dist/cjs/token/sequelize.d.ts +4 -4
- package/dist/cjs/token/sequelize.js +67 -85
- package/dist/cjs/token/types.d.ts +8 -1
- package/dist/cjs/user/base.d.ts +1 -0
- package/dist/cjs/user/base.js +11 -4
- package/dist/cjs/user/memory.d.ts +2 -0
- package/dist/cjs/user/memory.js +9 -10
- package/dist/cjs/user/sequelize.d.ts +7 -2
- package/dist/cjs/user/sequelize.js +19 -32
- package/dist/esm/api-module.d.ts +7 -4
- package/dist/esm/api-module.js +9 -0
- package/dist/esm/api-server-base.d.ts +80 -23
- package/dist/esm/api-server-base.js +608 -100
- package/dist/esm/auth-api/auth-module.d.ts +23 -3
- package/dist/esm/auth-api/auth-module.js +321 -125
- package/dist/esm/auth-api/compat-auth-storage.d.ts +7 -5
- package/dist/esm/auth-api/compat-auth-storage.js +13 -1
- package/dist/esm/auth-api/mem-auth-store.d.ts +5 -3
- package/dist/esm/auth-api/mem-auth-store.js +14 -28
- package/dist/esm/auth-api/module.d.ts +1 -1
- package/dist/esm/auth-api/sql-auth-store.d.ts +16 -4
- package/dist/esm/auth-api/sql-auth-store.js +43 -30
- package/dist/esm/auth-api/storage.d.ts +6 -4
- package/dist/esm/auth-api/storage.js +13 -3
- package/dist/esm/auth-api/types.d.ts +7 -2
- package/dist/esm/auth-api/user-id.d.ts +5 -0
- package/dist/esm/auth-api/user-id.js +32 -0
- package/dist/esm/auth-cookie-options.d.ts +11 -0
- package/dist/esm/auth-cookie-options.js +63 -0
- package/dist/esm/index.d.ts +4 -9
- package/dist/esm/index.js +2 -7
- package/dist/esm/oauth/memory.d.ts +6 -0
- package/dist/esm/oauth/memory.js +44 -11
- package/dist/esm/oauth/models.d.ts +7 -2
- package/dist/esm/oauth/models.js +6 -19
- package/dist/esm/oauth/sequelize.d.ts +10 -48
- package/dist/esm/oauth/sequelize.js +32 -87
- package/dist/esm/oauth/types.d.ts +1 -0
- package/dist/esm/passkey/base.d.ts +2 -0
- package/dist/esm/passkey/config.d.ts +2 -0
- package/dist/esm/passkey/config.js +23 -0
- package/dist/esm/passkey/memory.d.ts +8 -0
- package/dist/esm/passkey/memory.js +57 -16
- package/dist/esm/passkey/models.d.ts +13 -4
- package/dist/esm/passkey/models.js +39 -12
- package/dist/esm/passkey/sequelize.d.ts +13 -25
- package/dist/esm/passkey/sequelize.js +69 -154
- package/dist/esm/passkey/service.d.ts +6 -2
- package/dist/esm/passkey/service.js +173 -28
- package/dist/esm/passkey/types.d.ts +18 -9
- package/dist/esm/sequelize-utils.d.ts +8 -0
- package/dist/esm/sequelize-utils.js +48 -0
- package/dist/esm/token/base.d.ts +2 -1
- package/dist/esm/token/base.js +3 -1
- package/dist/esm/token/memory.d.ts +10 -0
- package/dist/esm/token/memory.js +122 -32
- package/dist/esm/token/sequelize.d.ts +4 -4
- package/dist/esm/token/sequelize.js +67 -85
- package/dist/esm/token/types.d.ts +8 -1
- package/dist/esm/user/base.d.ts +1 -0
- package/dist/esm/user/base.js +11 -4
- package/dist/esm/user/memory.d.ts +2 -0
- package/dist/esm/user/memory.js +9 -10
- package/dist/esm/user/sequelize.d.ts +7 -2
- package/dist/esm/user/sequelize.js +19 -32
- package/docs/swagger/openapi.json +1876 -0
- package/package.json +84 -34
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizePasskeyConfig = normalizePasskeyConfig;
|
|
4
|
+
const DEFAULT_PASSKEY_CONFIG = {
|
|
5
|
+
rpId: 'localhost',
|
|
6
|
+
rpName: 'API Server',
|
|
7
|
+
origins: ['http://localhost:5173'],
|
|
8
|
+
timeoutMs: 5 * 60 * 1000,
|
|
9
|
+
userVerification: 'preferred'
|
|
10
|
+
};
|
|
11
|
+
function isOriginString(origin) {
|
|
12
|
+
return typeof origin === 'string' && origin.trim().length > 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizePasskeyConfig(config = {}) {
|
|
15
|
+
const candidateOrigins = Array.isArray(config.origins) && config.origins.length > 0 ? config.origins.filter(isOriginString) : null;
|
|
16
|
+
return {
|
|
17
|
+
rpId: config.rpId?.trim() || DEFAULT_PASSKEY_CONFIG.rpId,
|
|
18
|
+
rpName: config.rpName?.trim() || DEFAULT_PASSKEY_CONFIG.rpName,
|
|
19
|
+
origins: candidateOrigins ? candidateOrigins.map((origin) => origin.trim()) : DEFAULT_PASSKEY_CONFIG.origins,
|
|
20
|
+
timeoutMs: typeof config.timeoutMs === 'number' && config.timeoutMs > 0
|
|
21
|
+
? config.timeoutMs
|
|
22
|
+
: DEFAULT_PASSKEY_CONFIG.timeoutMs,
|
|
23
|
+
userVerification: config.userVerification ?? DEFAULT_PASSKEY_CONFIG.userVerification,
|
|
24
|
+
debug: Boolean(config.debug)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -6,21 +6,29 @@ export interface MemoryPasskeyStoreOptions {
|
|
|
6
6
|
userId?: AuthIdentifier;
|
|
7
7
|
login?: string;
|
|
8
8
|
}) => Promise<PasskeyUserDescriptor | null>;
|
|
9
|
+
maxCredentials?: number;
|
|
10
|
+
maxChallenges?: number;
|
|
9
11
|
}
|
|
10
12
|
export declare class MemoryPasskeyStore extends PasskeyStore {
|
|
11
13
|
private readonly resolveUserFn;
|
|
12
14
|
private readonly credentials;
|
|
13
15
|
private readonly challenges;
|
|
16
|
+
private readonly maxCredentials?;
|
|
17
|
+
private readonly maxChallenges?;
|
|
14
18
|
constructor(options: MemoryPasskeyStoreOptions);
|
|
15
19
|
resolveUser(params: {
|
|
16
20
|
userId?: AuthIdentifier;
|
|
17
21
|
login?: string;
|
|
18
22
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
19
23
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
24
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
20
25
|
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
21
26
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
22
27
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
23
28
|
saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
29
|
+
getChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
24
30
|
consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
25
31
|
cleanupChallenges(now: Date): Promise<void>;
|
|
32
|
+
private enforceCredentialCapacity;
|
|
33
|
+
private enforceChallengeCapacity;
|
|
26
34
|
}
|
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemoryPasskeyStore = void 0;
|
|
4
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
4
5
|
const base_js_1 = require("./base.js");
|
|
5
6
|
function encodeCredentialId(value) {
|
|
6
7
|
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
7
8
|
}
|
|
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
9
|
function cloneCredential(record) {
|
|
18
10
|
return {
|
|
19
11
|
...record,
|
|
20
12
|
credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
|
|
13
|
+
publicKey: Buffer.from(record.publicKey),
|
|
21
14
|
transports: record.transports ? [...record.transports] : undefined
|
|
22
15
|
};
|
|
23
16
|
}
|
|
@@ -27,16 +20,32 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
27
20
|
this.credentials = new Map();
|
|
28
21
|
this.challenges = new Map();
|
|
29
22
|
this.resolveUserFn = options.resolveUser;
|
|
23
|
+
this.maxCredentials =
|
|
24
|
+
typeof options.maxCredentials === 'number' &&
|
|
25
|
+
Number.isFinite(options.maxCredentials) &&
|
|
26
|
+
options.maxCredentials > 0
|
|
27
|
+
? Math.floor(options.maxCredentials)
|
|
28
|
+
: undefined;
|
|
29
|
+
this.maxChallenges =
|
|
30
|
+
typeof options.maxChallenges === 'number' &&
|
|
31
|
+
Number.isFinite(options.maxChallenges) &&
|
|
32
|
+
options.maxChallenges > 0
|
|
33
|
+
? Math.floor(options.maxChallenges)
|
|
34
|
+
: undefined;
|
|
30
35
|
}
|
|
31
36
|
async resolveUser(params) {
|
|
32
37
|
return this.resolveUserFn(params);
|
|
33
38
|
}
|
|
34
39
|
async listUserCredentials(userId) {
|
|
35
|
-
const normalizedUserId =
|
|
40
|
+
const normalizedUserId = (0, user_id_js_1.normalizeComparableUserId)(userId);
|
|
36
41
|
return [...this.credentials.values()]
|
|
37
|
-
.filter((record) =>
|
|
42
|
+
.filter((record) => (0, user_id_js_1.normalizeComparableUserId)(record.userId) === normalizedUserId)
|
|
38
43
|
.map((record) => cloneCredential(record));
|
|
39
44
|
}
|
|
45
|
+
async deleteCredential(credentialId) {
|
|
46
|
+
const key = encodeCredentialId(credentialId);
|
|
47
|
+
return this.credentials.delete(key);
|
|
48
|
+
}
|
|
40
49
|
async findCredentialById(credentialId) {
|
|
41
50
|
const record = this.credentials.get(encodeCredentialId(credentialId));
|
|
42
51
|
return record ? cloneCredential(record) : null;
|
|
@@ -44,10 +53,11 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
44
53
|
async saveCredential(record) {
|
|
45
54
|
this.credentials.set(encodeCredentialId(record.credentialId), {
|
|
46
55
|
...record,
|
|
47
|
-
userId:
|
|
56
|
+
userId: (0, user_id_js_1.normalizeComparableUserId)(record.userId),
|
|
48
57
|
credentialId: Buffer.isBuffer(record.credentialId) ? Buffer.from(record.credentialId) : record.credentialId,
|
|
49
58
|
transports: record.transports ? [...record.transports] : undefined
|
|
50
59
|
});
|
|
60
|
+
this.enforceCredentialCapacity();
|
|
51
61
|
}
|
|
52
62
|
async updateCredentialCounter(credentialId, counter) {
|
|
53
63
|
const key = encodeCredentialId(credentialId);
|
|
@@ -58,10 +68,17 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
58
68
|
}
|
|
59
69
|
async saveChallenge(record) {
|
|
60
70
|
this.challenges.set(record.challenge, {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
challenge: record.challenge,
|
|
72
|
+
action: record.action,
|
|
73
|
+
userId: record.userId !== undefined ? (0, user_id_js_1.normalizeComparableUserId)(record.userId) : undefined,
|
|
74
|
+
login: record.login ?? undefined,
|
|
75
|
+
expiresAt: record.expiresAt
|
|
64
76
|
});
|
|
77
|
+
this.enforceChallengeCapacity();
|
|
78
|
+
}
|
|
79
|
+
async getChallenge(challenge) {
|
|
80
|
+
const record = this.challenges.get(challenge);
|
|
81
|
+
return record ? { ...record } : null;
|
|
65
82
|
}
|
|
66
83
|
async consumeChallenge(challenge) {
|
|
67
84
|
const record = this.challenges.get(challenge);
|
|
@@ -69,7 +86,7 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
69
86
|
return null;
|
|
70
87
|
}
|
|
71
88
|
this.challenges.delete(challenge);
|
|
72
|
-
return { ...record
|
|
89
|
+
return { ...record };
|
|
73
90
|
}
|
|
74
91
|
async cleanupChallenges(now) {
|
|
75
92
|
for (const [challenge, record] of this.challenges.entries()) {
|
|
@@ -78,5 +95,29 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
}
|
|
98
|
+
enforceCredentialCapacity() {
|
|
99
|
+
if (!this.maxCredentials) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
while (this.credentials.size > this.maxCredentials) {
|
|
103
|
+
const oldest = this.credentials.keys().next().value;
|
|
104
|
+
if (!oldest) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
this.credentials.delete(oldest);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
enforceChallengeCapacity() {
|
|
111
|
+
if (!this.maxChallenges) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
while (this.challenges.size > this.maxChallenges) {
|
|
115
|
+
const oldest = this.challenges.keys().next().value;
|
|
116
|
+
if (!oldest) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.challenges.delete(oldest);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
81
122
|
}
|
|
82
123
|
exports.MemoryPasskeyStore = MemoryPasskeyStore;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Model, type InferAttributes, type InferCreationAttributes, type ModelStatic, type Sequelize } from 'sequelize';
|
|
2
|
-
import type { PasskeyChallengeMetadata } from './service.js';
|
|
3
2
|
export declare class PasskeyCredentialModel extends Model<InferAttributes<PasskeyCredentialModel>, InferCreationAttributes<PasskeyCredentialModel>> {
|
|
4
3
|
credentialId: Buffer;
|
|
5
4
|
userId: number;
|
|
@@ -8,6 +7,13 @@ export declare class PasskeyCredentialModel extends Model<InferAttributes<Passke
|
|
|
8
7
|
transports: string[] | null;
|
|
9
8
|
backedUp: boolean;
|
|
10
9
|
deviceType: string;
|
|
10
|
+
label: string | null;
|
|
11
|
+
createdDomain: string | null;
|
|
12
|
+
createdUserAgent: string | null;
|
|
13
|
+
createdBrowser: string | null;
|
|
14
|
+
createdOs: string | null;
|
|
15
|
+
createdDevice: string | null;
|
|
16
|
+
createdIp: string | null;
|
|
11
17
|
createdAt?: Date;
|
|
12
18
|
updatedAt?: Date;
|
|
13
19
|
}
|
|
@@ -16,10 +22,13 @@ export declare class PasskeyChallengeModel extends Model<InferAttributes<Passkey
|
|
|
16
22
|
action: 'register' | 'authenticate';
|
|
17
23
|
userId: number | null;
|
|
18
24
|
login: string | null;
|
|
19
|
-
metadata: PasskeyChallengeMetadata | null;
|
|
20
25
|
expiresAt: Date;
|
|
21
26
|
createdAt?: Date;
|
|
22
27
|
updatedAt?: Date;
|
|
23
28
|
}
|
|
24
|
-
export declare function initPasskeyCredentialModel(sequelize: Sequelize
|
|
25
|
-
|
|
29
|
+
export declare function initPasskeyCredentialModel(sequelize: Sequelize, options?: {
|
|
30
|
+
tablePrefix?: string;
|
|
31
|
+
}): ModelStatic<PasskeyCredentialModel>;
|
|
32
|
+
export declare function initPasskeyChallengeModel(sequelize: Sequelize, options?: {
|
|
33
|
+
tablePrefix?: string;
|
|
34
|
+
}): ModelStatic<PasskeyChallengeModel>;
|
|
@@ -4,18 +4,15 @@ exports.PasskeyChallengeModel = exports.PasskeyCredentialModel = void 0;
|
|
|
4
4
|
exports.initPasskeyCredentialModel = initPasskeyCredentialModel;
|
|
5
5
|
exports.initPasskeyChallengeModel = initPasskeyChallengeModel;
|
|
6
6
|
const sequelize_1 = require("sequelize");
|
|
7
|
-
const
|
|
8
|
-
function integerIdType(sequelize) {
|
|
9
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
|
-
}
|
|
7
|
+
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
11
8
|
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
12
9
|
}
|
|
13
10
|
exports.PasskeyCredentialModel = PasskeyCredentialModel;
|
|
14
11
|
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
15
12
|
}
|
|
16
13
|
exports.PasskeyChallengeModel = PasskeyChallengeModel;
|
|
17
|
-
function initPasskeyCredentialModel(sequelize) {
|
|
18
|
-
const idType = integerIdType(sequelize);
|
|
14
|
+
function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
15
|
+
const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
|
|
19
16
|
return PasskeyCredentialModel.init({
|
|
20
17
|
credentialId: {
|
|
21
18
|
field: 'credential_id',
|
|
@@ -67,16 +64,50 @@ function initPasskeyCredentialModel(sequelize) {
|
|
|
67
64
|
type: sequelize_1.DataTypes.STRING(32),
|
|
68
65
|
allowNull: false,
|
|
69
66
|
defaultValue: 'multiDevice'
|
|
67
|
+
},
|
|
68
|
+
label: {
|
|
69
|
+
type: sequelize_1.DataTypes.STRING(120),
|
|
70
|
+
allowNull: true
|
|
71
|
+
},
|
|
72
|
+
createdDomain: {
|
|
73
|
+
field: 'created_domain',
|
|
74
|
+
type: sequelize_1.DataTypes.STRING(255),
|
|
75
|
+
allowNull: true
|
|
76
|
+
},
|
|
77
|
+
createdUserAgent: {
|
|
78
|
+
field: 'created_user_agent',
|
|
79
|
+
type: sequelize_1.DataTypes.TEXT,
|
|
80
|
+
allowNull: true
|
|
81
|
+
},
|
|
82
|
+
createdBrowser: {
|
|
83
|
+
field: 'created_browser',
|
|
84
|
+
type: sequelize_1.DataTypes.STRING(120),
|
|
85
|
+
allowNull: true
|
|
86
|
+
},
|
|
87
|
+
createdOs: {
|
|
88
|
+
field: 'created_os',
|
|
89
|
+
type: sequelize_1.DataTypes.STRING(120),
|
|
90
|
+
allowNull: true
|
|
91
|
+
},
|
|
92
|
+
createdDevice: {
|
|
93
|
+
field: 'created_device',
|
|
94
|
+
type: sequelize_1.DataTypes.STRING(120),
|
|
95
|
+
allowNull: true
|
|
96
|
+
},
|
|
97
|
+
createdIp: {
|
|
98
|
+
field: 'created_ip',
|
|
99
|
+
type: sequelize_1.DataTypes.STRING(45),
|
|
100
|
+
allowNull: true
|
|
70
101
|
}
|
|
71
102
|
}, {
|
|
72
103
|
sequelize,
|
|
73
|
-
tableName: 'passkey_credentials',
|
|
104
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
|
|
74
105
|
timestamps: true,
|
|
75
106
|
underscored: true
|
|
76
107
|
});
|
|
77
108
|
}
|
|
78
|
-
function initPasskeyChallengeModel(sequelize) {
|
|
79
|
-
const idType = integerIdType(sequelize);
|
|
109
|
+
function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
110
|
+
const idType = (0, sequelize_utils_js_1.integerIdType)(sequelize);
|
|
80
111
|
return PasskeyChallengeModel.init({
|
|
81
112
|
challenge: {
|
|
82
113
|
type: sequelize_1.DataTypes.STRING(255),
|
|
@@ -96,10 +127,6 @@ function initPasskeyChallengeModel(sequelize) {
|
|
|
96
127
|
type: sequelize_1.DataTypes.STRING(128),
|
|
97
128
|
allowNull: true
|
|
98
129
|
},
|
|
99
|
-
metadata: {
|
|
100
|
-
type: sequelize_1.DataTypes.JSON,
|
|
101
|
-
allowNull: true
|
|
102
|
-
},
|
|
103
130
|
expiresAt: {
|
|
104
131
|
field: 'expires_at',
|
|
105
132
|
type: sequelize_1.DataTypes.DATE,
|
|
@@ -107,7 +134,7 @@ function initPasskeyChallengeModel(sequelize) {
|
|
|
107
134
|
}
|
|
108
135
|
}, {
|
|
109
136
|
sequelize,
|
|
110
|
-
tableName: 'passkey_challenges',
|
|
137
|
+
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
|
|
111
138
|
timestamps: true,
|
|
112
139
|
underscored: true,
|
|
113
140
|
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
@@ -1,34 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ModelStatic, type Sequelize } from 'sequelize';
|
|
2
2
|
import { PasskeyStore } from './base.js';
|
|
3
|
+
import { PasskeyChallengeModel, PasskeyCredentialModel } from './models.js';
|
|
3
4
|
import type { PasskeyChallengeRecord, PasskeyUserDescriptor, StoredPasskeyCredential } from './types.js';
|
|
4
5
|
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
6
|
export interface SequelizePasskeyStoreOptions {
|
|
27
7
|
sequelize: Sequelize;
|
|
8
|
+
tablePrefix?: string;
|
|
28
9
|
credentialModel?: ModelStatic<PasskeyCredentialModel>;
|
|
29
10
|
challengeModel?: ModelStatic<PasskeyChallengeModel>;
|
|
30
|
-
credentialModelFactory?: (sequelize: Sequelize
|
|
31
|
-
|
|
11
|
+
credentialModelFactory?: (sequelize: Sequelize, options?: {
|
|
12
|
+
tablePrefix?: string;
|
|
13
|
+
}) => ModelStatic<PasskeyCredentialModel>;
|
|
14
|
+
challengeModelFactory?: (sequelize: Sequelize, options?: {
|
|
15
|
+
tablePrefix?: string;
|
|
16
|
+
}) => ModelStatic<PasskeyChallengeModel>;
|
|
32
17
|
resolveUser: (params: {
|
|
33
18
|
userId?: AuthIdentifier;
|
|
34
19
|
login?: string;
|
|
@@ -44,11 +29,14 @@ export declare class SequelizePasskeyStore extends PasskeyStore {
|
|
|
44
29
|
login?: string;
|
|
45
30
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
46
31
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
32
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
47
33
|
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
48
34
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
49
35
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
50
36
|
saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
37
|
+
getChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
51
38
|
consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
52
39
|
cleanupChallenges(now: Date): Promise<void>;
|
|
40
|
+
private toChallengeRecord;
|
|
41
|
+
private toStoredCredential;
|
|
53
42
|
}
|
|
54
|
-
export {};
|
|
@@ -2,126 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SequelizePasskeyStore = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
|
+
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
5
6
|
const base_js_1 = require("./base.js");
|
|
6
|
-
const
|
|
7
|
-
function integerIdType(sequelize) {
|
|
8
|
-
return DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
9
|
-
}
|
|
7
|
+
const models_js_1 = require("./models.js");
|
|
10
8
|
function encodeCredentialId(value) {
|
|
11
9
|
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
12
10
|
}
|
|
13
|
-
function normalizeUserId(identifier) {
|
|
14
|
-
if (typeof identifier === 'number' && Number.isFinite(identifier)) {
|
|
15
|
-
return identifier;
|
|
16
|
-
}
|
|
17
|
-
if (typeof identifier === 'string' && /^\d+$/.test(identifier)) {
|
|
18
|
-
return Number(identifier);
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`Unable to normalise user identifier: ${identifier}`);
|
|
21
|
-
}
|
|
22
|
-
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
23
|
-
}
|
|
24
|
-
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
25
|
-
}
|
|
26
|
-
function initPasskeyCredentialModel(sequelize) {
|
|
27
|
-
const idType = integerIdType(sequelize);
|
|
28
|
-
return PasskeyCredentialModel.init({
|
|
29
|
-
credentialId: {
|
|
30
|
-
field: 'credential_id',
|
|
31
|
-
type: sequelize_1.DataTypes.STRING(768),
|
|
32
|
-
primaryKey: true,
|
|
33
|
-
allowNull: false,
|
|
34
|
-
get() {
|
|
35
|
-
const raw = this.getDataValue('credentialId');
|
|
36
|
-
if (!raw) {
|
|
37
|
-
return raw;
|
|
38
|
-
}
|
|
39
|
-
if (Buffer.isBuffer(raw)) {
|
|
40
|
-
return raw;
|
|
41
|
-
}
|
|
42
|
-
return Buffer.from(raw, 'base64');
|
|
43
|
-
},
|
|
44
|
-
set(value) {
|
|
45
|
-
const encoded = typeof value === 'string' ? value : value.toString('base64');
|
|
46
|
-
this.setDataValue('credentialId', encoded);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
userId: {
|
|
50
|
-
field: 'user_id',
|
|
51
|
-
type: idType,
|
|
52
|
-
allowNull: false
|
|
53
|
-
},
|
|
54
|
-
publicKey: {
|
|
55
|
-
field: 'public_key',
|
|
56
|
-
type: sequelize_1.DataTypes.BLOB,
|
|
57
|
-
allowNull: false
|
|
58
|
-
},
|
|
59
|
-
counter: {
|
|
60
|
-
type: sequelize_1.DataTypes.INTEGER,
|
|
61
|
-
allowNull: false,
|
|
62
|
-
defaultValue: 0
|
|
63
|
-
},
|
|
64
|
-
transports: {
|
|
65
|
-
type: sequelize_1.DataTypes.JSON,
|
|
66
|
-
allowNull: true
|
|
67
|
-
},
|
|
68
|
-
backedUp: {
|
|
69
|
-
field: 'backed_up',
|
|
70
|
-
type: sequelize_1.DataTypes.BOOLEAN,
|
|
71
|
-
allowNull: false,
|
|
72
|
-
defaultValue: false
|
|
73
|
-
},
|
|
74
|
-
deviceType: {
|
|
75
|
-
field: 'device_type',
|
|
76
|
-
type: sequelize_1.DataTypes.STRING(32),
|
|
77
|
-
allowNull: false,
|
|
78
|
-
defaultValue: 'multiDevice'
|
|
79
|
-
}
|
|
80
|
-
}, {
|
|
81
|
-
sequelize,
|
|
82
|
-
tableName: 'passkey_credentials',
|
|
83
|
-
timestamps: true,
|
|
84
|
-
underscored: true
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function initPasskeyChallengeModel(sequelize) {
|
|
88
|
-
const idType = integerIdType(sequelize);
|
|
89
|
-
return PasskeyChallengeModel.init({
|
|
90
|
-
challenge: {
|
|
91
|
-
type: sequelize_1.DataTypes.STRING(255),
|
|
92
|
-
primaryKey: true,
|
|
93
|
-
allowNull: false
|
|
94
|
-
},
|
|
95
|
-
action: {
|
|
96
|
-
type: sequelize_1.DataTypes.STRING(16),
|
|
97
|
-
allowNull: false
|
|
98
|
-
},
|
|
99
|
-
userId: {
|
|
100
|
-
field: 'user_id',
|
|
101
|
-
type: idType,
|
|
102
|
-
allowNull: true
|
|
103
|
-
},
|
|
104
|
-
login: {
|
|
105
|
-
type: sequelize_1.DataTypes.STRING(128),
|
|
106
|
-
allowNull: true
|
|
107
|
-
},
|
|
108
|
-
metadata: {
|
|
109
|
-
type: sequelize_1.DataTypes.JSON,
|
|
110
|
-
allowNull: true
|
|
111
|
-
},
|
|
112
|
-
expiresAt: {
|
|
113
|
-
field: 'expires_at',
|
|
114
|
-
type: sequelize_1.DataTypes.DATE,
|
|
115
|
-
allowNull: false
|
|
116
|
-
}
|
|
117
|
-
}, {
|
|
118
|
-
sequelize,
|
|
119
|
-
tableName: 'passkey_challenges',
|
|
120
|
-
timestamps: true,
|
|
121
|
-
underscored: true,
|
|
122
|
-
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
11
|
class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
126
12
|
constructor(options) {
|
|
127
13
|
super();
|
|
@@ -131,49 +17,47 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
131
17
|
this.resolveUserFn = options.resolveUser;
|
|
132
18
|
this.credentials =
|
|
133
19
|
options.credentialModel ??
|
|
134
|
-
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize
|
|
20
|
+
(options.credentialModelFactory ?? models_js_1.initPasskeyCredentialModel)(options.sequelize, {
|
|
21
|
+
tablePrefix: options.tablePrefix
|
|
22
|
+
});
|
|
135
23
|
this.challenges =
|
|
136
|
-
options.challengeModel ??
|
|
24
|
+
options.challengeModel ??
|
|
25
|
+
(options.challengeModelFactory ?? models_js_1.initPasskeyChallengeModel)(options.sequelize, {
|
|
26
|
+
tablePrefix: options.tablePrefix
|
|
27
|
+
});
|
|
137
28
|
}
|
|
138
29
|
async resolveUser(params) {
|
|
139
30
|
return this.resolveUserFn(params);
|
|
140
31
|
}
|
|
141
32
|
async listUserCredentials(userId) {
|
|
142
|
-
const models = await this.credentials.findAll({ where: { userId:
|
|
143
|
-
return models.map((model) => (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
backedUp: model.backedUp,
|
|
150
|
-
deviceType: model.deviceType
|
|
151
|
-
}));
|
|
33
|
+
const models = await this.credentials.findAll({ where: { userId: (0, user_id_js_1.normalizeNumericUserId)(userId) } });
|
|
34
|
+
return models.map((model) => this.toStoredCredential(model));
|
|
35
|
+
}
|
|
36
|
+
async deleteCredential(credentialId) {
|
|
37
|
+
const encoded = Buffer.isBuffer(credentialId) ? credentialId.toString('base64') : credentialId;
|
|
38
|
+
const deleted = await this.credentials.destroy({ where: { credentialId: encoded } });
|
|
39
|
+
return deleted > 0;
|
|
152
40
|
}
|
|
153
41
|
async findCredentialById(credentialId) {
|
|
154
42
|
const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
|
|
155
|
-
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
return {
|
|
159
|
-
userId: model.userId,
|
|
160
|
-
credentialId: model.credentialId,
|
|
161
|
-
publicKey: model.publicKey,
|
|
162
|
-
counter: model.counter,
|
|
163
|
-
transports: (model.transports ?? undefined),
|
|
164
|
-
backedUp: model.backedUp,
|
|
165
|
-
deviceType: model.deviceType
|
|
166
|
-
};
|
|
43
|
+
return model ? this.toStoredCredential(model) : null;
|
|
167
44
|
}
|
|
168
45
|
async saveCredential(record) {
|
|
169
46
|
await this.credentials.upsert({
|
|
170
47
|
credentialId: record.credentialId,
|
|
171
|
-
userId:
|
|
48
|
+
userId: (0, user_id_js_1.normalizeNumericUserId)(record.userId),
|
|
172
49
|
publicKey: record.publicKey,
|
|
173
50
|
counter: record.counter,
|
|
174
51
|
transports: record.transports ?? null,
|
|
175
52
|
backedUp: record.backedUp,
|
|
176
|
-
deviceType: record.deviceType
|
|
53
|
+
deviceType: record.deviceType,
|
|
54
|
+
label: record.label ?? null,
|
|
55
|
+
createdDomain: record.createdDomain ?? null,
|
|
56
|
+
createdUserAgent: record.createdUserAgent ?? null,
|
|
57
|
+
createdBrowser: record.createdBrowser ?? null,
|
|
58
|
+
createdOs: record.createdOs ?? null,
|
|
59
|
+
createdDevice: record.createdDevice ?? null,
|
|
60
|
+
createdIp: record.createdIp ?? null
|
|
177
61
|
});
|
|
178
62
|
}
|
|
179
63
|
async updateCredentialCounter(credentialId, counter) {
|
|
@@ -183,29 +67,60 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
183
67
|
await this.challenges.upsert({
|
|
184
68
|
challenge: record.challenge,
|
|
185
69
|
action: record.action,
|
|
186
|
-
userId: record.userId !== undefined ?
|
|
70
|
+
userId: record.userId !== undefined ? (0, user_id_js_1.normalizeNumericUserId)(record.userId) : null,
|
|
187
71
|
login: record.login ?? null,
|
|
188
|
-
metadata: (record.metadata ?? {}),
|
|
189
72
|
expiresAt: record.expiresAt
|
|
190
73
|
});
|
|
191
74
|
}
|
|
192
|
-
async
|
|
75
|
+
async getChallenge(challenge) {
|
|
193
76
|
const model = await this.challenges.findByPk(challenge);
|
|
194
|
-
|
|
195
|
-
|
|
77
|
+
return model ? this.toChallengeRecord(model) : null;
|
|
78
|
+
}
|
|
79
|
+
async consumeChallenge(challenge) {
|
|
80
|
+
const sequelize = this.challenges.sequelize;
|
|
81
|
+
if (!sequelize) {
|
|
82
|
+
throw new Error('Challenge model is not bound to a Sequelize instance');
|
|
196
83
|
}
|
|
197
|
-
|
|
84
|
+
return sequelize.transaction({ isolationLevel: sequelize_1.Transaction.ISOLATION_LEVELS.READ_COMMITTED }, async (transaction) => {
|
|
85
|
+
const model = await this.challenges.findByPk(challenge, { transaction, lock: true });
|
|
86
|
+
if (!model) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
await model.destroy({ transaction });
|
|
90
|
+
return this.toChallengeRecord(model);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async cleanupChallenges(now) {
|
|
94
|
+
await this.challenges.destroy({ where: { expiresAt: { [sequelize_1.Op.lte]: now } } });
|
|
95
|
+
}
|
|
96
|
+
toChallengeRecord(model) {
|
|
198
97
|
return {
|
|
199
98
|
challenge: model.challenge,
|
|
200
99
|
action: model.action,
|
|
201
|
-
userId: model.userId
|
|
100
|
+
userId: model.userId !== null ? String(model.userId) : undefined,
|
|
202
101
|
login: model.login ?? undefined,
|
|
203
|
-
expiresAt: model.expiresAt
|
|
204
|
-
metadata: model.metadata ?? {}
|
|
102
|
+
expiresAt: model.expiresAt
|
|
205
103
|
};
|
|
206
104
|
}
|
|
207
|
-
|
|
208
|
-
|
|
105
|
+
toStoredCredential(model) {
|
|
106
|
+
return {
|
|
107
|
+
userId: String(model.userId),
|
|
108
|
+
credentialId: model.credentialId,
|
|
109
|
+
publicKey: model.publicKey,
|
|
110
|
+
counter: model.counter,
|
|
111
|
+
transports: (model.transports ?? undefined),
|
|
112
|
+
backedUp: model.backedUp,
|
|
113
|
+
deviceType: model.deviceType,
|
|
114
|
+
label: model.label ?? undefined,
|
|
115
|
+
createdDomain: model.createdDomain ?? undefined,
|
|
116
|
+
createdUserAgent: model.createdUserAgent ?? undefined,
|
|
117
|
+
createdBrowser: model.createdBrowser ?? undefined,
|
|
118
|
+
createdOs: model.createdOs ?? undefined,
|
|
119
|
+
createdDevice: model.createdDevice ?? undefined,
|
|
120
|
+
createdIp: model.createdIp ?? undefined,
|
|
121
|
+
createdAt: model.createdAt ?? undefined,
|
|
122
|
+
updatedAt: model.updatedAt ?? undefined
|
|
123
|
+
};
|
|
209
124
|
}
|
|
210
125
|
}
|
|
211
126
|
exports.SequelizePasskeyStore = SequelizePasskeyStore;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyStorageAdapter, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig } from './types.js';
|
|
2
|
-
|
|
1
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyStorageAdapter, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, StoredPasskeyCredential } from './types.js';
|
|
2
|
+
import type { AuthIdentifier } from '../auth-api/types.js';
|
|
3
|
+
export type { CredentialDeviceType, PasskeyChallenge, PasskeyChallengeParams, PasskeyChallengeRecord, PasskeyUserDescriptor, PasskeyVerificationParams, PasskeyVerificationResult, PasskeyServiceConfig, PasskeyStorageAdapter, StoredPasskeyCredential } from './types.js';
|
|
3
4
|
type Logger = Pick<typeof console, 'error' | 'warn'>;
|
|
4
5
|
export declare class PasskeyService {
|
|
5
6
|
private readonly config;
|
|
6
7
|
private readonly adapter;
|
|
7
8
|
private readonly logger;
|
|
8
9
|
constructor(config: PasskeyServiceConfig, adapter: PasskeyStorageAdapter, logger?: Logger);
|
|
10
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
11
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
9
12
|
createChallenge(params: PasskeyChallengeParams): Promise<PasskeyChallenge>;
|
|
10
13
|
verifyResponse(params: PasskeyVerificationParams): Promise<PasskeyVerificationResult>;
|
|
11
14
|
private createRegistrationChallenge;
|
|
@@ -14,4 +17,5 @@ export declare class PasskeyService {
|
|
|
14
17
|
private verifyAuthentication;
|
|
15
18
|
private requireUser;
|
|
16
19
|
private createExpiry;
|
|
20
|
+
private requireUserVerification;
|
|
17
21
|
}
|