@zintrust/core 0.7.8 → 0.9.0
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/package.json +1 -1
- package/src/auth/LoginFlow.d.ts.map +1 -1
- package/src/auth/LoginFlow.js +1 -3
- package/src/cli/OptionalCliExtensions.d.ts +1 -0
- package/src/cli/OptionalCliExtensions.d.ts.map +1 -1
- package/src/cli/OptionalCliExtensions.js +11 -1
- package/src/cli/commands/MySqlProxyCommand.d.ts.map +1 -1
- package/src/cli/commands/MySqlProxyCommand.js +1 -1
- package/src/cli/commands/ScheduleStartCommand.d.ts.map +1 -1
- package/src/cli/commands/ScheduleStartCommand.js +14 -9
- package/src/cli/scaffolding/GovernanceScaffolder.d.ts.map +1 -1
- package/src/cli/scaffolding/GovernanceScaffolder.js +7 -2
- package/src/cli/scaffolding/ProjectScaffolder.d.ts.map +1 -1
- package/src/cli/scaffolding/ProjectScaffolder.js +3 -10
- package/src/helper/index.d.ts +6 -0
- package/src/helper/index.d.ts.map +1 -1
- package/src/helper/index.js +19 -8
- package/src/index.d.ts +7 -4
- package/src/index.d.ts.map +1 -1
- package/src/index.js +7 -5
- package/src/middleware/BulletproofAuthMiddleware.d.ts +2 -1
- package/src/middleware/BulletproofAuthMiddleware.d.ts.map +1 -1
- package/src/middleware/BulletproofAuthMiddleware.js +103 -39
- package/src/security/BulletproofDeviceStore.d.ts.map +1 -1
- package/src/security/BulletproofDeviceStore.js +144 -33
- package/src/security/JwtVerifier.d.ts +75 -0
- package/src/security/JwtVerifier.d.ts.map +1 -0
- package/src/security/JwtVerifier.js +336 -0
- package/src/templates/project/basic/app/Controllers/AuthController.ts.tpl +1 -1
- package/src/templates/project/basic/config/trace.ts.tpl +73 -0
- package/src/templates/project/basic/package.json.tpl +1 -1
- package/src/zintrust.plugins.d.ts +3 -6
- package/src/zintrust.plugins.d.ts.map +1 -1
- package/src/zintrust.plugins.js +3 -6
|
@@ -93,6 +93,59 @@ const parseBulletproofHeaders = (params) => {
|
|
|
93
93
|
};
|
|
94
94
|
return { ok: true, deviceId, timezone, signingHeaders };
|
|
95
95
|
};
|
|
96
|
+
const isMissingDeviceStoreRegistration = (error) => {
|
|
97
|
+
if (!(error instanceof Error))
|
|
98
|
+
return false;
|
|
99
|
+
return (error.message.includes("Database connection '") && error.message.includes('is not registered'));
|
|
100
|
+
};
|
|
101
|
+
const normalizeStaticSecrets = (staticSecrets) => {
|
|
102
|
+
return (staticSecrets?.map((s) => (typeof s === 'string' ? s.trim() : '')).filter((s) => s !== '') ?? []);
|
|
103
|
+
};
|
|
104
|
+
const createDynamicSignedRequestAttempt = (params) => {
|
|
105
|
+
return async () => {
|
|
106
|
+
try {
|
|
107
|
+
return await SignedRequest.verify({
|
|
108
|
+
...params.baseParams,
|
|
109
|
+
getSecretForKeyId: async (keyId) => {
|
|
110
|
+
const secretForKey = await params.getSecretForKeyId(keyId, params.req);
|
|
111
|
+
const normalized = typeof secretForKey === 'string' ? secretForKey.trim() : '';
|
|
112
|
+
return normalized === '' ? undefined : normalized;
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (params.hasStaticFallback && isMissingDeviceStoreRegistration(error)) {
|
|
118
|
+
return { ok: false, code: 'UNKNOWN_KEY', message: 'Unknown key' };
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
const collectSignedRequestAttempts = async (params) => {
|
|
125
|
+
const staticSecrets = normalizeStaticSecrets(params.staticSecrets);
|
|
126
|
+
const attemptResolvers = [
|
|
127
|
+
createDynamicSignedRequestAttempt({
|
|
128
|
+
req: params.req,
|
|
129
|
+
baseParams: params.baseParams,
|
|
130
|
+
getSecretForKeyId: params.getSecretForKeyId,
|
|
131
|
+
hasStaticFallback: staticSecrets.length > 0,
|
|
132
|
+
}),
|
|
133
|
+
...staticSecrets.map((secret) => async () => {
|
|
134
|
+
return SignedRequest.verify({
|
|
135
|
+
...params.baseParams,
|
|
136
|
+
getSecretForKeyId: () => secret,
|
|
137
|
+
});
|
|
138
|
+
}),
|
|
139
|
+
];
|
|
140
|
+
return Promise.all(attemptResolvers.map(async (attemptResolver) => {
|
|
141
|
+
try {
|
|
142
|
+
return { result: await attemptResolver(), error: undefined };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
return { result: undefined, error };
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
};
|
|
96
149
|
const verifySignedRequest = async (params) => {
|
|
97
150
|
const baseParams = {
|
|
98
151
|
method: params.req.getMethod?.() ?? 'GET',
|
|
@@ -108,35 +161,35 @@ const verifySignedRequest = async (params) => {
|
|
|
108
161
|
return params.verifyNonce(keyId, nonce, ttlMs);
|
|
109
162
|
},
|
|
110
163
|
};
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
const attemptOutcomes = await collectSignedRequestAttempts({
|
|
165
|
+
req: params.req,
|
|
166
|
+
baseParams,
|
|
167
|
+
getSecretForKeyId: params.getSecretForKeyId,
|
|
168
|
+
staticSecrets: params.staticSecrets,
|
|
169
|
+
});
|
|
170
|
+
const attempts = attemptOutcomes.flatMap((attemptOutcome) => {
|
|
171
|
+
return attemptOutcome.result === undefined ? [] : [attemptOutcome.result];
|
|
172
|
+
});
|
|
173
|
+
if (attempts.length === 0) {
|
|
174
|
+
const firstError = attemptOutcomes.find((attemptOutcome) => attemptOutcome.error !== undefined)?.error;
|
|
175
|
+
if (firstError !== undefined) {
|
|
176
|
+
throw firstError;
|
|
177
|
+
}
|
|
178
|
+
return { ok: false, message: 'Unauthorized' };
|
|
179
|
+
}
|
|
180
|
+
let signed = attempts[0];
|
|
181
|
+
for (const attempt of attempts) {
|
|
182
|
+
if (attempt.ok === true) {
|
|
183
|
+
signed = attempt;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (signed.ok !== true) {
|
|
188
|
+
const nonSignatureFailure = attempts.find((attempt) => attempt.ok === false && attempt.code !== 'INVALID_SIGNATURE');
|
|
189
|
+
if (nonSignatureFailure !== undefined) {
|
|
190
|
+
signed = nonSignatureFailure;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
140
193
|
if (!signed.ok) {
|
|
141
194
|
Logger.debug('Bulletproof auth signed-request verification failed', {
|
|
142
195
|
code: signed.code,
|
|
@@ -296,20 +349,31 @@ const resolveSigningConfig = (options) => {
|
|
|
296
349
|
const signingSecretFromEnv = Env.get('BULLETPROOF_SIGNING_SECRET', appKeyFallback).trim();
|
|
297
350
|
const signingSecret = (options.signingSecret ?? signingSecretFromEnv).trim();
|
|
298
351
|
const backupSecrets = parseBackupSecrets(Env.get('BULLETPROOF_SIGNING_SECRET_BK', ''));
|
|
352
|
+
const staticSigningSecrets = dedupeSecrets(backupSecrets.filter((secret) => secret.trim() !== '' && secret.trim() !== signingSecret));
|
|
299
353
|
const hasCustomResolver = typeof options.getSecretForKeyId === 'function';
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
354
|
+
let getSecretForKeyId;
|
|
355
|
+
if (hasCustomResolver) {
|
|
356
|
+
getSecretForKeyId = options.getSecretForKeyId;
|
|
357
|
+
}
|
|
358
|
+
else if (isNonEmptyString(signingSecret)) {
|
|
359
|
+
getSecretForKeyId = () => signingSecret;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
getSecretForKeyId = async (keyId) => {
|
|
363
|
+
const device = await BulletproofDeviceStore.findByDeviceId(keyId);
|
|
364
|
+
if (device && typeof device.signingSecret === 'string' && device.signingSecret !== '') {
|
|
305
365
|
return device.signingSecret;
|
|
306
|
-
|
|
366
|
+
}
|
|
367
|
+
return undefined;
|
|
307
368
|
};
|
|
369
|
+
}
|
|
308
370
|
const verifyNonce = options.verifyNonce ?? NonceReplay.createMemoryVerifier();
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
371
|
+
const resolvedStaticSigningSecrets = hasCustomResolver ? undefined : staticSigningSecrets;
|
|
372
|
+
return {
|
|
373
|
+
getSecretForKeyId,
|
|
374
|
+
verifyNonce,
|
|
375
|
+
staticSigningSecrets: resolvedStaticSigningSecrets,
|
|
376
|
+
};
|
|
313
377
|
};
|
|
314
378
|
const resolveBulletproof = (options) => {
|
|
315
379
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulletproofDeviceStore.d.ts","sourceRoot":"","sources":["../../../src/security/BulletproofDeviceStore.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BulletproofDeviceStore.d.ts","sourceRoot":"","sources":["../../../src/security/BulletproofDeviceStore.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,uBAAuB,GACjE,QAAQ,CAAC;IACP,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC,CAAC;AA0ML,eAAO,MAAM,sBAAsB;6BACF,MAAM,GAAG,OAAO,CAAC,6BAA6B,GAAG,IAAI,CAAC;mBA4BhE,uBAAuB,GAAG,OAAO,CAAC,6BAA6B,CAAC;+BAuDpD,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;EAavD,CAAC;AAEH,eAAe,sBAAsB,CAAC"}
|
|
@@ -17,6 +17,18 @@ const getTable = () => {
|
|
|
17
17
|
const normalizeString = (value) => {
|
|
18
18
|
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
19
19
|
};
|
|
20
|
+
const normalizeRequiredDeviceId = (value) => {
|
|
21
|
+
const normalized = normalizeString(value);
|
|
22
|
+
if (normalized !== undefined)
|
|
23
|
+
return normalized;
|
|
24
|
+
throw ErrorFactory.createValidationError('Bulletproof device store requires a non-empty deviceId');
|
|
25
|
+
};
|
|
26
|
+
const normalizeRequiredSigningSecret = (value) => {
|
|
27
|
+
const normalized = normalizeString(value);
|
|
28
|
+
if (normalized !== undefined)
|
|
29
|
+
return normalized;
|
|
30
|
+
throw ErrorFactory.createValidationError('Bulletproof device store requires a non-empty signingSecret');
|
|
31
|
+
};
|
|
20
32
|
const normalizeDate = (value) => {
|
|
21
33
|
if (value instanceof Date && Number.isFinite(value.getTime()))
|
|
22
34
|
return value;
|
|
@@ -26,10 +38,36 @@ const normalizeDate = (value) => {
|
|
|
26
38
|
}
|
|
27
39
|
return undefined;
|
|
28
40
|
};
|
|
29
|
-
const
|
|
30
|
-
return
|
|
41
|
+
const getErrorMessage = (error) => {
|
|
42
|
+
return error instanceof Error ? error.message : String(error);
|
|
43
|
+
};
|
|
44
|
+
const isSchemaError = (error) => {
|
|
45
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
46
|
+
return (message.includes('no such table') ||
|
|
47
|
+
message.includes('no such column') ||
|
|
48
|
+
message.includes('unknown column') ||
|
|
49
|
+
message.includes('does not exist') ||
|
|
50
|
+
message.includes('undefined column') ||
|
|
51
|
+
message.includes('missing column'));
|
|
52
|
+
};
|
|
53
|
+
const createStoreError = (table, operation, error, details) => {
|
|
54
|
+
if (typeof error === 'object' && error !== null) {
|
|
55
|
+
const code = error.code;
|
|
56
|
+
if (typeof code === 'string' && code === 'CONFIG_ERROR') {
|
|
57
|
+
return error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const message = getErrorMessage(error);
|
|
61
|
+
const detailPayload = { table, operation, error: message, ...details };
|
|
62
|
+
if (isSchemaError(error)) {
|
|
63
|
+
return ErrorFactory.createConfigError(`Bulletproof device store table '${table}' is missing required columns (run migrations)`, detailPayload);
|
|
64
|
+
}
|
|
65
|
+
return ErrorFactory.createDatabaseError(`Bulletproof device store ${operation} failed`, detailPayload);
|
|
66
|
+
};
|
|
67
|
+
const createInvalidStoredRecordError = (table, deviceId) => {
|
|
68
|
+
return ErrorFactory.createConfigError('Bulletproof device store returned an invalid record', {
|
|
31
69
|
table,
|
|
32
|
-
|
|
70
|
+
...(deviceId === undefined ? {} : { deviceId }),
|
|
33
71
|
});
|
|
34
72
|
};
|
|
35
73
|
const toStoredRecord = (row) => {
|
|
@@ -57,63 +95,135 @@ const toStoredRecord = (row) => {
|
|
|
57
95
|
: { updatedAt: normalizeDate(row['updated_at']) }),
|
|
58
96
|
};
|
|
59
97
|
};
|
|
60
|
-
const
|
|
98
|
+
const buildInsertPayload = (record) => {
|
|
61
99
|
const lastSeenAt = record.lastSeenAt.toISOString();
|
|
100
|
+
const deviceId = normalizeRequiredDeviceId(record.deviceId);
|
|
101
|
+
const signingSecret = normalizeRequiredSigningSecret(record.signingSecret);
|
|
62
102
|
return {
|
|
63
103
|
user_id: normalizeString(record.userId) ?? null,
|
|
64
|
-
device_id:
|
|
65
|
-
signing_secret:
|
|
104
|
+
device_id: deviceId,
|
|
105
|
+
signing_secret: signingSecret,
|
|
66
106
|
user_agent: normalizeString(record.userAgent) ?? null,
|
|
67
107
|
last_seen_at: lastSeenAt,
|
|
68
108
|
created_at: lastSeenAt,
|
|
69
109
|
updated_at: lastSeenAt,
|
|
70
110
|
};
|
|
71
111
|
};
|
|
112
|
+
const buildUpdatePayload = (record) => {
|
|
113
|
+
const lastSeenAt = record.lastSeenAt.toISOString();
|
|
114
|
+
const signingSecret = normalizeRequiredSigningSecret(record.signingSecret);
|
|
115
|
+
return {
|
|
116
|
+
user_id: normalizeString(record.userId) ?? null,
|
|
117
|
+
signing_secret: signingSecret,
|
|
118
|
+
user_agent: normalizeString(record.userAgent) ?? null,
|
|
119
|
+
last_seen_at: lastSeenAt,
|
|
120
|
+
updated_at: lastSeenAt,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
const buildIgnoreInsert = (db, table, columns, conflictColumns) => {
|
|
124
|
+
const columnList = columns.join(', ');
|
|
125
|
+
const placeholders = columns.map(() => '?').join(', ');
|
|
126
|
+
const driver = db.getType();
|
|
127
|
+
if (driver === 'sqlite' || driver === 'd1' || driver === 'd1-remote') {
|
|
128
|
+
return `INSERT OR IGNORE INTO ${table} (${columnList}) VALUES (${placeholders})`;
|
|
129
|
+
}
|
|
130
|
+
if (driver === 'mysql') {
|
|
131
|
+
return `INSERT IGNORE INTO ${table} (${columnList}) VALUES (${placeholders})`;
|
|
132
|
+
}
|
|
133
|
+
if (driver === 'postgresql') {
|
|
134
|
+
return `INSERT INTO ${table} (${columnList}) VALUES (${placeholders}) ON CONFLICT (${conflictColumns.join(', ')}) DO NOTHING`;
|
|
135
|
+
}
|
|
136
|
+
if (driver === 'sqlserver') {
|
|
137
|
+
const sourceColumns = columns.map((_, index) => `v${index + 1}`);
|
|
138
|
+
const selectClause = sourceColumns.map((name) => `? AS ${name}`).join(', ');
|
|
139
|
+
const conflictClause = conflictColumns
|
|
140
|
+
.map((column) => `target.${column} = source.${column}`)
|
|
141
|
+
.join(' AND ');
|
|
142
|
+
const insertValues = columns.map((column) => `source.${column}`).join(', ');
|
|
143
|
+
const sourceProjection = columns
|
|
144
|
+
.map((column, index) => `${sourceColumns[index]} AS ${column}`)
|
|
145
|
+
.join(', ');
|
|
146
|
+
return [
|
|
147
|
+
`MERGE INTO ${table} WITH (HOLDLOCK) AS target`,
|
|
148
|
+
`USING (SELECT ${sourceProjection} FROM (SELECT ${selectClause}) seed) AS source`,
|
|
149
|
+
`ON ${conflictClause}`,
|
|
150
|
+
`WHEN NOT MATCHED THEN INSERT (${columnList}) VALUES (${insertValues});`,
|
|
151
|
+
].join(' ');
|
|
152
|
+
}
|
|
153
|
+
return `INSERT INTO ${table} (${columnList}) VALUES (${placeholders})`;
|
|
154
|
+
};
|
|
72
155
|
export const BulletproofDeviceStore = Object.freeze({
|
|
73
156
|
async findByDeviceId(deviceId) {
|
|
74
157
|
if (!isNonEmptyString(deviceId))
|
|
75
158
|
return null;
|
|
76
159
|
const db = useDatabase(undefined, getConnection());
|
|
77
160
|
const table = getTable();
|
|
161
|
+
const normalizedDeviceId = deviceId.trim();
|
|
78
162
|
try {
|
|
79
163
|
const row = await db
|
|
80
164
|
.table(table)
|
|
81
|
-
.where('device_id', '=',
|
|
165
|
+
.where('device_id', '=', normalizedDeviceId)
|
|
82
166
|
.first();
|
|
83
|
-
|
|
167
|
+
if (row === null) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const normalized = toStoredRecord(row);
|
|
171
|
+
if (normalized === null) {
|
|
172
|
+
throw createInvalidStoredRecordError(table, normalizedDeviceId);
|
|
173
|
+
}
|
|
174
|
+
return normalized;
|
|
84
175
|
}
|
|
85
176
|
catch (error) {
|
|
86
|
-
throw
|
|
177
|
+
throw createStoreError(table, 'lookup', error, { deviceId: normalizedDeviceId });
|
|
87
178
|
}
|
|
88
179
|
},
|
|
89
180
|
async upsert(record) {
|
|
90
181
|
const db = useDatabase(undefined, getConnection());
|
|
91
182
|
const table = getTable();
|
|
92
|
-
const
|
|
183
|
+
const deviceId = normalizeRequiredDeviceId(record.deviceId);
|
|
184
|
+
const normalizedUserId = normalizeString(record.userId);
|
|
185
|
+
const normalizedUserAgent = normalizeString(record.userAgent);
|
|
186
|
+
const normalizedRecord = {
|
|
187
|
+
...record,
|
|
188
|
+
deviceId,
|
|
189
|
+
signingSecret: normalizeRequiredSigningSecret(record.signingSecret),
|
|
190
|
+
...(normalizedUserId === undefined ? {} : { userId: normalizedUserId }),
|
|
191
|
+
...(normalizedUserAgent === undefined ? {} : { userAgent: normalizedUserAgent }),
|
|
192
|
+
};
|
|
193
|
+
const insertPayload = buildInsertPayload(normalizedRecord);
|
|
194
|
+
const updatePayload = buildUpdatePayload(normalizedRecord);
|
|
195
|
+
const insertColumns = Object.keys(insertPayload);
|
|
196
|
+
const insertValues = insertColumns.map((column) => insertPayload[column]);
|
|
197
|
+
const insertSql = buildIgnoreInsert(db, table, insertColumns, ['device_id']);
|
|
93
198
|
try {
|
|
94
|
-
await db.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
199
|
+
return await db.transaction(async (transactionDb) => {
|
|
200
|
+
await transactionDb.table(table).where('device_id', '=', deviceId).update(updatePayload);
|
|
201
|
+
try {
|
|
202
|
+
await transactionDb.execute(insertSql, insertValues);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
const duplicateMessage = getErrorMessage(error).toLowerCase();
|
|
206
|
+
const isDuplicateKeyError = duplicateMessage.includes('duplicate') ||
|
|
207
|
+
duplicateMessage.includes('unique constraint') ||
|
|
208
|
+
duplicateMessage.includes('unique failed') ||
|
|
209
|
+
duplicateMessage.includes('duplicate key');
|
|
210
|
+
if (!isDuplicateKeyError) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const stored = await transactionDb
|
|
215
|
+
.table(table)
|
|
216
|
+
.where('device_id', '=', deviceId)
|
|
217
|
+
.first();
|
|
218
|
+
const normalized = stored === null ? null : toStoredRecord(stored);
|
|
219
|
+
if (normalized === null) {
|
|
220
|
+
throw createInvalidStoredRecordError(table, deviceId);
|
|
221
|
+
}
|
|
222
|
+
return normalized;
|
|
223
|
+
});
|
|
114
224
|
}
|
|
115
225
|
catch (error) {
|
|
116
|
-
throw
|
|
226
|
+
throw createStoreError(table, 'upsert', error, { deviceId });
|
|
117
227
|
}
|
|
118
228
|
},
|
|
119
229
|
async removeByDeviceId(deviceId) {
|
|
@@ -121,11 +231,12 @@ export const BulletproofDeviceStore = Object.freeze({
|
|
|
121
231
|
return;
|
|
122
232
|
const db = useDatabase(undefined, getConnection());
|
|
123
233
|
const table = getTable();
|
|
234
|
+
const normalizedDeviceId = deviceId.trim();
|
|
124
235
|
try {
|
|
125
|
-
await db.table(table).where('device_id', '=',
|
|
236
|
+
await db.table(table).where('device_id', '=', normalizedDeviceId).delete();
|
|
126
237
|
}
|
|
127
238
|
catch (error) {
|
|
128
|
-
throw
|
|
239
|
+
throw createStoreError(table, 'delete', error, { deviceId: normalizedDeviceId });
|
|
129
240
|
}
|
|
130
241
|
},
|
|
131
242
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { JwtPayload } from './JwtManager';
|
|
2
|
+
export type JwtVerifierAlgorithm = 'RS256';
|
|
3
|
+
type JwtVerifierJsonWebKey = Readonly<{
|
|
4
|
+
alg?: string;
|
|
5
|
+
crv?: string;
|
|
6
|
+
d?: string;
|
|
7
|
+
dp?: string;
|
|
8
|
+
dq?: string;
|
|
9
|
+
e?: string;
|
|
10
|
+
ext?: boolean;
|
|
11
|
+
k?: string;
|
|
12
|
+
key_ops?: string[];
|
|
13
|
+
kid?: string;
|
|
14
|
+
kty?: string;
|
|
15
|
+
n?: string;
|
|
16
|
+
oth?: unknown[];
|
|
17
|
+
p?: string;
|
|
18
|
+
q?: string;
|
|
19
|
+
qi?: string;
|
|
20
|
+
use?: string;
|
|
21
|
+
x?: string;
|
|
22
|
+
x5c?: string[];
|
|
23
|
+
x5t?: string;
|
|
24
|
+
'x5t#S256'?: string;
|
|
25
|
+
x5u?: string;
|
|
26
|
+
y?: string;
|
|
27
|
+
}>;
|
|
28
|
+
export type JwtVerifierJwk = JwtVerifierJsonWebKey & Readonly<{
|
|
29
|
+
kid?: string;
|
|
30
|
+
alg?: string;
|
|
31
|
+
use?: string;
|
|
32
|
+
}>;
|
|
33
|
+
export type JwtVerifierJwksDocument = Readonly<{
|
|
34
|
+
keys: readonly JwtVerifierJwk[];
|
|
35
|
+
}>;
|
|
36
|
+
export type JwtVerifierFailureReason = 'invalid_token_format' | 'invalid_header' | 'invalid_payload' | 'missing_kid' | 'key_not_found' | 'jwks_fetch_failed' | 'invalid_jwks' | 'unsupported_algorithm' | 'invalid_jwk' | 'invalid_signature' | 'issuer_mismatch' | 'audience_mismatch' | 'token_expired' | 'token_not_yet_valid';
|
|
37
|
+
export type JwtVerifierFailure = Readonly<{
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: JwtVerifierFailureReason;
|
|
40
|
+
message: string;
|
|
41
|
+
details?: unknown;
|
|
42
|
+
}>;
|
|
43
|
+
export type JwtVerifierSuccess = Readonly<{
|
|
44
|
+
ok: true;
|
|
45
|
+
payload: JwtPayload;
|
|
46
|
+
header: Readonly<Record<string, unknown>>;
|
|
47
|
+
jwk: JwtVerifierJwk;
|
|
48
|
+
cacheHit?: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
export type JwtVerifierResult = JwtVerifierSuccess | JwtVerifierFailure;
|
|
51
|
+
export type JwtVerifierCommonInput = Readonly<{
|
|
52
|
+
token: string;
|
|
53
|
+
algorithm?: JwtVerifierAlgorithm;
|
|
54
|
+
issuer?: string | readonly string[];
|
|
55
|
+
audience?: string | readonly string[];
|
|
56
|
+
nowMs?: number;
|
|
57
|
+
}>;
|
|
58
|
+
export type JwtVerifierWithJwkInput = JwtVerifierCommonInput & Readonly<{
|
|
59
|
+
jwk: JwtVerifierJwk;
|
|
60
|
+
}>;
|
|
61
|
+
export type JwtVerifierWithJwksInput = JwtVerifierCommonInput & Readonly<{
|
|
62
|
+
jwksUrl: string;
|
|
63
|
+
cacheKey?: string;
|
|
64
|
+
cacheTtlSeconds?: number;
|
|
65
|
+
fetcher?: typeof fetch;
|
|
66
|
+
}>;
|
|
67
|
+
export declare const JwtVerifier: Readonly<{
|
|
68
|
+
clearCache: (cacheKey?: string) => void;
|
|
69
|
+
verifyWithJwk: (input: JwtVerifierWithJwkInput) => Promise<JwtPayload>;
|
|
70
|
+
verifyWithJwkResult: (input: JwtVerifierWithJwkInput) => Promise<JwtVerifierResult>;
|
|
71
|
+
verifyWithJwks: (input: JwtVerifierWithJwksInput) => Promise<JwtPayload>;
|
|
72
|
+
verifyWithJwksResult: (input: JwtVerifierWithJwksInput) => Promise<JwtVerifierResult>;
|
|
73
|
+
}>;
|
|
74
|
+
export default JwtVerifier;
|
|
75
|
+
//# sourceMappingURL=JwtVerifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JwtVerifier.d.ts","sourceRoot":"","sources":["../../../src/security/JwtVerifier.ts"],"names":[],"mappings":"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"}
|