@technomoron/api-server-base 2.0.0-beta.19 → 2.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/api-server-base.cjs +48 -21
- package/dist/cjs/auth-api/auth-module.js +16 -30
- package/dist/cjs/auth-api/mem-auth-store.js +5 -4
- package/dist/cjs/auth-api/sql-auth-store.js +6 -4
- package/dist/cjs/auth-api/user-id.d.ts +1 -0
- package/dist/cjs/auth-api/user-id.js +7 -0
- package/dist/cjs/auth-cookie-options.js +10 -1
- package/dist/cjs/oauth/memory.d.ts +6 -0
- package/dist/cjs/oauth/memory.js +43 -4
- package/dist/cjs/oauth/models.d.ts +1 -0
- package/dist/cjs/oauth/models.js +7 -18
- package/dist/cjs/oauth/sequelize.d.ts +5 -52
- package/dist/cjs/oauth/sequelize.js +34 -93
- package/dist/cjs/oauth/types.d.ts +1 -0
- package/dist/cjs/passkey/base.d.ts +1 -0
- package/dist/cjs/passkey/memory.d.ts +7 -0
- package/dist/cjs/passkey/memory.js +47 -5
- package/dist/cjs/passkey/models.js +2 -5
- package/dist/cjs/passkey/sequelize.d.ts +5 -29
- package/dist/cjs/passkey/sequelize.js +48 -191
- package/dist/cjs/passkey/service.d.ts +1 -0
- package/dist/cjs/passkey/service.js +52 -15
- package/dist/cjs/passkey/types.d.ts +1 -0
- package/dist/cjs/sequelize-utils.d.ts +5 -0
- package/dist/cjs/sequelize-utils.js +40 -0
- package/dist/cjs/token/base.js +3 -1
- package/dist/cjs/token/memory.d.ts +6 -0
- package/dist/cjs/token/memory.js +32 -7
- package/dist/cjs/token/sequelize.d.ts +0 -3
- package/dist/cjs/token/sequelize.js +50 -81
- package/dist/cjs/token/types.d.ts +1 -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 +8 -2
- package/dist/cjs/user/sequelize.js +12 -22
- package/dist/esm/api-server-base.js +48 -21
- package/dist/esm/auth-api/auth-module.js +16 -30
- package/dist/esm/auth-api/mem-auth-store.js +5 -4
- package/dist/esm/auth-api/sql-auth-store.js +6 -4
- package/dist/esm/auth-api/user-id.d.ts +1 -0
- package/dist/esm/auth-api/user-id.js +6 -0
- package/dist/esm/auth-cookie-options.js +10 -1
- package/dist/esm/oauth/memory.d.ts +6 -0
- package/dist/esm/oauth/memory.js +44 -5
- package/dist/esm/oauth/models.d.ts +1 -0
- package/dist/esm/oauth/models.js +2 -15
- package/dist/esm/oauth/sequelize.d.ts +5 -52
- package/dist/esm/oauth/sequelize.js +21 -80
- package/dist/esm/oauth/types.d.ts +1 -0
- package/dist/esm/passkey/base.d.ts +1 -0
- package/dist/esm/passkey/memory.d.ts +7 -0
- package/dist/esm/passkey/memory.js +47 -5
- package/dist/esm/passkey/models.js +1 -4
- package/dist/esm/passkey/sequelize.d.ts +5 -29
- package/dist/esm/passkey/sequelize.js +47 -190
- package/dist/esm/passkey/service.d.ts +1 -0
- package/dist/esm/passkey/service.js +52 -15
- package/dist/esm/passkey/types.d.ts +1 -0
- package/dist/esm/sequelize-utils.d.ts +5 -0
- package/dist/esm/sequelize-utils.js +36 -0
- package/dist/esm/token/base.js +3 -1
- package/dist/esm/token/memory.d.ts +6 -0
- package/dist/esm/token/memory.js +32 -7
- package/dist/esm/token/sequelize.d.ts +0 -3
- package/dist/esm/token/sequelize.js +51 -82
- package/dist/esm/token/types.d.ts +1 -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 +8 -2
- package/dist/esm/user/sequelize.js +13 -23
- package/package.json +5 -5
|
@@ -3,150 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SequelizePasskeyStore = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
5
|
const user_id_js_1 = require("../auth-api/user-id.js");
|
|
6
|
-
const sequelize_utils_js_1 = require("../sequelize-utils.js");
|
|
7
6
|
const base_js_1 = require("./base.js");
|
|
8
|
-
|
|
9
|
-
return sequelize_utils_js_1.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
10
|
-
}
|
|
7
|
+
const models_js_1 = require("./models.js");
|
|
11
8
|
function encodeCredentialId(value) {
|
|
12
9
|
return Buffer.isBuffer(value) ? value.toString('base64') : value;
|
|
13
10
|
}
|
|
14
|
-
function normalizeUserId(identifier) {
|
|
15
|
-
return (0, user_id_js_1.normalizeNumericUserId)(identifier);
|
|
16
|
-
}
|
|
17
|
-
class PasskeyCredentialModel extends sequelize_1.Model {
|
|
18
|
-
}
|
|
19
|
-
class PasskeyChallengeModel extends sequelize_1.Model {
|
|
20
|
-
}
|
|
21
|
-
function initPasskeyCredentialModel(sequelize, options = {}) {
|
|
22
|
-
const idType = integerIdType(sequelize);
|
|
23
|
-
return PasskeyCredentialModel.init({
|
|
24
|
-
credentialId: {
|
|
25
|
-
field: 'credential_id',
|
|
26
|
-
type: sequelize_1.DataTypes.STRING(768),
|
|
27
|
-
primaryKey: true,
|
|
28
|
-
allowNull: false,
|
|
29
|
-
get() {
|
|
30
|
-
const raw = this.getDataValue('credentialId');
|
|
31
|
-
if (!raw) {
|
|
32
|
-
return raw;
|
|
33
|
-
}
|
|
34
|
-
if (Buffer.isBuffer(raw)) {
|
|
35
|
-
return raw;
|
|
36
|
-
}
|
|
37
|
-
return Buffer.from(raw, 'base64');
|
|
38
|
-
},
|
|
39
|
-
set(value) {
|
|
40
|
-
const encoded = typeof value === 'string' ? value : value.toString('base64');
|
|
41
|
-
this.setDataValue('credentialId', encoded);
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
userId: {
|
|
45
|
-
field: 'user_id',
|
|
46
|
-
type: idType,
|
|
47
|
-
allowNull: false
|
|
48
|
-
},
|
|
49
|
-
publicKey: {
|
|
50
|
-
field: 'public_key',
|
|
51
|
-
type: sequelize_1.DataTypes.BLOB,
|
|
52
|
-
allowNull: false
|
|
53
|
-
},
|
|
54
|
-
counter: {
|
|
55
|
-
type: sequelize_1.DataTypes.INTEGER,
|
|
56
|
-
allowNull: false,
|
|
57
|
-
defaultValue: 0
|
|
58
|
-
},
|
|
59
|
-
transports: {
|
|
60
|
-
type: sequelize_1.DataTypes.JSON,
|
|
61
|
-
allowNull: true
|
|
62
|
-
},
|
|
63
|
-
backedUp: {
|
|
64
|
-
field: 'backed_up',
|
|
65
|
-
type: sequelize_1.DataTypes.BOOLEAN,
|
|
66
|
-
allowNull: false,
|
|
67
|
-
defaultValue: false
|
|
68
|
-
},
|
|
69
|
-
deviceType: {
|
|
70
|
-
field: 'device_type',
|
|
71
|
-
type: sequelize_1.DataTypes.STRING(32),
|
|
72
|
-
allowNull: false,
|
|
73
|
-
defaultValue: 'multiDevice'
|
|
74
|
-
},
|
|
75
|
-
label: {
|
|
76
|
-
type: sequelize_1.DataTypes.STRING(120),
|
|
77
|
-
allowNull: true
|
|
78
|
-
},
|
|
79
|
-
createdDomain: {
|
|
80
|
-
field: 'created_domain',
|
|
81
|
-
type: sequelize_1.DataTypes.STRING(255),
|
|
82
|
-
allowNull: true
|
|
83
|
-
},
|
|
84
|
-
createdUserAgent: {
|
|
85
|
-
field: 'created_user_agent',
|
|
86
|
-
type: sequelize_1.DataTypes.TEXT,
|
|
87
|
-
allowNull: true
|
|
88
|
-
},
|
|
89
|
-
createdBrowser: {
|
|
90
|
-
field: 'created_browser',
|
|
91
|
-
type: sequelize_1.DataTypes.STRING(120),
|
|
92
|
-
allowNull: true
|
|
93
|
-
},
|
|
94
|
-
createdOs: {
|
|
95
|
-
field: 'created_os',
|
|
96
|
-
type: sequelize_1.DataTypes.STRING(120),
|
|
97
|
-
allowNull: true
|
|
98
|
-
},
|
|
99
|
-
createdDevice: {
|
|
100
|
-
field: 'created_device',
|
|
101
|
-
type: sequelize_1.DataTypes.STRING(120),
|
|
102
|
-
allowNull: true
|
|
103
|
-
},
|
|
104
|
-
createdIp: {
|
|
105
|
-
field: 'created_ip',
|
|
106
|
-
type: sequelize_1.DataTypes.STRING(45),
|
|
107
|
-
allowNull: true
|
|
108
|
-
}
|
|
109
|
-
}, {
|
|
110
|
-
sequelize,
|
|
111
|
-
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_credentials'),
|
|
112
|
-
timestamps: true,
|
|
113
|
-
underscored: true
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
function initPasskeyChallengeModel(sequelize, options = {}) {
|
|
117
|
-
const idType = integerIdType(sequelize);
|
|
118
|
-
return PasskeyChallengeModel.init({
|
|
119
|
-
challenge: {
|
|
120
|
-
type: sequelize_1.DataTypes.STRING(255),
|
|
121
|
-
primaryKey: true,
|
|
122
|
-
allowNull: false
|
|
123
|
-
},
|
|
124
|
-
action: {
|
|
125
|
-
type: sequelize_1.DataTypes.STRING(16),
|
|
126
|
-
allowNull: false
|
|
127
|
-
},
|
|
128
|
-
userId: {
|
|
129
|
-
field: 'user_id',
|
|
130
|
-
type: idType,
|
|
131
|
-
allowNull: true
|
|
132
|
-
},
|
|
133
|
-
login: {
|
|
134
|
-
type: sequelize_1.DataTypes.STRING(128),
|
|
135
|
-
allowNull: true
|
|
136
|
-
},
|
|
137
|
-
expiresAt: {
|
|
138
|
-
field: 'expires_at',
|
|
139
|
-
type: sequelize_1.DataTypes.DATE,
|
|
140
|
-
allowNull: false
|
|
141
|
-
}
|
|
142
|
-
}, {
|
|
143
|
-
sequelize,
|
|
144
|
-
tableName: (0, sequelize_utils_js_1.applyTablePrefix)(options.tablePrefix, 'passkey_challenges'),
|
|
145
|
-
timestamps: true,
|
|
146
|
-
underscored: true,
|
|
147
|
-
indexes: [{ fields: ['expires_at'] }, { fields: ['user_id'] }]
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
11
|
class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
151
12
|
constructor(options) {
|
|
152
13
|
super();
|
|
@@ -156,12 +17,12 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
156
17
|
this.resolveUserFn = options.resolveUser;
|
|
157
18
|
this.credentials =
|
|
158
19
|
options.credentialModel ??
|
|
159
|
-
(options.credentialModelFactory ?? initPasskeyCredentialModel)(options.sequelize, {
|
|
20
|
+
(options.credentialModelFactory ?? models_js_1.initPasskeyCredentialModel)(options.sequelize, {
|
|
160
21
|
tablePrefix: options.tablePrefix
|
|
161
22
|
});
|
|
162
23
|
this.challenges =
|
|
163
24
|
options.challengeModel ??
|
|
164
|
-
(options.challengeModelFactory ?? initPasskeyChallengeModel)(options.sequelize, {
|
|
25
|
+
(options.challengeModelFactory ?? models_js_1.initPasskeyChallengeModel)(options.sequelize, {
|
|
165
26
|
tablePrefix: options.tablePrefix
|
|
166
27
|
});
|
|
167
28
|
}
|
|
@@ -169,25 +30,8 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
169
30
|
return this.resolveUserFn(params);
|
|
170
31
|
}
|
|
171
32
|
async listUserCredentials(userId) {
|
|
172
|
-
const models = await this.credentials.findAll({ where: { userId:
|
|
173
|
-
return models.map((model) => (
|
|
174
|
-
userId: model.userId,
|
|
175
|
-
credentialId: model.credentialId,
|
|
176
|
-
publicKey: model.publicKey,
|
|
177
|
-
counter: model.counter,
|
|
178
|
-
transports: (model.transports ?? undefined),
|
|
179
|
-
backedUp: model.backedUp,
|
|
180
|
-
deviceType: model.deviceType,
|
|
181
|
-
label: model.label ?? undefined,
|
|
182
|
-
createdDomain: model.createdDomain ?? undefined,
|
|
183
|
-
createdUserAgent: model.createdUserAgent ?? undefined,
|
|
184
|
-
createdBrowser: model.createdBrowser ?? undefined,
|
|
185
|
-
createdOs: model.createdOs ?? undefined,
|
|
186
|
-
createdDevice: model.createdDevice ?? undefined,
|
|
187
|
-
createdIp: model.createdIp ?? undefined,
|
|
188
|
-
createdAt: model.createdAt ?? undefined,
|
|
189
|
-
updatedAt: model.updatedAt ?? undefined
|
|
190
|
-
}));
|
|
33
|
+
const models = await this.credentials.findAll({ where: { userId: (0, user_id_js_1.normalizeNumericUserId)(userId) } });
|
|
34
|
+
return models.map((model) => this.toStoredCredential(model));
|
|
191
35
|
}
|
|
192
36
|
async deleteCredential(credentialId) {
|
|
193
37
|
const encoded = Buffer.isBuffer(credentialId) ? credentialId.toString('base64') : credentialId;
|
|
@@ -196,32 +40,12 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
196
40
|
}
|
|
197
41
|
async findCredentialById(credentialId) {
|
|
198
42
|
const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
|
|
199
|
-
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
return {
|
|
203
|
-
userId: model.userId,
|
|
204
|
-
credentialId: model.credentialId,
|
|
205
|
-
publicKey: model.publicKey,
|
|
206
|
-
counter: model.counter,
|
|
207
|
-
transports: (model.transports ?? undefined),
|
|
208
|
-
backedUp: model.backedUp,
|
|
209
|
-
deviceType: model.deviceType,
|
|
210
|
-
label: model.label ?? undefined,
|
|
211
|
-
createdDomain: model.createdDomain ?? undefined,
|
|
212
|
-
createdUserAgent: model.createdUserAgent ?? undefined,
|
|
213
|
-
createdBrowser: model.createdBrowser ?? undefined,
|
|
214
|
-
createdOs: model.createdOs ?? undefined,
|
|
215
|
-
createdDevice: model.createdDevice ?? undefined,
|
|
216
|
-
createdIp: model.createdIp ?? undefined,
|
|
217
|
-
createdAt: model.createdAt ?? undefined,
|
|
218
|
-
updatedAt: model.updatedAt ?? undefined
|
|
219
|
-
};
|
|
43
|
+
return model ? this.toStoredCredential(model) : null;
|
|
220
44
|
}
|
|
221
45
|
async saveCredential(record) {
|
|
222
46
|
await this.credentials.upsert({
|
|
223
47
|
credentialId: record.credentialId,
|
|
224
|
-
userId:
|
|
48
|
+
userId: (0, user_id_js_1.normalizeNumericUserId)(record.userId),
|
|
225
49
|
publicKey: record.publicKey,
|
|
226
50
|
counter: record.counter,
|
|
227
51
|
transports: record.transports ?? null,
|
|
@@ -243,27 +67,60 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
243
67
|
await this.challenges.upsert({
|
|
244
68
|
challenge: record.challenge,
|
|
245
69
|
action: record.action,
|
|
246
|
-
userId: record.userId !== undefined ?
|
|
70
|
+
userId: record.userId !== undefined ? (0, user_id_js_1.normalizeNumericUserId)(record.userId) : null,
|
|
247
71
|
login: record.login ?? null,
|
|
248
72
|
expiresAt: record.expiresAt
|
|
249
73
|
});
|
|
250
74
|
}
|
|
251
|
-
async
|
|
75
|
+
async getChallenge(challenge) {
|
|
252
76
|
const model = await this.challenges.findByPk(challenge);
|
|
253
|
-
|
|
254
|
-
|
|
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');
|
|
255
83
|
}
|
|
256
|
-
|
|
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) {
|
|
257
97
|
return {
|
|
258
98
|
challenge: model.challenge,
|
|
259
99
|
action: model.action,
|
|
260
|
-
userId: model.userId
|
|
100
|
+
userId: model.userId !== null ? String(model.userId) : undefined,
|
|
261
101
|
login: model.login ?? undefined,
|
|
262
102
|
expiresAt: model.expiresAt
|
|
263
103
|
};
|
|
264
104
|
}
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
};
|
|
267
124
|
}
|
|
268
125
|
}
|
|
269
126
|
exports.SequelizePasskeyStore = SequelizePasskeyStore;
|
|
@@ -102,21 +102,51 @@ async function spkiToCosePublicKey(spki) {
|
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
104
|
const spkiView = new Uint8Array(spki);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const ecCurves = [
|
|
106
|
+
{ namedCurve: 'P-256', crv: 1, alg: -7, rawLength: 65 },
|
|
107
|
+
{ namedCurve: 'P-384', crv: 2, alg: -35, rawLength: 97 },
|
|
108
|
+
{ namedCurve: 'P-521', crv: 3, alg: -36, rawLength: 133 }
|
|
109
|
+
];
|
|
110
|
+
for (const curve of ecCurves) {
|
|
111
|
+
try {
|
|
112
|
+
const key = await subtle.importKey('spki', spkiView, { name: 'ECDSA', namedCurve: curve.namedCurve }, true, ['verify']);
|
|
113
|
+
const raw = Buffer.from(await subtle.exportKey('raw', key));
|
|
114
|
+
if (raw.length !== curve.rawLength || raw[0] !== 0x04) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const coordLength = (raw.length - 1) / 2;
|
|
118
|
+
const x = raw.slice(1, 1 + coordLength);
|
|
119
|
+
const y = raw.slice(1 + coordLength);
|
|
120
|
+
const coseMap = new Map([
|
|
121
|
+
[1, 2], // kty: EC2
|
|
122
|
+
[3, curve.alg],
|
|
123
|
+
[-1, curve.crv],
|
|
124
|
+
[-2, x],
|
|
125
|
+
[-3, y]
|
|
126
|
+
]);
|
|
127
|
+
return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// try next algorithm
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const edKey = await subtle.importKey('spki', spkiView, { name: 'Ed25519' }, true, ['verify']);
|
|
135
|
+
const raw = Buffer.from(await subtle.exportKey('raw', edKey));
|
|
136
|
+
if (raw.length !== 32) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const coseMap = new Map([
|
|
140
|
+
[1, 1], // kty: OKP
|
|
141
|
+
[3, -8], // alg: EdDSA
|
|
142
|
+
[-1, 6], // crv: Ed25519
|
|
143
|
+
[-2, raw]
|
|
144
|
+
]);
|
|
145
|
+
return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
108
148
|
return null;
|
|
109
149
|
}
|
|
110
|
-
const x = raw.slice(1, 33);
|
|
111
|
-
const y = raw.slice(33, 65);
|
|
112
|
-
const coseMap = new Map([
|
|
113
|
-
[1, 2], // kty: EC2
|
|
114
|
-
[3, -7], // alg: ES256
|
|
115
|
-
[-1, 1], // crv: P-256
|
|
116
|
-
[-2, x],
|
|
117
|
-
[-3, y]
|
|
118
|
-
]);
|
|
119
|
-
return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
|
|
120
150
|
}
|
|
121
151
|
catch {
|
|
122
152
|
return null;
|
|
@@ -146,6 +176,10 @@ class PasskeyService {
|
|
|
146
176
|
}
|
|
147
177
|
async verifyResponse(params) {
|
|
148
178
|
await this.adapter.cleanupChallenges?.(new Date());
|
|
179
|
+
const existing = this.adapter.getChallenge ? await this.adapter.getChallenge(params.expectedChallenge) : null;
|
|
180
|
+
if (existing && existing.expiresAt.getTime() <= Date.now()) {
|
|
181
|
+
return { verified: false };
|
|
182
|
+
}
|
|
149
183
|
const record = await this.adapter.consumeChallenge(params.expectedChallenge);
|
|
150
184
|
if (!record) {
|
|
151
185
|
return { verified: false };
|
|
@@ -240,7 +274,7 @@ class PasskeyService {
|
|
|
240
274
|
expectedChallenge: record.challenge,
|
|
241
275
|
expectedOrigin: this.config.origins,
|
|
242
276
|
expectedRPID: this.config.rpId,
|
|
243
|
-
requireUserVerification:
|
|
277
|
+
requireUserVerification: this.requireUserVerification()
|
|
244
278
|
});
|
|
245
279
|
if (!result.verified || !result.registrationInfo) {
|
|
246
280
|
if (!result.verified) {
|
|
@@ -334,7 +368,7 @@ class PasskeyService {
|
|
|
334
368
|
expectedOrigin: this.config.origins,
|
|
335
369
|
expectedRPID: this.config.rpId,
|
|
336
370
|
credential: storedAuthData,
|
|
337
|
-
requireUserVerification:
|
|
371
|
+
requireUserVerification: this.requireUserVerification()
|
|
338
372
|
});
|
|
339
373
|
if (!result.verified) {
|
|
340
374
|
const err = result.error ?? result;
|
|
@@ -358,5 +392,8 @@ class PasskeyService {
|
|
|
358
392
|
createExpiry() {
|
|
359
393
|
return new Date(Date.now() + this.config.timeoutMs);
|
|
360
394
|
}
|
|
395
|
+
requireUserVerification() {
|
|
396
|
+
return this.config.userVerification !== 'discouraged';
|
|
397
|
+
}
|
|
361
398
|
}
|
|
362
399
|
exports.PasskeyService = PasskeyService;
|
|
@@ -55,6 +55,7 @@ export interface PasskeyStorageAdapter {
|
|
|
55
55
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
56
56
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
57
57
|
saveChallenge(record: PasskeyChallengeRecord): Promise<void>;
|
|
58
|
+
getChallenge?(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
58
59
|
consumeChallenge(challenge: string): Promise<PasskeyChallengeRecord | null>;
|
|
59
60
|
cleanupChallenges?(now: Date): Promise<void>;
|
|
60
61
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { DataTypes, type InitOptions, type Model, type Sequelize } from 'sequelize';
|
|
1
2
|
export declare const DIALECTS_SUPPORTING_UNSIGNED: Set<string>;
|
|
3
|
+
export declare function integerIdType(sequelize: Sequelize): DataTypes.IntegerDataTypeConstructor;
|
|
4
|
+
export declare function tableOptions<ModelType extends Model>(sequelize: Sequelize, tableName: string, tablePrefix?: string, extra?: Partial<InitOptions<ModelType>>): InitOptions<ModelType>;
|
|
2
5
|
export declare function normalizeTablePrefix(prefix?: string): string | undefined;
|
|
3
6
|
export declare function applyTablePrefix(prefix: string | undefined, tableName: string): string;
|
|
7
|
+
export declare function encodeStringArray(values: string[] | undefined): string;
|
|
8
|
+
export declare function decodeStringArray(raw: string | null | undefined): string[];
|
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DIALECTS_SUPPORTING_UNSIGNED = void 0;
|
|
4
|
+
exports.integerIdType = integerIdType;
|
|
5
|
+
exports.tableOptions = tableOptions;
|
|
4
6
|
exports.normalizeTablePrefix = normalizeTablePrefix;
|
|
5
7
|
exports.applyTablePrefix = applyTablePrefix;
|
|
8
|
+
exports.encodeStringArray = encodeStringArray;
|
|
9
|
+
exports.decodeStringArray = decodeStringArray;
|
|
10
|
+
const sequelize_1 = require("sequelize");
|
|
6
11
|
exports.DIALECTS_SUPPORTING_UNSIGNED = new Set(['mysql', 'mariadb']);
|
|
12
|
+
function integerIdType(sequelize) {
|
|
13
|
+
return exports.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect()) ? sequelize_1.DataTypes.INTEGER.UNSIGNED : sequelize_1.DataTypes.INTEGER;
|
|
14
|
+
}
|
|
15
|
+
function tableOptions(sequelize, tableName, tablePrefix, extra) {
|
|
16
|
+
const opts = { sequelize, tableName: applyTablePrefix(tablePrefix, tableName) };
|
|
17
|
+
if (extra) {
|
|
18
|
+
Object.assign(opts, extra);
|
|
19
|
+
}
|
|
20
|
+
if (exports.DIALECTS_SUPPORTING_UNSIGNED.has(sequelize.getDialect())) {
|
|
21
|
+
opts.charset = 'utf8mb4';
|
|
22
|
+
opts.collate = 'utf8mb4_unicode_ci';
|
|
23
|
+
}
|
|
24
|
+
return opts;
|
|
25
|
+
}
|
|
7
26
|
function normalizeTablePrefix(prefix) {
|
|
8
27
|
if (!prefix) {
|
|
9
28
|
return undefined;
|
|
@@ -15,3 +34,24 @@ function applyTablePrefix(prefix, tableName) {
|
|
|
15
34
|
const normalized = normalizeTablePrefix(prefix);
|
|
16
35
|
return normalized ? `${normalized}${tableName}` : tableName;
|
|
17
36
|
}
|
|
37
|
+
function encodeStringArray(values) {
|
|
38
|
+
return JSON.stringify(values ?? []);
|
|
39
|
+
}
|
|
40
|
+
function decodeStringArray(raw) {
|
|
41
|
+
if (!raw) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
if (Array.isArray(parsed)) {
|
|
47
|
+
return parsed.filter((entry) => typeof entry === 'string' && entry.length > 0);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore malformed values
|
|
52
|
+
}
|
|
53
|
+
return raw
|
|
54
|
+
.split(/\s+/)
|
|
55
|
+
.map((entry) => entry.trim())
|
|
56
|
+
.filter((entry) => entry.length > 0);
|
|
57
|
+
}
|
package/dist/cjs/token/base.js
CHANGED
|
@@ -49,10 +49,10 @@ function normalizeTokenInternal(input) {
|
|
|
49
49
|
const status = input.status === 'revoked' ? 'revoked' : expires.getTime() < Date.now() ? 'expired' : 'active';
|
|
50
50
|
const sessionCookie = typeof input.sessionCookie === 'boolean' ? input.sessionCookie : false;
|
|
51
51
|
return {
|
|
52
|
-
...input,
|
|
53
52
|
accessToken: input.accessToken,
|
|
54
53
|
refreshToken: input.refreshToken,
|
|
55
54
|
userId,
|
|
55
|
+
clientId: typeof input.clientId === 'string' && input.clientId.length > 0 ? input.clientId : undefined,
|
|
56
56
|
domain: typeof input.domain === 'string' ? input.domain : '',
|
|
57
57
|
fingerprint: typeof input.fingerprint === 'string' ? input.fingerprint : '',
|
|
58
58
|
label: typeof input.label === 'string' ? input.label : '',
|
|
@@ -76,6 +76,8 @@ class TokenStore {
|
|
|
76
76
|
normalizeToken(token) {
|
|
77
77
|
return normalizeTokenInternal(token);
|
|
78
78
|
}
|
|
79
|
+
// JWT helpers live on TokenStore so every adapter automatically exposes
|
|
80
|
+
// signing/verification without additional wiring.
|
|
79
81
|
jwtSign(payload, secret, expiresInSeconds, options) {
|
|
80
82
|
const opts = { ...(options ?? {}), expiresIn: expiresInSeconds };
|
|
81
83
|
try {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { TokenStore } from './base.js';
|
|
2
2
|
import type { Token } from './types.js';
|
|
3
|
+
export interface MemoryTokenStoreOptions {
|
|
4
|
+
maxTokens?: number;
|
|
5
|
+
}
|
|
3
6
|
export declare class MemoryTokenStore extends TokenStore {
|
|
4
7
|
private readonly tokens;
|
|
5
8
|
private readonly tokensByUser;
|
|
9
|
+
private readonly maxTokens?;
|
|
10
|
+
constructor(options?: MemoryTokenStoreOptions);
|
|
6
11
|
private indexToken;
|
|
7
12
|
private unindexToken;
|
|
8
13
|
private removeByRefreshToken;
|
|
@@ -20,4 +25,5 @@ export declare class MemoryTokenStore extends TokenStore {
|
|
|
20
25
|
includeExpired?: boolean;
|
|
21
26
|
}): Promise<Token[]>;
|
|
22
27
|
close(): Promise<void>;
|
|
28
|
+
private enforceCapacity;
|
|
23
29
|
}
|
package/dist/cjs/token/memory.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MemoryTokenStore = void 0;
|
|
4
4
|
const base_js_1 = require("./base.js");
|
|
5
5
|
function comparableUserId(value) {
|
|
6
|
-
if (value === undefined
|
|
6
|
+
if (value === undefined) {
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
9
|
return String(value);
|
|
@@ -48,10 +48,14 @@ function matchesQuery(record, query, includeExpired) {
|
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
class MemoryTokenStore extends base_js_1.TokenStore {
|
|
51
|
-
constructor() {
|
|
52
|
-
super(
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
super();
|
|
53
53
|
this.tokens = new Map();
|
|
54
54
|
this.tokensByUser = new Map();
|
|
55
|
+
this.maxTokens =
|
|
56
|
+
typeof options.maxTokens === 'number' && Number.isFinite(options.maxTokens) && options.maxTokens > 0
|
|
57
|
+
? Math.floor(options.maxTokens)
|
|
58
|
+
: undefined;
|
|
55
59
|
}
|
|
56
60
|
indexToken(token) {
|
|
57
61
|
const userId = comparableUserId(token.userId);
|
|
@@ -118,6 +122,7 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
118
122
|
this.removeByRefreshToken(stored.refreshToken);
|
|
119
123
|
this.tokens.set(stored.refreshToken, stored);
|
|
120
124
|
this.indexToken(stored);
|
|
125
|
+
this.enforceCapacity();
|
|
121
126
|
}
|
|
122
127
|
async get(query, opts) {
|
|
123
128
|
if (!query.refreshToken && !query.accessToken && query.userId === undefined) {
|
|
@@ -165,8 +170,12 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
165
170
|
merged[key] = value;
|
|
166
171
|
}
|
|
167
172
|
};
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
if (params.accessToken !== undefined && params.accessToken !== null) {
|
|
174
|
+
merged.accessToken = params.accessToken;
|
|
175
|
+
}
|
|
176
|
+
if (params.expires !== undefined && params.expires !== null) {
|
|
177
|
+
merged.expires = params.expires;
|
|
178
|
+
}
|
|
170
179
|
maybeAssign('scope');
|
|
171
180
|
maybeAssign('label');
|
|
172
181
|
maybeAssign('domain');
|
|
@@ -177,8 +186,12 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
177
186
|
maybeAssign('os');
|
|
178
187
|
maybeAssign('refreshTtlSeconds');
|
|
179
188
|
maybeAssign('loginType');
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
if (params.issuedAt !== undefined && params.issuedAt !== null) {
|
|
190
|
+
merged.issuedAt = params.issuedAt;
|
|
191
|
+
}
|
|
192
|
+
if (params.lastSeenAt !== undefined && params.lastSeenAt !== null) {
|
|
193
|
+
merged.lastSeenAt = params.lastSeenAt;
|
|
194
|
+
}
|
|
182
195
|
maybeAssign('sessionCookie');
|
|
183
196
|
const normalized = this.normalizeToken(merged);
|
|
184
197
|
const previousUserId = token.userId;
|
|
@@ -210,5 +223,17 @@ class MemoryTokenStore extends base_js_1.TokenStore {
|
|
|
210
223
|
async close() {
|
|
211
224
|
return;
|
|
212
225
|
}
|
|
226
|
+
enforceCapacity() {
|
|
227
|
+
if (!this.maxTokens) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
while (this.tokens.size > this.maxTokens) {
|
|
231
|
+
const oldestRefresh = this.tokens.keys().next().value;
|
|
232
|
+
if (!oldestRefresh) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.removeByRefreshToken(oldestRefresh);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
213
238
|
}
|
|
214
239
|
exports.MemoryTokenStore = MemoryTokenStore;
|
|
@@ -52,10 +52,7 @@ export declare class SequelizeTokenStore extends TokenStore {
|
|
|
52
52
|
close(): Promise<void>;
|
|
53
53
|
private normalizeUserId;
|
|
54
54
|
private resolveRealUserId;
|
|
55
|
-
private encodeStringArray;
|
|
56
|
-
private decodeStringArray;
|
|
57
55
|
private encodeScope;
|
|
58
|
-
private decodeScope;
|
|
59
56
|
private toTokenRecord;
|
|
60
57
|
}
|
|
61
58
|
export {};
|