@adminforth/two-factors-auth 1.10.3 → 1.10.5

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 (3) hide show
  1. package/dist/index.js +44 -12
  2. package/index.ts +57 -13
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ 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 } from "adminforth";
10
+ import { AdminForthPlugin, Filters, suggestIfTypo } from "adminforth";
11
11
  import twofactor from 'node-2fa';
12
12
  import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server';
13
13
  import { isoUint8Array, isoBase64URL } from '@simplewebauthn/server/helpers';
@@ -161,9 +161,23 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
161
161
  if (!this.options.passkeys.credentialIdFieldName) {
162
162
  throw new Error('Passkeys credentialIdFieldName is required');
163
163
  }
164
+ const credentialResource = adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
165
+ const credentialIDField = credentialResource.columns.find(c => c.name === this.options.passkeys.credentialIdFieldName);
166
+ if (!credentialIDField) {
167
+ const similar = suggestIfTypo(credentialResource.columns.map(c => c.name), this.options.passkeys.credentialIdFieldName);
168
+ throw new Error(`Passkeys credentialIdFieldName '${this.options.passkeys.credentialIdFieldName}' not found in resource '${this.options.passkeys.credentialResourceID}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
169
+ }
170
+ credentialIDField.backendOnly = true;
164
171
  if (!this.options.passkeys.credentialMetaFieldName) {
165
172
  throw new Error('Passkeys credentialMetaFieldName is required');
166
173
  }
174
+ const metaResource = adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialMetaFieldName);
175
+ const metaField = credentialResource.columns.find(c => c.name === this.options.passkeys.credentialMetaFieldName);
176
+ if (!metaField) {
177
+ const similar = suggestIfTypo(metaResource.columns.map(c => c.name), this.options.passkeys.credentialMetaFieldName);
178
+ throw new Error(`Passkeys credentialMetaFieldName '${this.options.passkeys.credentialMetaFieldName}' not found in resource '${this.options.passkeys.credentialMetaFieldName}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
179
+ }
180
+ metaField.backendOnly = true;
167
181
  if (!this.options.passkeys.credentialUserIdFieldName) {
168
182
  throw new Error('Passkeys credentialUserIdFieldName is required');
169
183
  }
@@ -334,7 +348,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
334
348
  return { status: 'ok', allowedLogin: true };
335
349
  }
336
350
  else {
337
- return { error: 'Wrong or expired OTP code' };
351
+ return { error: 'Verification failed' };
338
352
  }
339
353
  }
340
354
  })
@@ -429,7 +443,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
429
443
  userVerification: ((_m = this.options.passkeys) === null || _m === void 0 ? void 0 : _m.settings.authenticatorSelection.userVerification) || "required"
430
444
  },
431
445
  });
432
- const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge, "user_id": adminUser.pk }, 'tempPasskeyChallenge', '10m');
446
+ const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge, "user_id": adminUser.pk }, 'registerTempPasskeyChallenge', '10m');
433
447
  this.adminforth.auth.setCustomCookie({ response, payload: { name: "registerPasskeyTemporaryJWT", value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true } });
434
448
  return { ok: true, data: options };
435
449
  })
@@ -444,7 +458,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
444
458
  if (!passkeysCookies) {
445
459
  return { error: 'Passkey token is required' };
446
460
  }
447
- const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'tempPasskeyChallenge', false);
461
+ const decodedPasskeysCookies = yield this.adminforth.auth.verify(passkeysCookies, 'registerTempPasskeyChallenge', false);
448
462
  if (!decodedPasskeysCookies) {
449
463
  return { error: 'Invalid passkey token' };
450
464
  }
@@ -556,11 +570,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
556
570
  path: `/plugin/passkeys/deletePasskey`,
557
571
  noAuth: false,
558
572
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
559
- const passkeyId = body.passkeyId;
560
- if (!passkeyId) {
573
+ const credentialID = body.passkeyId;
574
+ if (!credentialID) {
561
575
  return { ok: false, error: 'Passkey ID is required' };
562
576
  }
563
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, passkeyId), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
577
+ 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)]);
564
578
  if (!passkeyRecord) {
565
579
  return { ok: false, error: 'Passkey not found' };
566
580
  }
@@ -569,9 +583,18 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
569
583
  if (!credResource) {
570
584
  throw new Error('Credential resource not found.');
571
585
  }
586
+ const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
587
+ if (!credResourcePKColumn) {
588
+ throw new Error('Credential resource primary key not found.');
589
+ }
590
+ const credResourcePKName = credResourcePKColumn.name;
591
+ 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)]);
592
+ if (!passkeyRecord) {
593
+ return { ok: false, error: 'Passkey not found' };
594
+ }
572
595
  yield this.adminforth.deleteResourceRecord({
573
596
  resource: credResource,
574
- recordId: passkeyId,
597
+ recordId: passkeyRecord[credResourcePKName],
575
598
  record: passkeyRecord,
576
599
  adminUser: adminUser,
577
600
  });
@@ -587,15 +610,24 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
587
610
  path: `/plugin/passkeys/renamePasskey`,
588
611
  noAuth: false,
589
612
  handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
590
- const passkeyId = body.passkeyId;
613
+ const credentialID = body.passkeyId;
591
614
  const newName = body.newName;
592
- if (!passkeyId) {
615
+ if (!credentialID) {
593
616
  return { ok: false, error: 'Passkey ID is required' };
594
617
  }
595
618
  if (!newName) {
596
619
  return { ok: false, error: 'New name is required' };
597
620
  }
598
- const passkeyRecord = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, passkeyId), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
621
+ const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
622
+ if (!credResource) {
623
+ throw new Error('Credential resource not found.');
624
+ }
625
+ const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
626
+ if (!credResourcePKColumn) {
627
+ throw new Error('Credential resource primary key not found.');
628
+ }
629
+ const credResourcePKName = credResourcePKColumn.name;
630
+ 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)]);
599
631
  if (!passkeyRecord) {
600
632
  return { ok: false, error: 'Passkey not found' };
601
633
  }
@@ -609,7 +641,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
609
641
  }
610
642
  yield this.adminforth.updateResourceRecord({
611
643
  resource: credResource,
612
- recordId: passkeyId,
644
+ recordId: passkeyRecord[credResourcePKName],
613
645
  oldRecord: passkeyRecord,
614
646
  record: newRecord,
615
647
  adminUser: adminUser
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AdminForthPlugin, Filters } from "adminforth";
1
+ import { AdminForthPlugin, Filters, suggestIfTypo } from "adminforth";
2
2
  import type { AdminForthResource, AdminUser, IAdminForth, IHttpServer, IAdminForthAuth, BeforeLoginConfirmationFunction, IAdminForthHttpResponse } from "adminforth";
3
3
  import twofactor from 'node-2fa';
4
4
  import { PluginOptions } from "./types.js"
@@ -173,9 +173,36 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
173
173
  if (!this.options.passkeys.credentialIdFieldName) {
174
174
  throw new Error('Passkeys credentialIdFieldName is required');
175
175
  }
176
+
177
+ const credentialResource = adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
178
+ const credentialIDField = credentialResource.columns.find(c => c.name === this.options.passkeys.credentialIdFieldName);
179
+ if ( !credentialIDField ) {
180
+ const similar = suggestIfTypo(credentialResource.columns.map(c => c.name), this.options.passkeys.credentialIdFieldName);
181
+ throw new Error(
182
+ `Passkeys credentialIdFieldName '${this.options.passkeys.credentialIdFieldName}' not found in resource '${this.options.passkeys.credentialResourceID}'. ${
183
+ similar ? `Did you mean '${similar}'?` : ''
184
+ }`
185
+ );
186
+ }
187
+ credentialIDField.backendOnly = true;
188
+
176
189
  if (!this.options.passkeys.credentialMetaFieldName) {
177
190
  throw new Error('Passkeys credentialMetaFieldName is required');
178
191
  }
192
+
193
+ const metaResource = adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialMetaFieldName);
194
+ const metaField = credentialResource.columns.find(c => c.name === this.options.passkeys.credentialMetaFieldName);
195
+ if ( !metaField ) {
196
+ const similar = suggestIfTypo(metaResource.columns.map(c => c.name), this.options.passkeys.credentialMetaFieldName);
197
+ throw new Error(
198
+ `Passkeys credentialMetaFieldName '${this.options.passkeys.credentialMetaFieldName}' not found in resource '${this.options.passkeys.credentialMetaFieldName}'. ${
199
+ similar ? `Did you mean '${similar}'?` : ''
200
+ }`
201
+ );
202
+ }
203
+ metaField.backendOnly = true;
204
+
205
+
179
206
  if (!this.options.passkeys.credentialUserIdFieldName) {
180
207
  throw new Error('Passkeys credentialUserIdFieldName is required');
181
208
  }
@@ -358,7 +385,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
358
385
  this.adminforth.auth.setAuthCookie({expireInDays: decoded.rememberMeDays, response, username:decoded.userName, pk:decoded.pk})
359
386
  return { status: 'ok', allowedLogin: true }
360
387
  } else {
361
- return {error: 'Wrong or expired OTP code'}
388
+ return {error: 'Verification failed'}
362
389
  }
363
390
  }
364
391
  }
@@ -455,7 +482,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
455
482
  userVerification: this.options.passkeys?.settings.authenticatorSelection.userVerification || "required"
456
483
  },
457
484
  });
458
- const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge, "user_id": adminUser.pk }, 'tempPasskeyChallenge', '10m');
485
+ const value = this.adminforth.auth.issueJWT({ "challenge": options.challenge, "user_id": adminUser.pk }, 'registerTempPasskeyChallenge', '10m');
459
486
  this.adminforth.auth.setCustomCookie({response, payload: {name: "registerPasskeyTemporaryJWT", value: value, expiry: undefined, expirySeconds: 10 * 60, httpOnly: true}});
460
487
  return { ok: true, data: options };
461
488
  }
@@ -469,7 +496,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
469
496
  if (!passkeysCookies) {
470
497
  return { error: 'Passkey token is required' };
471
498
  }
472
- const decodedPasskeysCookies = await this.adminforth.auth.verify(passkeysCookies, 'tempPasskeyChallenge', false);
499
+ const decodedPasskeysCookies = await this.adminforth.auth.verify(passkeysCookies, 'registerTempPasskeyChallenge', false);
473
500
  if (!decodedPasskeysCookies) {
474
501
  return { error: 'Invalid passkey token' };
475
502
  }
@@ -584,12 +611,12 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
584
611
  path: `/plugin/passkeys/deletePasskey`,
585
612
  noAuth: false,
586
613
  handler: async ({body, adminUser }) => {
587
- const passkeyId = body.passkeyId;
588
- if (!passkeyId) {
614
+ const credentialID = body.passkeyId;
615
+ if (!credentialID) {
589
616
  return { ok: false, error: 'Passkey ID is required' };
590
617
  }
591
618
 
592
- const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, passkeyId), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
619
+ const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
593
620
  if (!passkeyRecord) {
594
621
  return { ok: false, error: 'Passkey not found' };
595
622
  }
@@ -598,9 +625,18 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
598
625
  if (!credResource) {
599
626
  throw new Error('Credential resource not found.');
600
627
  }
628
+ const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
629
+ if (!credResourcePKColumn) {
630
+ throw new Error('Credential resource primary key not found.');
631
+ }
632
+ const credResourcePKName = credResourcePKColumn.name;
633
+ const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
634
+ if (!passkeyRecord) {
635
+ return { ok: false, error: 'Passkey not found' };
636
+ }
601
637
  await this.adminforth.deleteResourceRecord({
602
638
  resource: credResource,
603
- recordId: passkeyId,
639
+ recordId: passkeyRecord[credResourcePKName],
604
640
  record: passkeyRecord,
605
641
  adminUser: adminUser,
606
642
  });
@@ -616,16 +652,24 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
616
652
  path: `/plugin/passkeys/renamePasskey`,
617
653
  noAuth: false,
618
654
  handler: async ({body, adminUser }) => {
619
- const passkeyId = body.passkeyId;
655
+ const credentialID = body.passkeyId;
620
656
  const newName = body.newName;
621
- if (!passkeyId) {
657
+ if (!credentialID) {
622
658
  return { ok: false, error: 'Passkey ID is required' };
623
659
  }
624
660
  if (!newName) {
625
661
  return { ok: false, error: 'New name is required' };
626
662
  }
627
-
628
- const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, passkeyId), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
663
+ const credResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.passkeys.credentialResourceID);
664
+ if (!credResource) {
665
+ throw new Error('Credential resource not found.');
666
+ }
667
+ const credResourcePKColumn = credResource.columns.find(c => c.primaryKey);
668
+ if (!credResourcePKColumn) {
669
+ throw new Error('Credential resource primary key not found.');
670
+ }
671
+ const credResourcePKName = credResourcePKColumn.name;
672
+ const passkeyRecord = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, credentialID), Filters.EQ(this.options.passkeys.credentialUserIdFieldName, adminUser.pk)]);
629
673
  if (!passkeyRecord) {
630
674
  return { ok: false, error: 'Passkey not found' };
631
675
  }
@@ -639,7 +683,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
639
683
  }
640
684
  await this.adminforth.updateResourceRecord({
641
685
  resource: credResource,
642
- recordId: passkeyId,
686
+ recordId: passkeyRecord[credResourcePKName],
643
687
  oldRecord: passkeyRecord,
644
688
  record: newRecord,
645
689
  adminUser: adminUser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/two-factors-auth",
3
- "version": "1.10.3",
3
+ "version": "1.10.5",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",