@adminforth/two-factors-auth 3.3.9 → 4.0.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.
Files changed (54) hide show
  1. package/build.log +2 -2
  2. package/custom/TwoFAModal.vue +4 -4
  3. package/custom/TwoFactorsConfirmation.vue +9 -10
  4. package/custom/TwoFactorsPasskeysSettings.vue +10 -10
  5. package/custom/utils.js +7 -8
  6. package/dist/custom/TwoFAModal.vue +4 -4
  7. package/dist/custom/TwoFactorsConfirmation.vue +9 -10
  8. package/dist/custom/TwoFactorsPasskeysSettings.vue +10 -10
  9. package/dist/custom/utils.js +7 -8
  10. package/dist/endpoints/registerPasskeyEndpoints.d.ts +2 -0
  11. package/dist/endpoints/registerPasskeyEndpoints.js +59 -0
  12. package/dist/endpoints/registerTwoFaEndpoints.d.ts +2 -0
  13. package/dist/endpoints/registerTwoFaEndpoints.js +46 -0
  14. package/dist/handlers/createPasskeyHandlers.d.ts +48 -0
  15. package/dist/handlers/createPasskeyHandlers.js +99 -0
  16. package/dist/handlers/createTwoFaHandlers.d.ts +56 -0
  17. package/dist/handlers/createTwoFaHandlers.js +167 -0
  18. package/dist/index.d.ts +37 -0
  19. package/dist/index.js +51 -858
  20. package/dist/repositories/passkeyRepository.d.ts +18 -0
  21. package/dist/repositories/passkeyRepository.js +89 -0
  22. package/dist/repositories/userRepository.d.ts +14 -0
  23. package/dist/repositories/userRepository.js +45 -0
  24. package/dist/services/cookieService.d.ts +52 -0
  25. package/dist/services/cookieService.js +144 -0
  26. package/dist/services/passkeyService.d.ts +84 -0
  27. package/dist/services/passkeyService.js +369 -0
  28. package/dist/services/totpService.d.ts +21 -0
  29. package/dist/services/totpService.js +43 -0
  30. package/dist/services/verifyMfaConfirmation.d.ts +30 -0
  31. package/dist/services/verifyMfaConfirmation.js +67 -0
  32. package/dist/types.d.ts +116 -0
  33. package/dist/utils/errors.d.ts +4 -0
  34. package/dist/utils/errors.js +9 -0
  35. package/dist/utils/parsePeriod.d.ts +1 -0
  36. package/dist/utils/parsePeriod.js +17 -0
  37. package/dist/utils/types.d.ts +13 -0
  38. package/dist/utils/types.js +1 -0
  39. package/endpoints/registerPasskeyEndpoints.ts +61 -0
  40. package/endpoints/registerTwoFaEndpoints.ts +45 -0
  41. package/handlers/createPasskeyHandlers.ts +103 -0
  42. package/handlers/createTwoFaHandlers.ts +172 -0
  43. package/index.ts +57 -886
  44. package/package.json +1 -1
  45. package/repositories/passkeyRepository.ts +78 -0
  46. package/repositories/userRepository.ts +42 -0
  47. package/services/cookieService.ts +165 -0
  48. package/services/passkeyService.ts +375 -0
  49. package/services/totpService.ts +37 -0
  50. package/services/verifyMfaConfirmation.ts +92 -0
  51. package/tsconfig.json +1 -1
  52. package/utils/errors.ts +13 -0
  53. package/utils/parsePeriod.ts +19 -0
  54. package/utils/types.ts +17 -0
package/dist/index.js CHANGED
@@ -7,11 +7,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { AdminForthPlugin, Filters, suggestIfTypo, convertPeriodToSeconds } from "adminforth";
11
- import twofactor from 'node-2fa';
12
- import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server';
13
- import { isoUint8Array, isoBase64URL } from '@simplewebauthn/server/helpers';
14
- import aaguids from './custom/aaguid.json';
10
+ import { AdminForthPlugin, suggestIfTypo } from "adminforth";
11
+ import { PasskeyService } from "./services/passkeyService.js";
12
+ import { TotpService } from "./services/totpService.js";
13
+ import { verifyMfaConfirmation } from "./services/verifyMfaConfirmation.js";
14
+ import { CookieService } from "./services/cookieService.js";
15
+ import { UserRepository } from "./repositories/userRepository.js";
16
+ import { registerPasskeyEndpoints } from "./endpoints/registerPasskeyEndpoints.js";
17
+ import { registerTwoFaEndpoints } from "./endpoints/registerTwoFaEndpoints.js";
18
+ import { createPasskeyHandlers } from "./handlers/createPasskeyHandlers.js";
19
+ import { createTwoFaHandlers } from "./handlers/createTwoFaHandlers.js";
20
+ import { parsePeriod } from "./utils/parsePeriod.js";
15
21
  import crypto from 'crypto';
16
22
  export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
17
23
  constructor(options) {
@@ -23,66 +29,22 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
23
29
  instanceUniqueRepresentation(pluginOptions) {
24
30
  return `single`;
25
31
  }
26
- parseIfNeeded(value) {
27
- if (typeof value === 'string') {
28
- try {
29
- return JSON.parse(value);
30
- }
31
- catch (e) {
32
- throw new Error('Failed to parse JSON string: ' + e.message);
33
- }
34
- }
35
- return value;
36
- }
37
- useChellenge(challenge, expiresIn) {
38
- const expiresInSeconds = expiresIn ? convertPeriodToSeconds(expiresIn) : undefined;
39
- this.options.passkeys.keyValueAdapter.set(challenge, 'stub_value', expiresInSeconds);
40
- }
41
- checkIfChellengeNotUsed(challenge) {
42
- return __awaiter(this, void 0, void 0, function* () {
43
- const res = yield this.options.passkeys.keyValueAdapter.get(challenge);
44
- if (!res) {
45
- return true;
46
- }
47
- return false;
48
- });
49
- }
50
- validateCookiesForPasskeyLogin(cookies) {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- var _a;
53
- const passkeysCookies = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: `passkeyLoginTemporaryJWT` });
54
- if (!passkeysCookies) {
55
- return { ok: false, error: 'Passkey token is required' };
56
- }
57
- const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'tempLoginPasskeyChallenge', false);
58
- const isChallangeValid = yield this.checkIfChellengeNotUsed(decodedPasskeysCookies.challenge);
59
- if (isChallangeValid) {
60
- this.useChellenge(decodedPasskeysCookies.challenge, ((_a = this.options.passkeys) === null || _a === void 0 ? void 0 : _a.challengeValidityPeriod) || '2m');
61
- }
62
- if (!decodedPasskeysCookies || !isChallangeValid) {
63
- return { ok: false, error: 'Invalid passkey' };
64
- }
65
- return { ok: true, decodedPasskeysCookies };
66
- });
67
- }
68
32
  checkIfSkipSetupAllowSkipVerify(adminUser) {
69
33
  return __awaiter(this, void 0, void 0, function* () {
70
34
  if (this.options.usersFilterToAllowSkipSetup) {
71
- const res = yield this.options.usersFilterToAllowSkipSetup(adminUser); // recieve result of usersFilterToAllowSkipSetup
35
+ const res = this.options.usersFilterToAllowSkipSetup(adminUser); // receive result of usersFilterToAllowSkipSetup
72
36
  if (res === false) { // if false, user is not allowed to skip anyway, so doesn't matter if they have 2FA set up or not
73
37
  return { skipAllowed: false };
74
38
  }
75
- //recieve user's record
76
- const usersResource = this.adminforth.config.resources.find(r => r.resourceId === this.adminforth.config.auth.usersResourceId);
77
- const usersPrimaryKeyColumn = usersResource.columns.find((col) => col.primaryKey);
78
- const userPkFieldName = usersPrimaryKeyColumn.name;
79
- const userRecord = yield this.adminforth.resource(this.adminforth.config.auth.usersResourceId).get([Filters.EQ(userPkFieldName, adminUser.pk)]);
39
+ //receive user's record
40
+ const userPkFieldName = this.userRepository.getUserPkField();
41
+ const userRecord = yield this.userRepository.getAuthUser(adminUser.pk);
80
42
  //check if user has 2FA set up
81
- const users2FASecret = userRecord[this.options.twoFaSecretFieldName];
43
+ const users2FASecret = this.userRepository.getSecret(userRecord);
82
44
  //check if user has any passkeys registered
83
- const passkeys = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).list([Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.dbUser[userPkFieldName])]);
45
+ const userHasPasskeys = yield this.passkeyService.hasPasskeysForUser(adminUser.dbUser[userPkFieldName]);
84
46
  // If user has either 2FA secret or any passkeys, they cannot skip
85
- if (users2FASecret || (passkeys && passkeys.length > 0)) {
47
+ if (users2FASecret || userHasPasskeys) {
86
48
  return { skipAllowed: false };
87
49
  }
88
50
  return { skipAllowed: res };
@@ -90,61 +52,6 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
90
52
  return { skipAllowed: false };
91
53
  });
92
54
  }
93
- generateHashForStepUpMfaGraceCookie(headers, cookies) {
94
- const ip = this.adminforth.auth.getClientIp(headers);
95
- const userAgent = headers['user-agent'] || '';
96
- const acceptLanguage = headers['accept-language'] || '';
97
- const session_cookie = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: "jwt" });
98
- if (!ip || !userAgent || !acceptLanguage || !session_cookie) {
99
- console.error("❗️❗️❗️ Cannot set step-up MFA grace cookie: missing required request headers to identify client ❗️❗️❗️");
100
- return null;
101
- }
102
- else {
103
- const hmac = crypto.createHmac('sha256', process.env.ADMINFORTH_SECRET)
104
- .update(`${acceptLanguage}_${userAgent}_${ip}_${session_cookie}`)
105
- .digest('hex');
106
- return hmac;
107
- }
108
- }
109
- issueTempSkip2FAGraceJWT(opts, cookies, response) {
110
- if (response) {
111
- if (opts.extra.headers) {
112
- const hash = this.generateHashForStepUpMfaGraceCookie(opts.extra.headers, cookies);
113
- if (!hash) {
114
- return;
115
- }
116
- const jwt = this.adminforth.auth.issueJWT({ hash: hash }, 'MfaGrace', `${this.options.stepUpMfaGracePeriodSeconds}s`);
117
- //TODO: fix ts-ignore after releasing new version of adminforth with updated types
118
- //@ts-ignore
119
- this.adminforth.auth.setCustomCookie({ response: response, payload: { name: "TempSkip2FA_Modal_JWT", sessionBased: true, value: jwt, httpOnly: true } });
120
- }
121
- }
122
- else {
123
- console.error("❗️❗️❗️ Cannot set step-up MFA grace cookie: response object is missing. You probably called verify() method without response parameter ❗️❗️❗️");
124
- }
125
- }
126
- isTempSkip2FAGraceValid(headers_1, cookies_1) {
127
- return __awaiter(this, arguments, void 0, function* (headers, cookies, checkIfJWTAboutToExpire = false) {
128
- const hash = this.generateHashForStepUpMfaGraceCookie(headers, cookies);
129
- if (!hash) {
130
- return false;
131
- }
132
- const jwt = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: "TempSkip2FA_Modal_JWT" });
133
- const jwtVerificationResult = yield this.adminforth.auth.verify(jwt, 'MfaGrace', false);
134
- if (!jwtVerificationResult) {
135
- return false;
136
- }
137
- const jwtHash = jwtVerificationResult['hash'];
138
- if (checkIfJWTAboutToExpire && (jwtVerificationResult["exp"] - (Date.now() / 1000)) < 30) {
139
- console.error("❗️❗️❗️ Cannot validate step-up MFA grace cookie: token is expired or about to expire ❗️❗️❗️");
140
- return false;
141
- }
142
- if (hash === jwtHash) {
143
- return true;
144
- }
145
- return false;
146
- });
147
- }
148
55
  waitForResponse(id, timeoutMs) {
149
56
  return new Promise((resolve) => {
150
57
  let timeout;
@@ -181,167 +88,34 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
181
88
  }
182
89
  verify(confirmationResult, opts) {
183
90
  return __awaiter(this, void 0, void 0, function* () {
184
- var _a, _b, _c, _d, _e, _f, _g;
185
- if (!confirmationResult)
186
- return { error: "Confirmation result is required" };
187
- if (!opts.adminUser)
188
- return { error: "Admin user is required" };
189
- if (!opts.userPk)
190
- return { error: "User PK is required" };
191
- const cookies = ((_a = opts.extra) === null || _a === void 0 ? void 0 : _a.cookies) || opts.cookies;
192
- const response = ((_b = opts.extra) === null || _b === void 0 ? void 0 : _b.response) || opts.response;
193
- if (this.options.usersFilterToApply) {
194
- const res = this.options.usersFilterToApply(opts.adminUser);
195
- if (res === false) {
196
- return { ok: true };
197
- }
198
- }
199
- if (this.options.usersFilterToAllowSkipSetup) {
200
- const res = yield this.checkIfSkipSetupAllowSkipVerify(opts.adminUser);
201
- if (res.skipAllowed === true) {
202
- return { ok: true };
203
- }
204
- }
205
- if (this.options.stepUpMfaGracePeriodSeconds && ((_c = opts.extra) === null || _c === void 0 ? void 0 : _c.headers) && !confirmationResult.mode) {
206
- const verificationResult = yield this.isTempSkip2FAGraceValid(opts.extra.headers, cookies);
207
- if (verificationResult === true) {
208
- return { ok: true };
209
- }
210
- }
211
- if (confirmationResult.mode === "totp") {
212
- const code = confirmationResult.result;
213
- const authRes = this.adminforth.config.resources
214
- .find(r => r.resourceId === this.adminforth.config.auth.usersResourceId);
215
- if (!authRes)
216
- return { error: "Auth resource not found" };
217
- const connector = this.adminforth.connectors[authRes.dataSource];
218
- const pkName = (_d = authRes.columns.find(c => c.primaryKey)) === null || _d === void 0 ? void 0 : _d.name;
219
- if (!pkName)
220
- return { error: "Primary key not found on auth resource" };
221
- const pk = (_e = opts === null || opts === void 0 ? void 0 : opts.userPk) !== null && _e !== void 0 ? _e : (_g = (_f = opts === null || opts === void 0 ? void 0 : opts.adminUser) === null || _f === void 0 ? void 0 : _f.dbUser) === null || _g === void 0 ? void 0 : _g[pkName];
222
- if (!pk)
223
- return { error: "User PK is required" };
224
- const user = yield connector.getRecordByPrimaryKey(authRes, pk);
225
- if (!user)
226
- return { error: "User not found" };
227
- const secret = user[this.options.twoFaSecretFieldName];
228
- if (!secret)
229
- return { error: "2FA is not set up for this user" };
230
- const verified = twofactor.verifyToken(secret, code, this.options.timeStepWindow);
231
- //* SET GRACE COOKIE *//
232
- if (verified) {
233
- if (this.options.stepUpMfaGracePeriodSeconds) {
234
- this.issueTempSkip2FAGraceJWT(opts, cookies, response);
235
- }
236
- return { ok: true };
237
- }
238
- else {
239
- return { error: "Wrong or expired OTP code" };
240
- }
241
- ;
242
- }
243
- else if (confirmationResult.mode === "passkey") {
244
- const cookiesValidationResult = yield this.validateCookiesForPasskeyLogin(cookies);
245
- if (!cookiesValidationResult.ok) {
246
- return { error: cookiesValidationResult.error };
247
- }
248
- const verificationResult = yield this.verifyPasskeyResponse(confirmationResult.result, opts.userPk, cookiesValidationResult.decodedPasskeysCookies);
249
- if (verificationResult.ok && verificationResult.passkeyConfirmed) {
250
- //* SET GRACE COOKIE *//
251
- if (this.options.stepUpMfaGracePeriodSeconds) {
252
- this.issueTempSkip2FAGraceJWT(opts, cookies, response);
253
- }
254
- return { ok: true };
255
- }
256
- return { error: "Invalid passkey" };
257
- }
258
- return { error: "Unsupported confirmation mode" };
259
- });
260
- }
261
- parsePeriod(period) {
262
- if (!period)
263
- return 0;
264
- const match = /^(\d+)([dhms])$/.exec(period.trim());
265
- if (!match)
266
- throw new Error(`Invalid suggestionPeriod format: ${period}`);
267
- const value = parseInt(match[1], 10);
268
- const unit = match[2];
269
- switch (unit) {
270
- case 'd': return value * 24 * 60 * 60 * 1000;
271
- case 'h': return value * 60 * 60 * 1000;
272
- case 'm': return value * 60 * 1000;
273
- case 's': return value * 1000;
274
- default: return value;
275
- }
276
- }
277
- verifyPasskeyResponse(body, user_id, cookies) {
278
- return __awaiter(this, void 0, void 0, function* () {
279
- var _a, _b, _c, _d, _e;
280
- const settingsOrigin = (_a = this.options.passkeys) === null || _a === void 0 ? void 0 : _a.settings.expectedOrigin;
281
- const expectedOrigin = body.origin;
282
- const expectedChallenge = cookies.challenge;
283
- const expectedRPID = ((_d = (_c = (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings) === null || _c === void 0 ? void 0 : _c.rp) === null || _d === void 0 ? void 0 : _d.id) || (new URL(settingsOrigin)).hostname;
284
- const response = JSON.parse(body.response);
285
- try {
286
- if (settingsOrigin !== expectedOrigin) {
287
- throw new Error(`Origin mismatch. Allowed in settings: ${settingsOrigin}, received from client: ${expectedOrigin}`);
288
- }
289
- const cred = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, response.id)]);
290
- if (!cred) {
291
- throw new Error('Credential not found.');
292
- }
293
- const credMeta = this.parseIfNeeded(cred[this.options.passkeys.credentialMetaFieldName]);
294
- if (!credMeta || !credMeta.public_key) {
295
- throw new Error('Credential public key not found.');
296
- }
297
- const userResourceId = this.adminforth.config.auth.usersResourceId;
298
- const usersResource = this.adminforth.config.resources.find(r => r.resourceId === userResourceId);
299
- const usersPrimaryKeyColumn = usersResource.columns.find((col) => col.primaryKey);
300
- const usersPrimaryKeyFieldName = usersPrimaryKeyColumn.name;
301
- const user = yield this.adminforth.resource(userResourceId).get([Filters.EQ(usersPrimaryKeyFieldName, cred[this.options.passkeys.credentialUserIdFieldName])]);
302
- if (!user || !user_id || user[usersPrimaryKeyFieldName] !== user_id) {
303
- throw new Error('User not found.');
304
- }
305
- const { verified, authenticationInfo } = yield verifyAuthenticationResponse({
306
- response,
307
- expectedChallenge,
308
- expectedOrigin: settingsOrigin,
309
- expectedRPID,
310
- credential: {
311
- id: cred[this.options.passkeys.credentialIdFieldName],
312
- publicKey: isoBase64URL.toBuffer(credMeta.public_key),
313
- counter: credMeta.counter,
314
- transports: credMeta.transports,
315
- },
316
- requireUserVerification: ((_e = this.options.passkeys) === null || _e === void 0 ? void 0 : _e.settings.authenticatorSelection.userVerification) === 'discouraged' ? false : true,
317
- });
318
- if (!verified) {
319
- return { ok: false, error: 'User verification failed.' };
320
- }
321
- credMeta.counter = authenticationInfo.newCounter;
322
- credMeta.last_used_at = new Date().toISOString();
323
- const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
324
- const credResourcePKColumn = credResource === null || credResource === void 0 ? void 0 : credResource.columns.find(c => c.primaryKey);
325
- if (!credResource || !credResourcePKColumn) {
326
- throw new Error('Credential resource or its primary key is not configured correctly');
327
- }
328
- const credResourcePKName = credResourcePKColumn.name;
329
- yield this.adminforth
330
- .resource(this.options.passkeys.credentialResourceID)
331
- .update(cred[credResourcePKName], { [this.options.passkeys.credentialMetaFieldName]: JSON.stringify(credMeta) });
332
- return { ok: true, passkeyConfirmed: true };
333
- }
334
- catch (e) {
335
- return { ok: false, error: 'Error authenticating passkey: ' + e };
336
- }
91
+ return verifyMfaConfirmation(this.ctx, confirmationResult, opts);
337
92
  });
338
93
  }
339
94
  modifyResourceConfig(adminforth, resourceConfig) {
340
- var _a;
95
+ var _a, _b;
341
96
  super.modifyResourceConfig(adminforth, resourceConfig);
342
97
  this.adminforth = adminforth;
343
98
  this.adminForthAuth = adminforth.auth;
344
- const suggestionPeriod = this.parsePeriod(((_a = this.options.passkeys) === null || _a === void 0 ? void 0 : _a.suggestionPeriod) || "5d");
99
+ this.userRepository = new UserRepository(this.adminforth, this.options);
100
+ this.cookieService = new CookieService(this.adminforth, this.options);
101
+ this.totpService = new TotpService(this.options, this.userRepository);
102
+ this.passkeyService = new PasskeyService(this.options, this.adminforth, this.userRepository, undefined, this.cookieService);
103
+ this.ctx = {
104
+ adminforth: this.adminforth,
105
+ options: this.options,
106
+ authResource: resourceConfig,
107
+ userRepository: this.userRepository,
108
+ totpService: this.totpService,
109
+ passkeyService: this.passkeyService,
110
+ cookieService: this.cookieService,
111
+ verifyMfaConfirmation: (confirmationResult, opts) => verifyMfaConfirmation(this.ctx, confirmationResult, opts),
112
+ checkIfSkipSetupAllowSkipVerify: this.checkIfSkipSetupAllowSkipVerify.bind(this),
113
+ autoVerify: {
114
+ verifyAuto: this.verifyAuto.bind(this),
115
+ resolveResponse: this.resolveResponse.bind(this),
116
+ },
117
+ };
118
+ const suggestionPeriod = parsePeriod(((_a = this.options.passkeys) === null || _a === void 0 ? void 0 : _a.suggestionPeriod) || "5d");
345
119
  const isPasskeysEnabled = this.options.passkeys ? true : false;
346
120
  const customPages = this.adminforth.config.customization.customPages;
347
121
  customPages.push({
@@ -365,15 +139,15 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
365
139
  slug: 'passkeys',
366
140
  component: this.componentPath('TwoFactorsPasskeysSettings.vue'),
367
141
  isVisible: (adminUser) => {
368
- const secret = adminUser.dbUser[this.options.twoFaSecretFieldName];
142
+ const secret = this.userRepository.getSecret(adminUser.dbUser);
369
143
  if (!secret || secret.length === 0) {
370
144
  return false;
371
145
  }
372
146
  return true;
373
147
  }
374
148
  });
375
- if (this.options.passkeys.allowLoginWithPasskeys !== false) {
376
- this.options.passkeys.allowLoginWithPasskeys = true;
149
+ const allowLoginWithPasskeys = (_b = this.options.passkeys.allowLoginWithPasskeys) !== null && _b !== void 0 ? _b : true;
150
+ if (allowLoginWithPasskeys) {
377
151
  if (!this.adminforth.config.customization.loginPageInjections) {
378
152
  this.adminforth.config.customization.loginPageInjections = { underLoginButton: [], panelHeader: [], underInputs: [] };
379
153
  }
@@ -467,20 +241,18 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
467
241
  const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation;
468
242
  const beforeLoginConfirmationArray = Array.isArray(beforeLoginConfirmation) ? beforeLoginConfirmation : [beforeLoginConfirmation];
469
243
  beforeLoginConfirmationArray.push((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, response, adminforth, extra, sessionDuration }) {
470
- var _b, _c, _d;
244
+ var _b;
471
245
  if (((_b = extra === null || extra === void 0 ? void 0 : extra.meta) === null || _b === void 0 ? void 0 : _b.loginAllowedByPasskeyDirectSignIn) === true) {
472
246
  return { body: { loginAllowed: true }, ok: true };
473
247
  }
474
- const secret = adminUser.dbUser[this.options.twoFaSecretFieldName];
248
+ const secret = this.userRepository.getSecret(adminUser.dbUser);
475
249
  const userName = adminUser.dbUser[adminforth.config.auth.usernameField];
476
250
  const brandName = adminforth.config.customization.brandName;
477
251
  const brandNameSlug = adminforth.config.customization.brandNameSlug;
478
252
  const issuerName = (this.options.customBrandPrefix && this.options.customBrandPrefix.trim())
479
253
  ? this.options.customBrandPrefix.trim()
480
254
  : brandName;
481
- const authResource = adminforth.config.resources.find((res) => res.resourceId === adminforth.config.auth.usersResourceId);
482
- const authPk = authResource.columns.find((col) => col.primaryKey).name;
483
- const userPk = adminUser.dbUser[authPk];
255
+ const userPk = this.userRepository.getUserPk(adminUser);
484
256
  let newSecret = null;
485
257
  const userNeeds2FA = this.options.usersFilterToApply ? this.options.usersFilterToApply(adminUser) : true;
486
258
  if (!userNeeds2FA) {
@@ -488,10 +260,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
488
260
  }
489
261
  const userCanSkipSetup = this.options.usersFilterToAllowSkipSetup ? this.options.usersFilterToAllowSkipSetup(adminUser) : false;
490
262
  if (!secret) {
491
- const tempSecret = twofactor.generateSecret({ name: issuerName, account: userName });
492
- newSecret = tempSecret.secret;
493
- const totpTemporaryJWT = this.adminforth.auth.issueJWT({ userName, newSecret, issuer: issuerName, pk: userPk, userCanSkipSetup, sessionDuration }, 'temp2FA', ((_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.challengeValidityPeriod) || '1m');
494
- this.adminforth.auth.setCustomCookie({ response, payload: { name: "2FaTemporaryJWT", value: totpTemporaryJWT, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
263
+ newSecret = this.totpService.generateSecret(issuerName, userName);
264
+ this.cookieService.setTotpTemporary(response, { userName, newSecret, issuer: issuerName, pk: userPk, userCanSkipSetup, sessionDuration });
495
265
  return {
496
266
  body: {
497
267
  loginAllowed: false,
@@ -501,8 +271,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
501
271
  };
502
272
  }
503
273
  else {
504
- const value = this.adminforth.auth.issueJWT({ userName, issuer: issuerName, pk: userPk, userCanSkipSetup, sessionDuration }, 'temp2FA', ((_d = this.options.passkeys) === null || _d === void 0 ? void 0 : _d.challengeValidityPeriod) || '1m');
505
- this.adminforth.auth.setCustomCookie({ response, payload: { name: "2FaTemporaryJWT", value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
274
+ this.cookieService.setTotpTemporary(response, { userName, issuer: issuerName, pk: userPk, userCanSkipSetup, sessionDuration });
506
275
  return {
507
276
  body: {
508
277
  loginAllowed: false,
@@ -514,583 +283,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
514
283
  }));
515
284
  }
516
285
  setupEndpoints(server) {
517
- server.endpoint({
518
- method: 'POST',
519
- path: `/plugin/twofa/initSetup`,
520
- noAuth: true,
521
- handler: (server) => __awaiter(this, void 0, void 0, function* () {
522
- const toReturn = { totpJWT: null, status: 'ok', };
523
- const totpTemporaryJWT = this.adminforth.auth.getCustomCookie({ cookies: server.cookies, name: "2FaTemporaryJWT" });
524
- if (totpTemporaryJWT) {
525
- toReturn.totpJWT = totpTemporaryJWT;
526
- }
527
- return toReturn;
528
- })
529
- });
530
- // this enpoints sets final login cookie if 2nd factor is confirmed
531
- server.endpoint({
532
- method: 'POST',
533
- path: `/plugin/twofa/confirmLogin`,
534
- noAuth: true,
535
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies }) {
536
- const brandNameSlug = this.adminforth.config.customization.brandNameSlug;
537
- const totpTemporaryJWT = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: "2FaTemporaryJWT" });
538
- if (!totpTemporaryJWT) {
539
- return { error: 'Login session expired. Please log in again.' };
540
- }
541
- const decoded = yield this.adminforth.auth.verify(totpTemporaryJWT, 'temp2FA');
542
- if (!decoded) {
543
- return { error: 'Login session expired. Please log in again.' };
544
- }
545
- if (decoded.newSecret) {
546
- // set up standard TOTP request - ensured by presence of newSecret in temp2FA token
547
- const verified = body.skip && decoded.userCanSkipSetup ? true : twofactor.verifyToken(decoded.newSecret, body.code, this.options.timeStepWindow);
548
- if (verified) {
549
- this.connectors = this.adminforth.connectors;
550
- if (!body.skip) {
551
- const connector = this.connectors[this.authResource.dataSource];
552
- yield connector.updateRecord({ resource: this.authResource, recordId: decoded.pk, newValues: { [this.options.twoFaSecretFieldName]: decoded.newSecret } });
553
- }
554
- this.adminforth.auth.removeCustomCookie({ response, name: '2FaTemporaryJWT' });
555
- this.adminforth.auth.setAuthCookie({ expireInDuration: decoded.sessionDuration, response, username: decoded.userName, pk: decoded.pk });
556
- return { status: 'ok', allowedLogin: true };
557
- }
558
- else {
559
- return { error: 'Wrong or expired OTP code' };
560
- }
561
- }
562
- else {
563
- // login with confirming existing TOTP or Passkey
564
- let verified = null;
565
- if (body.usePasskey && this.options.passkeys) {
566
- // passkeys are enabled and user wants to use them
567
- const cookiesValidationResult = yield this.validateCookiesForPasskeyLogin(cookies);
568
- if (!cookiesValidationResult.ok) {
569
- return { error: cookiesValidationResult.error };
570
- }
571
- const res = yield this.verifyPasskeyResponse(body.passkeyOptions, decoded.pk, cookiesValidationResult.decodedPasskeysCookies);
572
- if (res.ok && res.passkeyConfirmed) {
573
- verified = true;
574
- }
575
- }
576
- else {
577
- // user already has TOTP secret, get it
578
- this.connectors = this.adminforth.connectors;
579
- const connector = this.connectors[this.authResource.dataSource];
580
- const user = yield connector.getRecordByPrimaryKey(this.authResource, decoded.pk);
581
- verified = twofactor.verifyToken(user[this.options.twoFaSecretFieldName], body.code, this.options.timeStepWindow);
582
- }
583
- if (verified) {
584
- this.adminforth.auth.removeCustomCookie({ response, name: '2FaTemporaryJWT' });
585
- this.adminforth.auth.setAuthCookie({ expireInDuration: decoded.sessionDuration, response, username: decoded.userName, pk: decoded.pk });
586
- return { status: 'ok', allowedLogin: true };
587
- }
588
- else {
589
- response.setStatus(403, "Wrong or expired TOTP code");
590
- return { error: 'Wrong or expired TOTP code', };
591
- }
592
- }
593
- })
594
- });
595
- server.endpoint({
596
- method: 'POST',
597
- path: `/plugin/twofa/confirmLoginWithPasskey`,
598
- noAuth: true,
599
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies, headers, requestUrl, query }) {
600
- if (this.options.passkeys.allowLoginWithPasskeys !== true) {
601
- return { error: 'Login with passkeys is not allowed' };
602
- }
603
- const passkeyResponse = body.passkeyResponse;
604
- if (!passkeyResponse) {
605
- return { error: 'Passkey response is required' };
606
- }
607
- const cookiesValidationResult = yield this.validateCookiesForPasskeyLogin(cookies);
608
- if (!cookiesValidationResult.ok) {
609
- return { error: cookiesValidationResult.error };
610
- }
611
- let parsedPasskeyResponse;
612
- try {
613
- parsedPasskeyResponse = JSON.parse(passkeyResponse.response);
614
- }
615
- catch (e) {
616
- return { error: 'Malformed passkey response' };
617
- }
618
- const credential_id = parsedPasskeyResponse.id;
619
- if (!credential_id) {
620
- return { error: 'Credential ID is required' };
621
- }
622
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credential_id)]);
623
- if (!passkeyRecord) {
624
- return { error: 'No such passkey found, most likeley it was removed on this website but you still have it on your device' };
625
- }
626
- const userPk = passkeyRecord[this.options.passkeys.credentialUserIdFieldName];
627
- if (!userPk) {
628
- return { error: 'User ID not found in passkey record' };
629
- }
630
- const userResourceId = this.adminforth.config.auth.usersResourceId;
631
- const usersResource = this.adminforth.config.resources.find(r => r.resourceId === userResourceId);
632
- const usersPrimaryKeyColumn = usersResource.columns.find((col) => col.primaryKey);
633
- const usersPrimaryKeyFieldName = usersPrimaryKeyColumn.name;
634
- const user = yield this.adminforth.resource(userResourceId).get([Filters.EQ(usersPrimaryKeyFieldName, userPk)]);
635
- if (!user) {
636
- return { error: 'User not found' };
637
- }
638
- const verificationResult = yield this.verifyPasskeyResponse(passkeyResponse, userPk, cookiesValidationResult.decodedPasskeysCookies);
639
- if (!verificationResult.ok || !verificationResult.passkeyConfirmed) {
640
- return { error: 'Passkey verification failed' };
641
- }
642
- const username = user[this.adminforth.config.auth.usernameField];
643
- const adminUser = {
644
- dbUser: user,
645
- pk: user.id,
646
- username,
647
- };
648
- const toReturn = { allowedLogin: true, error: '' };
649
- const rememberMe = (body === null || body === void 0 ? void 0 : body.rememberMe) || false;
650
- yield this.adminforth.restApi.processLoginCallbacks(adminUser, toReturn, response, {
651
- headers,
652
- cookies,
653
- requestUrl,
654
- query,
655
- body: {},
656
- response,
657
- meta: {
658
- loginAllowedByPasskeyDirectSignIn: true
659
- },
660
- }, rememberMe ? this.adminforth.config.auth.rememberMeDuration || '30d' : '1d');
661
- const rememberDaysAfterPasskeyLogin = (this.options.passkeys.rememberDaysAfterPasskeyLogin ? this.options.passkeys.rememberDaysAfterPasskeyLogin.toString().concat('d') : null);
662
- if (toReturn.allowedLogin === true) {
663
- this.adminforth.auth.setAuthCookie({
664
- response,
665
- username,
666
- pk: user.id,
667
- expireInDuration: rememberDaysAfterPasskeyLogin ? rememberDaysAfterPasskeyLogin : this.adminforth.config.auth.rememberMeDuration,
668
- });
669
- }
670
- return toReturn;
671
- })
672
- }),
673
- server.endpoint({
674
- method: "GET",
675
- path: "/plugin/twofa/skip-allow",
676
- noAuth: true,
677
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) {
678
- const totpTemporaryJWT = this.adminforth.auth.getCustomCookie({
679
- cookies,
680
- name: `2FaTemporaryJWT`,
681
- });
682
- const decoded = yield this.adminforth.auth.verify(totpTemporaryJWT, "temp2FA");
683
- if (!decoded) {
684
- return { status: "error", message: "Invalid token" };
685
- }
686
- if (!decoded.newSecret) {
687
- return { status: "ok", skipAllowed: false };
688
- }
689
- else {
690
- return {
691
- status: "ok",
692
- skipAllowed: decoded.userCanSkipSetup,
693
- };
694
- }
695
- }),
696
- });
697
- server.endpoint({
698
- method: "GET",
699
- path: "/plugin/twofa/skip-allow-modal",
700
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, headers, cookies }) {
701
- if (this.options.usersFilterToApply) {
702
- const res = this.options.usersFilterToApply(adminUser);
703
- if (res === false) {
704
- return { skipAllowed: true };
705
- }
706
- }
707
- if (this.options.usersFilterToAllowSkipSetup) {
708
- const res = yield this.checkIfSkipSetupAllowSkipVerify(adminUser);
709
- if (res.skipAllowed === true) {
710
- return { skipAllowed: true };
711
- }
712
- }
713
- if (this.options.stepUpMfaGracePeriodSeconds) {
714
- const verificationResult = yield this.isTempSkip2FAGraceValid(headers, cookies, true);
715
- if (verificationResult === true) {
716
- return { skipAllowed: true };
717
- }
718
- }
719
- return { skipAllowed: false };
720
- }),
721
- });
722
- server.endpoint({
723
- method: 'POST',
724
- path: `/plugin/twofa/verify`,
725
- noAuth: false,
726
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser, body }) {
727
- if (!(body === null || body === void 0 ? void 0 : body.code))
728
- return { error: 'Code is required' };
729
- const authRes = this.adminforth.config.resources
730
- .find(r => r.resourceId === this.adminforth.config.auth.usersResourceId);
731
- const connector = this.adminforth.connectors[authRes.dataSource];
732
- const pkName = authRes.columns.find(c => c.primaryKey).name;
733
- const user = yield connector.getRecordByPrimaryKey(authRes, adminUser.dbUser[pkName]);
734
- const secret = user[this.options.twoFaSecretFieldName];
735
- if (!secret)
736
- return { error: '2FA is not set up for this user' };
737
- const verified = twofactor.verifyToken(secret, body.code, this.options.timeStepWindow);
738
- return verified ? { ok: true } : { error: 'Wrong or expired OTP code' };
739
- })
740
- });
741
- server.endpoint({
742
- method: 'POST',
743
- path: `/plugin/passkeys/registerPasskeyRequest`,
744
- noAuth: false,
745
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
746
- var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
747
- const mode = body === null || body === void 0 ? void 0 : body.mode;
748
- const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
749
- const verificationResult = yield this.verify(confirmationResult, {
750
- adminUser: adminUser,
751
- userPk: adminUser.pk,
752
- cookies: cookies,
753
- response: response,
754
- extra: {
755
- headers,
756
- }
757
- });
758
- if (!verificationResult || !('ok' in verificationResult)) {
759
- return { ok: false, error: 'error' in verificationResult ? verificationResult.error : 'Verification failed' };
760
- }
761
- const settingsOrigin = (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings.expectedOrigin;
762
- const rp = {
763
- name: ((_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.settings.rp.name) || this.adminforth.config.customization.brandName,
764
- id: ((_f = (_e = (_d = this.options.passkeys) === null || _d === void 0 ? void 0 : _d.settings) === null || _e === void 0 ? void 0 : _e.rp) === null || _f === void 0 ? void 0 : _f.id) || (new URL(settingsOrigin)).hostname,
765
- };
766
- const userResourceId = this.adminforth.config.auth.usersResourceId;
767
- const usersResource = this.adminforth.config.resources.find(r => r.resourceId === userResourceId);
768
- const usersPrimaryKeyColumn = usersResource.columns.find((col) => col.primaryKey);
769
- const usersPrimaryKeyFieldName = usersPrimaryKeyColumn.name;
770
- const userInfo = yield this.adminforth.resource(userResourceId).get([Filters.EQ(usersPrimaryKeyFieldName, adminUser.pk)]);
771
- const user = {
772
- id: adminUser.pk,
773
- name: userInfo[(_g = this.options.passkeys) === null || _g === void 0 ? void 0 : _g.settings.user.nameField],
774
- displayName: userInfo[(_h = this.options.passkeys) === null || _h === void 0 ? void 0 : _h.settings.user.displayNameField] ? userInfo[(_j = this.options.passkeys) === null || _j === void 0 ? void 0 : _j.settings.user.displayNameField] : userInfo[(_k = this.options.passkeys) === null || _k === void 0 ? void 0 : _k.settings.user.nameField],
775
- };
776
- const excludeCredentials = [];
777
- const temp = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).list([Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
778
- for (const rec of temp) {
779
- if (rec.credential_id && rec.credential_id.length > 0) {
780
- const meta = this.parseIfNeeded(rec[this.options.passkeys.credentialMetaFieldName]);
781
- excludeCredentials.push({
782
- id: rec.credential_id,
783
- type: "public-key",
784
- transports: JSON.parse(meta.transports || '[]')
785
- });
786
- }
787
- }
788
- const options = yield generateRegistrationOptions({
789
- rpName: rp.name,
790
- rpID: rp.id,
791
- userID: isoUint8Array.fromUTF8String(user.id),
792
- userName: user.name,
793
- userDisplayName: user.displayName,
794
- excludeCredentials,
795
- authenticatorSelection: {
796
- authenticatorAttachment: mode,
797
- requireResidentKey: ((_l = this.options.passkeys) === null || _l === void 0 ? void 0 : _l.settings.authenticatorSelection.requireResidentKey) || true,
798
- userVerification: ((_m = this.options.passkeys) === null || _m === void 0 ? void 0 : _m.settings.authenticatorSelection.userVerification) || "required"
799
- },
800
- });
801
- const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge, "user_id": adminUser.pk }, 'registerTempPasskeyChallenge', ((_o = this.options.passkeys) === null || _o === void 0 ? void 0 : _o.challengeValidityPeriod) || '1m');
802
- this.adminforth.auth.setCustomCookie({ response, payload: { name: "registerPasskeyTemporaryJWT", value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
803
- return { ok: true, data: options };
804
- })
805
- });
806
- server.endpoint({
807
- method: 'POST',
808
- path: `/plugin/passkeys/finishRegisteringPasskey`,
809
- noAuth: false,
810
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, cookies }) {
811
- var _b, _c, _d, _e, _f, _g;
812
- const passkeysCookies = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: "registerPasskeyTemporaryJWT" });
813
- if (!passkeysCookies) {
814
- return { error: 'Passkey token is required' };
815
- }
816
- const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'registerTempPasskeyChallenge', false);
817
- if (!decodedPasskeysCookies) {
818
- return { error: 'Invalid passkey token' };
819
- }
820
- if (decodedPasskeysCookies.user_id !== adminUser.pk) {
821
- return { error: 'Invalid user' };
822
- }
823
- const secret = adminUser.dbUser[this.options.twoFaSecretFieldName];
824
- if (!secret || secret.length === 0) {
825
- return { error: 'TOTP must be set up before registering a passkey' };
826
- }
827
- const settingsOrigin = (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings.expectedOrigin;
828
- const expectedOrigin = body.origin;
829
- const expectedChallenge = decodedPasskeysCookies.challenge;
830
- const expectedRPID = ((_e = (_d = (_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.settings) === null || _d === void 0 ? void 0 : _d.rp) === null || _e === void 0 ? void 0 : _e.id) || (new URL(settingsOrigin)).hostname;
831
- const response = JSON.parse(body.credential);
832
- try {
833
- if (settingsOrigin !== expectedOrigin) {
834
- throw new Error('Invalid origin');
835
- }
836
- const { verified, registrationInfo } = yield verifyRegistrationResponse({
837
- response,
838
- expectedChallenge,
839
- expectedOrigin,
840
- expectedRPID,
841
- requireUserVerification: ((_f = this.options.passkeys) === null || _f === void 0 ? void 0 : _f.settings.authenticatorSelection.userVerification) === 'discouraged' ? false : true,
842
- });
843
- if (!verified) {
844
- throw new Error('Verification failed.');
845
- }
846
- const { aaguid, credential, credentialBackedUp } = registrationInfo;
847
- const provider_name = ((_g = aaguids[aaguid]) === null || _g === void 0 ? void 0 : _g.name) || 'Unknown provider';
848
- const credentialPublicKey = credential.publicKey;
849
- const credentialID = credential.id;
850
- const base64CredentialID = credentialID;
851
- const base64PublicKey = isoBase64URL.fromBuffer(credentialPublicKey);
852
- const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
853
- if (!credResource) {
854
- throw new Error('Credential resource not found.');
855
- }
856
- yield this.adminforth.createResourceRecord({
857
- resource: credResource,
858
- record: {
859
- [this.options.passkeys.credentialIdFieldName]: base64CredentialID,
860
- [this.options.passkeys.credentialUserIdFieldName]: adminUser.pk,
861
- [this.options.passkeys.credentialMetaFieldName]: JSON.stringify({
862
- public_key: base64PublicKey,
863
- public_key_algorithm: response.response.publicKeyAlgorithm,
864
- sign_count: 0,
865
- transports: JSON.stringify(response.response.transports),
866
- created_at: new Date().toISOString(),
867
- last_used_at: new Date().toISOString(),
868
- aaguid: aaguid,
869
- name: provider_name,
870
- }),
871
- },
872
- adminUser: adminUser
873
- });
874
- }
875
- catch (error) {
876
- console.error(error);
877
- return { error: 'Error registering passkey: ' + error.message };
878
- }
879
- return { ok: true };
880
- })
881
- });
882
- server.endpoint({
883
- method: 'POST',
884
- path: `/plugin/passkeys/signInRequest`,
885
- noAuth: true,
886
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ response }) {
887
- var _b, _c, _d;
888
- try {
889
- const options = yield generateAuthenticationOptions({
890
- rpID: (_b = this.options.passkeys) === null || _b === void 0 ? void 0 : _b.settings.rp.id,
891
- userVerification: ((_c = this.options.passkeys) === null || _c === void 0 ? void 0 : _c.settings.authenticatorSelection.userVerification) || "required"
892
- });
893
- const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge }, 'tempLoginPasskeyChallenge', ((_d = this.options.passkeys) === null || _d === void 0 ? void 0 : _d.challengeValidityPeriod) || '1m');
894
- this.adminforth.auth.setCustomCookie({ response, payload: { name: `passkeyLoginTemporaryJWT`, value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
895
- return { ok: true, data: options };
896
- }
897
- catch (e) {
898
- return { ok: false, error: e };
899
- }
900
- })
901
- });
902
- server.endpoint({
903
- method: 'GET',
904
- path: `/plugin/passkeys/getPasskeys`,
905
- noAuth: false,
906
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
907
- var _b, _c, _d;
908
- let passkeys;
909
- try {
910
- passkeys = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).list([Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
911
- }
912
- catch (error) {
913
- return { ok: false, error: 'Error fetching passkeys: ' + error.message };
914
- }
915
- let dataToReturn = [];
916
- for (const pk of passkeys) {
917
- const parsedKey = this.parseIfNeeded(pk[this.options.passkeys.credentialMetaFieldName]);
918
- dataToReturn.push({
919
- name: parsedKey.name,
920
- light_icon: ((_b = aaguids[parsedKey.aaguid]) === null || _b === void 0 ? void 0 : _b.icon_light) || null,
921
- dark_icon: ((_c = aaguids[parsedKey.aaguid]) === null || _c === void 0 ? void 0 : _c.icon_dark) || null,
922
- created_at: parsedKey.created_at,
923
- last_used_at: parsedKey.last_used_at,
924
- id: pk[this.options.passkeys.credentialIdFieldName],
925
- });
926
- }
927
- return { ok: true, data: dataToReturn, authenticatorAttachment: ((_d = this.options.passkeys) === null || _d === void 0 ? void 0 : _d.settings.authenticatorSelection.authenticatorAttachment) || 'both' };
928
- })
929
- });
930
- server.endpoint({
931
- method: 'DELETE',
932
- path: `/plugin/passkeys/deletePasskey`,
933
- noAuth: false,
934
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
935
- const credentialID = body.passkeyId;
936
- if (!credentialID) {
937
- return { ok: false, error: 'Passkey ID is required' };
938
- }
939
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
940
- if (!passkeyRecord) {
941
- return { ok: false, error: 'Passkey not found' };
942
- }
943
- try {
944
- const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
945
- if (!credResource) {
946
- throw new Error('Credential resource not found.');
947
- }
948
- const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
949
- if (!credResourcePKColumn) {
950
- throw new Error('Credential resource primary key not found.');
951
- }
952
- const credResourcePKName = credResourcePKColumn.name;
953
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
954
- if (!passkeyRecord) {
955
- return { ok: false, error: 'Passkey not found' };
956
- }
957
- yield this.adminforth.deleteResourceRecord({
958
- resource: credResource,
959
- recordId: passkeyRecord[credResourcePKName],
960
- record: passkeyRecord,
961
- adminUser: adminUser,
962
- });
963
- }
964
- catch (error) {
965
- return { ok: false, error: 'Error deleting passkey: ' + error.message };
966
- }
967
- return { ok: true };
968
- })
969
- });
970
- server.endpoint({
971
- method: 'POST',
972
- path: `/plugin/passkeys/renamePasskey`,
973
- noAuth: false,
974
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
975
- const credentialID = body.passkeyId;
976
- const newName = body.newName;
977
- if (!credentialID) {
978
- return { ok: false, error: 'Passkey ID is required' };
979
- }
980
- if (!newName) {
981
- return { ok: false, error: 'New name is required' };
982
- }
983
- const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
984
- if (!credResource) {
985
- throw new Error('Credential resource not found.');
986
- }
987
- const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
988
- if (!credResourcePKColumn) {
989
- throw new Error('Credential resource primary key not found.');
990
- }
991
- const credResourcePKName = credResourcePKColumn.name;
992
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
993
- if (!passkeyRecord) {
994
- return { ok: false, error: 'Passkey not found' };
995
- }
996
- const meta = this.parseIfNeeded(passkeyRecord[this.options.passkeys.credentialMetaFieldName]);
997
- meta.name = newName;
998
- const newRecord = Object.assign(Object.assign({}, passkeyRecord), { [this.options.passkeys.credentialMetaFieldName]: JSON.stringify(meta) });
999
- try {
1000
- const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
1001
- if (!credResource) {
1002
- throw new Error('Credential resource not found.');
1003
- }
1004
- yield this.adminforth.updateResourceRecord({
1005
- resource: credResource,
1006
- recordId: passkeyRecord[credResourcePKName],
1007
- oldRecord: passkeyRecord,
1008
- record: newRecord,
1009
- adminUser: adminUser
1010
- });
1011
- }
1012
- catch (error) {
1013
- return { ok: false, error: 'Error renaming passkey: ' + error.message };
1014
- }
1015
- return { ok: true };
1016
- })
1017
- });
1018
- server.endpoint({
1019
- method: 'POST',
1020
- path: `/plugin/passkeys/checkIfUserHasPasskeys`,
1021
- noAuth: true,
1022
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ cookies }) {
1023
- if (!this.options.passkeys) {
1024
- return { ok: false, hasPasskeys: false };
1025
- }
1026
- const totpTemporaryJWT = this.adminforth.auth.getCustomCookie({ cookies: cookies, name: "2FaTemporaryJWT" });
1027
- const decoded = yield this.adminforth.auth.verify(totpTemporaryJWT, 'temp2FA');
1028
- if (!decoded)
1029
- return { ok: false, error: 'Invalid token' };
1030
- if (decoded.newSecret) {
1031
- return { ok: true, hasPasskeys: false };
1032
- }
1033
- const passkeys = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).list([Filters.EQ(this.options.passkeys.credentialUserIdFieldName, decoded.pk)]);
1034
- if (passkeys && passkeys.length > 0) {
1035
- return { ok: true, hasPasskeys: true };
1036
- }
1037
- else {
1038
- return { ok: true, hasPasskeys: false };
1039
- }
1040
- })
1041
- });
1042
- server.endpoint({
1043
- method: 'POST',
1044
- path: `/plugin/passkeys/resolveVerifyAuto`,
1045
- noAuth: false,
1046
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
1047
- const sessionsIds = body === null || body === void 0 ? void 0 : body.sessionsIds;
1048
- const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
1049
- const idsToResolve = Array.isArray(sessionsIds) ? sessionsIds : [];
1050
- const resolveAllIdsAsFailed = (message) => {
1051
- for (const id of idsToResolve) {
1052
- this.resolveResponse(id, { ok: false, error: message });
1053
- }
1054
- return { ok: false, error: message };
1055
- };
1056
- try {
1057
- if (!idsToResolve.length || !confirmationResult) {
1058
- return (resolveAllIdsAsFailed('Confirmation window was closed or did not return required data'));
1059
- }
1060
- for (const id of idsToResolve) {
1061
- const validationResult = yield this.adminforth.auth.verify(id, 'auto2FA', false);
1062
- if (!validationResult) {
1063
- return (resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
1064
- }
1065
- if (validationResult.adminUserPk !== adminUser.pk) {
1066
- return (resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
1067
- }
1068
- }
1069
- const verificationResult = yield this.verify(confirmationResult, {
1070
- adminUser: adminUser,
1071
- userPk: adminUser.pk,
1072
- cookies: cookies,
1073
- response: response,
1074
- extra: {
1075
- headers: headers,
1076
- }
1077
- });
1078
- if (!verificationResult || !('ok' in verificationResult)) {
1079
- return (resolveAllIdsAsFailed('Verification failed'));
1080
- }
1081
- if ('ok' in verificationResult && verificationResult.ok) {
1082
- for (const id of idsToResolve) {
1083
- this.resolveResponse(id, { ok: true, passkeyConfirmed: verificationResult });
1084
- }
1085
- return { ok: true };
1086
- }
1087
- return (resolveAllIdsAsFailed('Verification failed'));
1088
- }
1089
- catch (error) {
1090
- console.error('[AdminForth 2FA] Error resolving automatic 2FA verification', error);
1091
- return (resolveAllIdsAsFailed(error instanceof Error ? error.message : 'Verification failed'));
1092
- }
1093
- })
1094
- });
286
+ registerTwoFaEndpoints(server, createTwoFaHandlers(this.ctx));
287
+ registerPasskeyEndpoints(server, createPasskeyHandlers(this.ctx));
1095
288
  }
1096
289
  }