@technomoron/api-server-base 2.0.0-beta.1 → 2.0.0-beta.11
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 +25 -2
- package/dist/cjs/api-server-base.cjs +277 -41
- package/dist/cjs/api-server-base.d.ts +27 -7
- package/dist/cjs/auth-api/auth-module.d.ts +11 -2
- package/dist/cjs/auth-api/auth-module.js +215 -46
- 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 +7 -1
- package/dist/cjs/auth-api/sql-auth-store.d.ts +5 -3
- package/dist/cjs/auth-api/sql-auth-store.js +7 -1
- 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/index.cjs +4 -4
- package/dist/cjs/index.d.ts +4 -4
- package/dist/cjs/oauth/sequelize.js +1 -1
- package/dist/cjs/passkey/base.d.ts +1 -0
- package/dist/cjs/passkey/memory.d.ts +1 -0
- package/dist/cjs/passkey/memory.js +4 -0
- package/dist/cjs/passkey/sequelize.d.ts +1 -0
- package/dist/cjs/passkey/sequelize.js +11 -2
- package/dist/cjs/passkey/service.d.ts +5 -2
- package/dist/cjs/passkey/service.js +145 -10
- package/dist/cjs/passkey/types.d.ts +3 -0
- package/dist/cjs/user/base.js +2 -1
- package/dist/esm/api-server-base.d.ts +27 -7
- package/dist/esm/api-server-base.js +278 -42
- package/dist/esm/auth-api/auth-module.d.ts +11 -2
- package/dist/esm/auth-api/auth-module.js +216 -47
- 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 +8 -2
- package/dist/esm/auth-api/sql-auth-store.d.ts +5 -3
- package/dist/esm/auth-api/sql-auth-store.js +8 -2
- 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/index.d.ts +4 -4
- package/dist/esm/index.js +2 -2
- package/dist/esm/oauth/sequelize.js +1 -1
- package/dist/esm/passkey/base.d.ts +1 -0
- package/dist/esm/passkey/memory.d.ts +1 -0
- package/dist/esm/passkey/memory.js +4 -0
- package/dist/esm/passkey/sequelize.d.ts +1 -0
- package/dist/esm/passkey/sequelize.js +11 -2
- package/dist/esm/passkey/service.d.ts +5 -2
- package/dist/esm/passkey/service.js +113 -11
- package/dist/esm/passkey/types.d.ts +3 -0
- package/dist/esm/user/base.js +2 -1
- package/package.json +13 -11
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export { default as ApiServer } from './api-server-base.js';
|
|
2
2
|
export { ApiError } from './api-server-base.js';
|
|
3
3
|
export { ApiModule } from './api-module.js';
|
|
4
|
-
export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, ExtendedReq } from './api-server-base.js';
|
|
5
|
-
export type { AuthIdentifier
|
|
4
|
+
export type { ApiErrorParams, ApiHandler, ApiKey, ApiServerConf, ApiRequest, ApiRoute, ApiAuthType, ApiAuthClass, ApiTokenData, ExtendedReq, ExpressApiRequest, ExpressApiLocals } from './api-server-base.js';
|
|
5
|
+
export type { AuthIdentifier } from './auth-api/types.js';
|
|
6
6
|
export type { Token, TokenPair, TokenStatus } from './token/types.js';
|
|
7
7
|
export type { JwtSignResult, JwtVerifyResult, JwtDecodeResult } from './token/base.js';
|
|
8
8
|
export type { OAuthClient, AuthCodeData, AuthCodeRequest } from './oauth/types.js';
|
|
9
9
|
export type { AuthProviderModule } from './auth-api/module.js';
|
|
10
|
-
export {
|
|
10
|
+
export { nullAuthAdapter, BaseAuthAdapter } from './auth-api/storage.js';
|
|
11
11
|
export { nullAuthModule, BaseAuthModule } from './auth-api/module.js';
|
|
12
|
-
export {
|
|
12
|
+
export { CompositeAuthAdapter } from './auth-api/compat-auth-storage.js';
|
|
13
13
|
export { MemAuthStore } from './auth-api/mem-auth-store.js';
|
|
14
14
|
export { SqlAuthStore } from './auth-api/sql-auth-store.js';
|
|
15
15
|
export { default as AuthModule } from './auth-api/auth-module.js';
|
|
@@ -126,7 +126,7 @@ class SequelizeOAuthStore extends base_js_1.OAuthStore {
|
|
|
126
126
|
const existing = await this.clients.findByPk(input.clientId);
|
|
127
127
|
const hashedSecret = input.clientSecret !== undefined && input.clientSecret !== null
|
|
128
128
|
? await bcryptjs_1.default.hash(input.clientSecret, this.bcryptRounds)
|
|
129
|
-
: existing?.client_secret ?? '';
|
|
129
|
+
: (existing?.client_secret ?? '');
|
|
130
130
|
const redirectUris = input.redirectUris ?? (existing ? decodeStringArray(existing.redirect_uris) : undefined);
|
|
131
131
|
const scope = input.scope ?? (existing ? decodeStringArray(existing.scope) : undefined);
|
|
132
132
|
const metadata = input.metadata ?? (existing ? parseMetadata(existing.metadata) : undefined);
|
|
@@ -6,6 +6,7 @@ export declare abstract class PasskeyStore implements PasskeyStorageAdapter {
|
|
|
6
6
|
login?: string;
|
|
7
7
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
8
8
|
abstract listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
9
|
+
abstract deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
9
10
|
abstract findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
10
11
|
abstract saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
11
12
|
abstract updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
@@ -17,6 +17,7 @@ export declare class MemoryPasskeyStore extends PasskeyStore {
|
|
|
17
17
|
login?: string;
|
|
18
18
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
19
19
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
20
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
20
21
|
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
21
22
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
22
23
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
@@ -37,6 +37,10 @@ class MemoryPasskeyStore extends base_js_1.PasskeyStore {
|
|
|
37
37
|
.filter((record) => normalizeUserId(record.userId) === normalizedUserId)
|
|
38
38
|
.map((record) => cloneCredential(record));
|
|
39
39
|
}
|
|
40
|
+
async deleteCredential(credentialId) {
|
|
41
|
+
const key = encodeCredentialId(credentialId);
|
|
42
|
+
return this.credentials.delete(key);
|
|
43
|
+
}
|
|
40
44
|
async findCredentialById(credentialId) {
|
|
41
45
|
const record = this.credentials.get(encodeCredentialId(credentialId));
|
|
42
46
|
return record ? cloneCredential(record) : null;
|
|
@@ -44,6 +44,7 @@ export declare class SequelizePasskeyStore extends PasskeyStore {
|
|
|
44
44
|
login?: string;
|
|
45
45
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
46
46
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
47
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
47
48
|
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
48
49
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
49
50
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
|
@@ -147,9 +147,16 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
147
147
|
counter: model.counter,
|
|
148
148
|
transports: (model.transports ?? undefined),
|
|
149
149
|
backedUp: model.backedUp,
|
|
150
|
-
deviceType: model.deviceType
|
|
150
|
+
deviceType: model.deviceType,
|
|
151
|
+
createdAt: model.createdAt ?? undefined,
|
|
152
|
+
updatedAt: model.updatedAt ?? undefined
|
|
151
153
|
}));
|
|
152
154
|
}
|
|
155
|
+
async deleteCredential(credentialId) {
|
|
156
|
+
const encoded = Buffer.isBuffer(credentialId) ? credentialId.toString('base64') : credentialId;
|
|
157
|
+
const deleted = await this.credentials.destroy({ where: { credentialId: encoded } });
|
|
158
|
+
return deleted > 0;
|
|
159
|
+
}
|
|
153
160
|
async findCredentialById(credentialId) {
|
|
154
161
|
const model = await this.credentials.findByPk(encodeCredentialId(credentialId));
|
|
155
162
|
if (!model) {
|
|
@@ -162,7 +169,9 @@ class SequelizePasskeyStore extends base_js_1.PasskeyStore {
|
|
|
162
169
|
counter: model.counter,
|
|
163
170
|
transports: (model.transports ?? undefined),
|
|
164
171
|
backedUp: model.backedUp,
|
|
165
|
-
deviceType: model.deviceType
|
|
172
|
+
deviceType: model.deviceType,
|
|
173
|
+
createdAt: model.createdAt ?? undefined,
|
|
174
|
+
updatedAt: model.updatedAt ?? undefined
|
|
166
175
|
};
|
|
167
176
|
}
|
|
168
177
|
async saveCredential(record) {
|
|
@@ -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, PasskeyChallengeMetadata, 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;
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.PasskeyService = void 0;
|
|
4
37
|
const server_1 = require("@simplewebauthn/server");
|
|
@@ -34,12 +67,64 @@ function toBuffer(value) {
|
|
|
34
67
|
const view = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
35
68
|
return Buffer.from(view);
|
|
36
69
|
}
|
|
70
|
+
function toBufferOrNull(value) {
|
|
71
|
+
if (!value) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (Buffer.isBuffer(value)) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
78
|
+
return toBuffer(value);
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === 'string') {
|
|
81
|
+
try {
|
|
82
|
+
return fromBase64Url(value);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
async function spkiToCosePublicKey(spki) {
|
|
91
|
+
try {
|
|
92
|
+
const subtle = globalThis.crypto?.subtle ?? (await Promise.resolve().then(() => __importStar(require('crypto')))).webcrypto?.subtle;
|
|
93
|
+
if (!subtle) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const key = await subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']);
|
|
97
|
+
const raw = Buffer.from(await subtle.exportKey('raw', key));
|
|
98
|
+
if (raw.length !== 65 || raw[0] !== 0x04) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const x = raw.slice(1, 33);
|
|
102
|
+
const y = raw.slice(33, 65);
|
|
103
|
+
const coseMap = new Map([
|
|
104
|
+
[1, 2], // kty: EC2
|
|
105
|
+
[3, -7], // alg: ES256
|
|
106
|
+
[-1, 1], // crv: P-256
|
|
107
|
+
[-2, x],
|
|
108
|
+
[-3, y]
|
|
109
|
+
]);
|
|
110
|
+
return Buffer.from(helpers_1.isoCBOR.encode(coseMap));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
37
116
|
class PasskeyService {
|
|
38
117
|
constructor(config, adapter, logger = console) {
|
|
39
118
|
this.config = config;
|
|
40
119
|
this.adapter = adapter;
|
|
41
120
|
this.logger = logger;
|
|
42
121
|
}
|
|
122
|
+
async listUserCredentials(userId) {
|
|
123
|
+
return this.adapter.listUserCredentials(userId);
|
|
124
|
+
}
|
|
125
|
+
async deleteCredential(credentialId) {
|
|
126
|
+
return this.adapter.deleteCredential(credentialId);
|
|
127
|
+
}
|
|
43
128
|
async createChallenge(params) {
|
|
44
129
|
await this.adapter.cleanupChallenges?.(new Date());
|
|
45
130
|
const metadata = {
|
|
@@ -107,7 +192,8 @@ class PasskeyService {
|
|
|
107
192
|
return {
|
|
108
193
|
challenge: options.challenge,
|
|
109
194
|
expiresAt: expiresAt.toISOString(),
|
|
110
|
-
userId: user.id
|
|
195
|
+
userId: user.id,
|
|
196
|
+
publicKey: options
|
|
111
197
|
};
|
|
112
198
|
}
|
|
113
199
|
async createAuthenticationChallenge(params, metadata) {
|
|
@@ -136,7 +222,8 @@ class PasskeyService {
|
|
|
136
222
|
return {
|
|
137
223
|
challenge: options.challenge,
|
|
138
224
|
expiresAt: expiresAt.toISOString(),
|
|
139
|
-
userId: user.id
|
|
225
|
+
userId: user.id,
|
|
226
|
+
publicKey: options
|
|
140
227
|
};
|
|
141
228
|
}
|
|
142
229
|
async verifyRegistration(params, record) {
|
|
@@ -155,14 +242,57 @@ class PasskeyService {
|
|
|
155
242
|
requireUserVerification: true
|
|
156
243
|
});
|
|
157
244
|
if (!result.verified || !result.registrationInfo) {
|
|
245
|
+
if (!result.verified) {
|
|
246
|
+
const err = result.error ?? result;
|
|
247
|
+
this.logger.error?.('Passkey registration verification failed', err);
|
|
248
|
+
}
|
|
158
249
|
return { verified: false };
|
|
159
250
|
}
|
|
160
251
|
const registrationInfo = result.registrationInfo;
|
|
252
|
+
const attestationResponse = params.response?.response;
|
|
253
|
+
const credentialIdPrimary = toBufferOrNull(registrationInfo.credentialID);
|
|
254
|
+
const credentialIdFallback = toBufferOrNull(params.response?.id);
|
|
255
|
+
const credentialId = credentialIdPrimary && credentialIdPrimary.length > 0 ? credentialIdPrimary : credentialIdFallback;
|
|
256
|
+
const publicKeyPrimary = toBufferOrNull(registrationInfo.credentialPublicKey);
|
|
257
|
+
let publicKeyFallback = toBufferOrNull(attestationResponse?.publicKey);
|
|
258
|
+
if ((!publicKeyPrimary || publicKeyPrimary.length === 0) && attestationResponse?.attestationObject) {
|
|
259
|
+
try {
|
|
260
|
+
const attObj = (0, helpers_1.decodeAttestationObject)(helpers_1.isoBase64URL.toBuffer(attestationResponse.attestationObject));
|
|
261
|
+
const parsedAuth = (0, helpers_1.parseAuthenticatorData)(attObj.get('authData'));
|
|
262
|
+
publicKeyFallback = toBufferOrNull(parsedAuth.credentialPublicKey) ?? publicKeyFallback;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
this.logger.warn?.('Passkey registration: failed to parse attestationObject', error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const publicKey = publicKeyPrimary && publicKeyPrimary.length > 0 ? publicKeyPrimary : publicKeyFallback;
|
|
269
|
+
if (this.logger?.warn) {
|
|
270
|
+
const pkPrimaryHex = publicKeyPrimary ? publicKeyPrimary.slice(0, 4).toString('hex') : 'null';
|
|
271
|
+
const pkFallbackHex = publicKeyFallback ? publicKeyFallback.slice(0, 4).toString('hex') : 'null';
|
|
272
|
+
this.logger.warn(`Passkey registration: pkPrimary len=${publicKeyPrimary?.length ?? 0} head=${pkPrimaryHex}, pkFallback len=${publicKeyFallback?.length ?? 0} head=${pkFallbackHex}`);
|
|
273
|
+
}
|
|
274
|
+
if (!credentialId || credentialId.length === 0) {
|
|
275
|
+
return { verified: false, message: 'Missing credential id in registration response' };
|
|
276
|
+
}
|
|
277
|
+
if (!publicKey || publicKey.length === 0) {
|
|
278
|
+
return { verified: false, message: 'Missing public key in registration response' };
|
|
279
|
+
}
|
|
280
|
+
let storedPublicKey = Buffer.isBuffer(publicKey) ? publicKey : toBuffer(publicKey);
|
|
281
|
+
// If fallback is an SPKI key (DER, starts with 0x30) convert to COSE so verification succeeds.
|
|
282
|
+
if (storedPublicKey[0] === 0x30) {
|
|
283
|
+
const converted = await spkiToCosePublicKey(storedPublicKey);
|
|
284
|
+
if (converted) {
|
|
285
|
+
storedPublicKey = converted;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
this.logger.warn?.('Passkey registration: failed to convert SPKI public key to COSE');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
161
291
|
await this.adapter.saveCredential({
|
|
162
292
|
userId: user.id,
|
|
163
|
-
credentialId
|
|
164
|
-
publicKey:
|
|
165
|
-
counter: registrationInfo.counter,
|
|
293
|
+
credentialId,
|
|
294
|
+
publicKey: storedPublicKey,
|
|
295
|
+
counter: registrationInfo.counter ?? 0,
|
|
166
296
|
transports: sanitizeTransports(params.response.transports),
|
|
167
297
|
backedUp: registrationInfo.credentialDeviceType === 'multiDevice',
|
|
168
298
|
deviceType: registrationInfo.credentialDeviceType
|
|
@@ -182,22 +312,27 @@ class PasskeyService {
|
|
|
182
312
|
}
|
|
183
313
|
const user = await this.requireUser({ userId: credential.userId, login: record.login });
|
|
184
314
|
const storedAuthData = {
|
|
185
|
-
|
|
315
|
+
id: credential.credentialId,
|
|
316
|
+
publicKey: toBuffer(credential.publicKey),
|
|
317
|
+
credentialPublicKey: toBuffer(credential.publicKey),
|
|
186
318
|
counter: credential.counter,
|
|
319
|
+
transports: credential.transports ?? undefined,
|
|
187
320
|
credentialBackedUp: credential.backedUp,
|
|
188
|
-
credentialDeviceType: credential.deviceType
|
|
189
|
-
|
|
190
|
-
|
|
321
|
+
credentialDeviceType: credential.deviceType
|
|
322
|
+
// simplewebauthn accepts either Uint8Array or Buffer; ensure Buffer
|
|
323
|
+
// see https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/server/src/authentication/verifyAuthenticationResponse.ts
|
|
191
324
|
};
|
|
192
325
|
const result = await (0, server_1.verifyAuthenticationResponse)({
|
|
193
326
|
response,
|
|
194
327
|
expectedChallenge: record.challenge,
|
|
195
328
|
expectedOrigin: this.config.origins,
|
|
196
329
|
expectedRPID: this.config.rpId,
|
|
197
|
-
|
|
330
|
+
credential: storedAuthData,
|
|
198
331
|
requireUserVerification: true
|
|
199
332
|
});
|
|
200
333
|
if (!result.verified) {
|
|
334
|
+
const err = result.error ?? result;
|
|
335
|
+
this.logger.error?.('Passkey authentication verification failed', err);
|
|
201
336
|
return { verified: false };
|
|
202
337
|
}
|
|
203
338
|
await this.adapter.updateCredentialCounter(credential.credentialId, result.authenticationInfo.newCounter);
|
|
@@ -36,6 +36,8 @@ export interface StoredPasskeyCredential {
|
|
|
36
36
|
transports?: AuthenticatorTransportFuture[];
|
|
37
37
|
backedUp: boolean;
|
|
38
38
|
deviceType: CredentialDeviceType;
|
|
39
|
+
createdAt?: Date;
|
|
40
|
+
updatedAt?: Date;
|
|
39
41
|
}
|
|
40
42
|
export interface PasskeyStorageAdapter {
|
|
41
43
|
resolveUser(params: {
|
|
@@ -43,6 +45,7 @@ export interface PasskeyStorageAdapter {
|
|
|
43
45
|
login?: string;
|
|
44
46
|
}): Promise<PasskeyUserDescriptor | null>;
|
|
45
47
|
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
48
|
+
deleteCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
46
49
|
findCredentialById(credentialId: Buffer): Promise<StoredPasskeyCredential | null>;
|
|
47
50
|
saveCredential(record: StoredPasskeyCredential): Promise<void>;
|
|
48
51
|
updateCredentialCounter(credentialId: Buffer, counter: number): Promise<void>;
|
package/dist/cjs/user/base.js
CHANGED
|
@@ -35,7 +35,8 @@ class UserStore {
|
|
|
35
35
|
toPublic(user) {
|
|
36
36
|
const mapped = this.toPublicUser(user);
|
|
37
37
|
if (mapped && typeof mapped === 'object') {
|
|
38
|
-
const { password, ...rest } = mapped;
|
|
38
|
+
const { password: _password, ...rest } = mapped;
|
|
39
|
+
void _password;
|
|
39
40
|
return rest;
|
|
40
41
|
}
|
|
41
42
|
return mapped;
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { Application, Request, Response } from 'express';
|
|
7
|
+
import { Application, Request, Response, type ErrorRequestHandler, type RequestHandler } from 'express';
|
|
8
8
|
import { ApiModule } from './api-module.js';
|
|
9
9
|
import { TokenStore, type JwtDecodeResult, type JwtSignResult, type JwtVerifyResult } from './token/base.js';
|
|
10
|
-
import type { ApiAuthClass, ApiKey } from './api-module.js';
|
|
10
|
+
import type { ApiAuthClass, ApiAuthType, ApiKey } from './api-module.js';
|
|
11
11
|
import type { AuthProviderModule } from './auth-api/module.js';
|
|
12
|
-
import type {
|
|
12
|
+
import type { AuthAdapter, AuthIdentifier } from './auth-api/types.js';
|
|
13
13
|
import type { OAuthStore } from './oauth/base.js';
|
|
14
14
|
import type { AuthCodeData, AuthCodeRequest, OAuthClient } from './oauth/types.js';
|
|
15
15
|
import type { PasskeyService } from './passkey/service.js';
|
|
16
|
-
import type { PasskeyChallenge, PasskeyChallengeParams, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
|
|
16
|
+
import type { PasskeyChallenge, PasskeyChallengeParams, StoredPasskeyCredential, PasskeyVerificationParams, PasskeyVerificationResult } from './passkey/types.js';
|
|
17
17
|
import type { Token } from './token/types.js';
|
|
18
18
|
import type { UserStore } from './user/base.js';
|
|
19
19
|
import type { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
|
@@ -47,6 +47,12 @@ export interface ApiRequest {
|
|
|
47
47
|
getRealUid: () => AuthIdentifier | null;
|
|
48
48
|
isImpersonating: () => boolean;
|
|
49
49
|
}
|
|
50
|
+
export interface ExpressApiRequest extends ExtendedReq {
|
|
51
|
+
apiReq?: ApiRequest;
|
|
52
|
+
}
|
|
53
|
+
export interface ExpressApiLocals {
|
|
54
|
+
apiReq?: ApiRequest;
|
|
55
|
+
}
|
|
50
56
|
export interface ClientAgentProfile {
|
|
51
57
|
ua: string;
|
|
52
58
|
browser: string;
|
|
@@ -101,6 +107,7 @@ export interface ApiServerConf {
|
|
|
101
107
|
devMode: boolean;
|
|
102
108
|
hydrateGetBody: boolean;
|
|
103
109
|
validateTokens: boolean;
|
|
110
|
+
refreshMaybe: boolean;
|
|
104
111
|
apiVersion: string;
|
|
105
112
|
minClientVersion: string;
|
|
106
113
|
tokenStore?: TokenStore;
|
|
@@ -122,23 +129,25 @@ export declare class ApiServer {
|
|
|
122
129
|
private canImpersonateAdapter;
|
|
123
130
|
private readonly jwtHelper;
|
|
124
131
|
constructor(config?: Partial<ApiServerConf>);
|
|
125
|
-
authStorage<UserRow, SafeUser>(storage:
|
|
132
|
+
authStorage<UserRow, SafeUser>(storage: AuthAdapter<UserRow, SafeUser>): this;
|
|
126
133
|
/**
|
|
127
134
|
* @deprecated Use {@link ApiServer.authStorage} instead.
|
|
128
135
|
*/
|
|
129
|
-
useAuthStorage<UserRow, SafeUser>(storage:
|
|
136
|
+
useAuthStorage<UserRow, SafeUser>(storage: AuthAdapter<UserRow, SafeUser>): this;
|
|
130
137
|
authModule<UserRow>(module: AuthProviderModule<UserRow>): this;
|
|
131
138
|
/**
|
|
132
139
|
* @deprecated Use {@link ApiServer.authModule} instead.
|
|
133
140
|
*/
|
|
134
141
|
useAuthModule<UserRow>(module: AuthProviderModule<UserRow>): this;
|
|
135
|
-
getAuthStorage():
|
|
142
|
+
getAuthStorage(): AuthAdapter<any, any>;
|
|
136
143
|
getAuthModule(): AuthProviderModule<any>;
|
|
137
144
|
setTokenStore(store: TokenStore): this;
|
|
138
145
|
getTokenStore(): TokenStore | null;
|
|
139
146
|
private ensureUserStore;
|
|
140
147
|
private ensureTokenStore;
|
|
141
148
|
private ensurePasskeyService;
|
|
149
|
+
listUserCredentials(userId: AuthIdentifier): Promise<StoredPasskeyCredential[]>;
|
|
150
|
+
deletePasskeyCredential(credentialId: Buffer | string): Promise<boolean>;
|
|
142
151
|
private ensureOAuthStore;
|
|
143
152
|
getUser(identifier: AuthIdentifier): Promise<any | null>;
|
|
144
153
|
getUserPasswordHash(user: any): string;
|
|
@@ -187,6 +196,9 @@ export declare class ApiServer {
|
|
|
187
196
|
private describeMissingEndpoint;
|
|
188
197
|
start(): this;
|
|
189
198
|
private verifyJWT;
|
|
199
|
+
private jwtCookieOptions;
|
|
200
|
+
private setAccessCookie;
|
|
201
|
+
private tryRefreshAccessToken;
|
|
190
202
|
private authenticate;
|
|
191
203
|
private tryAuthenticateApiKey;
|
|
192
204
|
private requiresAuthToken;
|
|
@@ -195,6 +207,14 @@ export declare class ApiServer {
|
|
|
195
207
|
private normalizeAuthIdentifier;
|
|
196
208
|
private extractTokenUserId;
|
|
197
209
|
private resolveRealUserId;
|
|
210
|
+
useExpress(path: string, ...handlers: Array<RequestHandler | ErrorRequestHandler>): this;
|
|
211
|
+
useExpress(...handlers: Array<RequestHandler | ErrorRequestHandler>): this;
|
|
212
|
+
private createApiRequest;
|
|
213
|
+
expressAuth(auth: {
|
|
214
|
+
type: ApiAuthType;
|
|
215
|
+
req: ApiAuthClass;
|
|
216
|
+
}): RequestHandler;
|
|
217
|
+
expressErrorHandler(): ErrorRequestHandler;
|
|
198
218
|
private handle_request;
|
|
199
219
|
api<T extends ApiModule<any>>(module: T): this;
|
|
200
220
|
dumpRequest(apiReq: ApiRequest): void;
|