@adminforth/two-factors-auth 1.10.4 → 1.10.6

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/build.log CHANGED
@@ -12,5 +12,5 @@ custom/package-lock.json
12
12
  custom/package.json
13
13
  custom/tsconfig.json
14
14
 
15
- sent 57,692 bytes received 153 bytes 115,690.00 bytes/sec
16
- total size is 57,119 speedup is 0.99
15
+ sent 58,387 bytes received 153 bytes 117,080.00 bytes/sec
16
+ total size is 57,814 speedup is 0.99
@@ -74,7 +74,7 @@
74
74
 
75
75
  <script setup lang="ts">
76
76
 
77
- import { onMounted, nextTick, onBeforeUnmount, ref, watch } from 'vue';
77
+ import { onMounted, nextTick, onBeforeUnmount, ref, watch, onBeforeMount } from 'vue';
78
78
  import { useCoreStore } from '@/stores/core';
79
79
  import { useUserStore } from '@/stores/user';
80
80
  import { callAdminForthApi, loadFile } from '@/utils';
@@ -95,6 +95,17 @@
95
95
  const router = useRouter();
96
96
  const codeError = ref(null);
97
97
 
98
+ onBeforeMount(() => {
99
+ if (localStorage.getItem('isAuthorized') === 'true') {
100
+ coreStore.fetchMenuAndResource();
101
+ if (route.query.next) {
102
+ router.push(route.query.next.toString());
103
+ } else {
104
+ router.push({ name: 'home' });
105
+ }
106
+ }
107
+ })
108
+
98
109
  const handleOnComplete = (value) => {
99
110
  sendCode(value, 'TOTP', null);
100
111
  };
@@ -121,16 +132,18 @@
121
132
  const isPasskeysSupported = ref(false);
122
133
 
123
134
  onMounted(async () => {
124
- await nextTick();
125
- await isCMAAvailable();
126
- tagOtpInputs();
127
- if (isPasskeysSupported.value === true) {
128
- checkIfUserHasPasskeys();
135
+ if (localStorage.getItem('isAuthorized') !== 'true') {
136
+ await nextTick();
137
+ await isCMAAvailable();
138
+ tagOtpInputs();
139
+ if (isPasskeysSupported.value === true) {
140
+ checkIfUserHasPasskeys();
141
+ }
142
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
143
+ focusFirstAvailableOtpInput();
144
+ const rootEl = otpRoot.value;
145
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
129
146
  }
130
- document.addEventListener('focusin', handleGlobalFocusIn, true);
131
- focusFirstAvailableOtpInput();
132
- const rootEl = otpRoot.value;
133
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
134
147
  });
135
148
 
136
149
  watch(route, (newRoute) => {
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="text-3xl text-gray-900 font-semibold mt-2 max-w-2xl mr-6 flex-col justify-center items-center">
3
- <p>Passkeys</p>
2
+ <div class="text-3xl text-gray-900 font-semibold max-w-2xl mr-6 flex-col justify-center items-center">
3
+ <p class="flex items-start justify-start leading-none">Passkeys</p>
4
4
  <div class="flex flex-col items-end">
5
5
  <Table
6
6
  class="mt-4 w-full"
@@ -67,7 +67,7 @@
67
67
 
68
68
  <script setup lang="ts">
69
69
 
70
- import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch } from 'vue';
70
+ import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch, onBeforeMount } from 'vue';
71
71
  import { useCoreStore } from '@/stores/core';
72
72
  import { useUserStore } from '@/stores/user';
73
73
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
@@ -86,6 +86,16 @@ import ErrorMessage from '@/components/ErrorMessage.vue';
86
86
  const { t } = useI18n();
87
87
  const route = useRoute()
88
88
 
89
+ onBeforeMount(() => {
90
+ if (localStorage.getItem('isAuthorized') === 'true') {
91
+ coreStore.fetchMenuAndResource();
92
+ if (route.query.next) {
93
+ router.push(route.query.next.toString());
94
+ } else {
95
+ router.push({ name: 'home' });
96
+ }
97
+ }
98
+ })
89
99
 
90
100
  const code = ref(null);
91
101
  const codeError = ref(null);
@@ -74,7 +74,7 @@
74
74
 
75
75
  <script setup lang="ts">
76
76
 
77
- import { onMounted, nextTick, onBeforeUnmount, ref, watch } from 'vue';
77
+ import { onMounted, nextTick, onBeforeUnmount, ref, watch, onBeforeMount } from 'vue';
78
78
  import { useCoreStore } from '@/stores/core';
79
79
  import { useUserStore } from '@/stores/user';
80
80
  import { callAdminForthApi, loadFile } from '@/utils';
@@ -95,6 +95,17 @@
95
95
  const router = useRouter();
96
96
  const codeError = ref(null);
97
97
 
98
+ onBeforeMount(() => {
99
+ if (localStorage.getItem('isAuthorized') === 'true') {
100
+ coreStore.fetchMenuAndResource();
101
+ if (route.query.next) {
102
+ router.push(route.query.next.toString());
103
+ } else {
104
+ router.push({ name: 'home' });
105
+ }
106
+ }
107
+ })
108
+
98
109
  const handleOnComplete = (value) => {
99
110
  sendCode(value, 'TOTP', null);
100
111
  };
@@ -121,16 +132,18 @@
121
132
  const isPasskeysSupported = ref(false);
122
133
 
123
134
  onMounted(async () => {
124
- await nextTick();
125
- await isCMAAvailable();
126
- tagOtpInputs();
127
- if (isPasskeysSupported.value === true) {
128
- checkIfUserHasPasskeys();
135
+ if (localStorage.getItem('isAuthorized') !== 'true') {
136
+ await nextTick();
137
+ await isCMAAvailable();
138
+ tagOtpInputs();
139
+ if (isPasskeysSupported.value === true) {
140
+ checkIfUserHasPasskeys();
141
+ }
142
+ document.addEventListener('focusin', handleGlobalFocusIn, true);
143
+ focusFirstAvailableOtpInput();
144
+ const rootEl = otpRoot.value;
145
+ rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
129
146
  }
130
- document.addEventListener('focusin', handleGlobalFocusIn, true);
131
- focusFirstAvailableOtpInput();
132
- const rootEl = otpRoot.value;
133
- rootEl && rootEl.addEventListener('focusout', handleFocusOut, true);
134
147
  });
135
148
 
136
149
  watch(route, (newRoute) => {
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="text-3xl text-gray-900 font-semibold mt-2 max-w-2xl mr-6 flex-col justify-center items-center">
3
- <p>Passkeys</p>
2
+ <div class="text-3xl text-gray-900 font-semibold max-w-2xl mr-6 flex-col justify-center items-center">
3
+ <p class="flex items-start justify-start leading-none">Passkeys</p>
4
4
  <div class="flex flex-col items-end">
5
5
  <Table
6
6
  class="mt-4 w-full"
@@ -67,7 +67,7 @@
67
67
 
68
68
  <script setup lang="ts">
69
69
 
70
- import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch } from 'vue';
70
+ import { onMounted, onBeforeUnmount, nextTick, ref, watchEffect,computed,watch, onBeforeMount } from 'vue';
71
71
  import { useCoreStore } from '@/stores/core';
72
72
  import { useUserStore } from '@/stores/user';
73
73
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
@@ -86,6 +86,16 @@ import ErrorMessage from '@/components/ErrorMessage.vue';
86
86
  const { t } = useI18n();
87
87
  const route = useRoute()
88
88
 
89
+ onBeforeMount(() => {
90
+ if (localStorage.getItem('isAuthorized') === 'true') {
91
+ coreStore.fetchMenuAndResource();
92
+ if (route.query.next) {
93
+ router.push(route.query.next.toString());
94
+ } else {
95
+ router.push({ name: 'home' });
96
+ }
97
+ }
98
+ })
89
99
 
90
100
  const code = ref(null);
91
101
  const codeError = ref(null);
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';
@@ -71,7 +71,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
71
71
  const response = JSON.parse(body.response);
72
72
  try {
73
73
  if (settingsOrigin !== expectedOrigin) {
74
- throw new Error('Invalid origin');
74
+ throw new Error(`Origin mismatch. Allowed in settings: ${settingsOrigin}, received from client: ${expectedOrigin}`);
75
75
  }
76
76
  const cred = yield this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, response.id)]);
77
77
  if (!cred) {
@@ -92,7 +92,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
92
92
  const { verified, authenticationInfo } = yield verifyAuthenticationResponse({
93
93
  response,
94
94
  expectedChallenge,
95
- expectedOrigin,
95
+ expectedOrigin: settingsOrigin,
96
96
  expectedRPID,
97
97
  credential: {
98
98
  id: cred[this.options.passkeys.credentialIdFieldName],
@@ -125,11 +125,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
125
125
  const customPages = this.adminforth.config.customization.customPages;
126
126
  customPages.push({
127
127
  path: '/confirm2fa',
128
- component: { file: this.componentPath('TwoFactorsConfirmation.vue'), meta: { customLayout: true, suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
128
+ component: { file: this.componentPath('TwoFactorsConfirmation.vue'), meta: { sidebarAndHeader: "none", suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
129
129
  });
130
130
  customPages.push({
131
131
  path: '/setup2fa',
132
- component: { file: this.componentPath('TwoFactorsSetup.vue'), meta: { title: 'Setup 2FA', customLayout: true, suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
132
+ component: { file: this.componentPath('TwoFactorsSetup.vue'), meta: { title: 'Setup 2FA', sidebarAndHeader: "none", suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
133
133
  });
134
134
  const everyPageBottomInjections = this.adminforth.config.customization.globalInjections.everyPageBottom || [];
135
135
  everyPageBottomInjections.push({ file: this.componentPath('TwoFAModal.vue'), meta: {} });
@@ -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
  }
@@ -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"
@@ -80,7 +80,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
80
80
  const response = JSON.parse(body.response);
81
81
  try {
82
82
  if (settingsOrigin !== expectedOrigin) {
83
- throw new Error('Invalid origin');
83
+ throw new Error(`Origin mismatch. Allowed in settings: ${settingsOrigin}, received from client: ${expectedOrigin}`);
84
84
  }
85
85
  const cred = await this.adminforth.resource(this.options.passkeys.credentialResourceID).get([Filters.EQ(this.options.passkeys.credentialIdFieldName, response.id)]);
86
86
  if (!cred) {
@@ -101,7 +101,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
101
101
  const { verified, authenticationInfo } = await verifyAuthenticationResponse({
102
102
  response,
103
103
  expectedChallenge,
104
- expectedOrigin,
104
+ expectedOrigin: settingsOrigin,
105
105
  expectedRPID,
106
106
  credential: {
107
107
  id: cred[this.options.passkeys.credentialIdFieldName],
@@ -134,11 +134,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
134
134
  const customPages = this.adminforth.config.customization.customPages
135
135
  customPages.push({
136
136
  path:'/confirm2fa',
137
- component: { file: this.componentPath('TwoFactorsConfirmation.vue'), meta: { customLayout: true, suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
137
+ component: { file: this.componentPath('TwoFactorsConfirmation.vue'), meta: { sidebarAndHeader: "none", suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
138
138
  })
139
139
  customPages.push({
140
140
  path:'/setup2fa',
141
- component: { file: this.componentPath('TwoFactorsSetup.vue'), meta: { title: 'Setup 2FA', customLayout: true, suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
141
+ component: { file: this.componentPath('TwoFactorsSetup.vue'), meta: { title: 'Setup 2FA', sidebarAndHeader: "none", suggestionPeriod: suggestionPeriod, isPasskeysEnabled: isPasskeysEnabled } }
142
142
  })
143
143
  const everyPageBottomInjections = this.adminforth.config.customization.globalInjections.everyPageBottom || []
144
144
  everyPageBottomInjections.push({ file: this.componentPath('TwoFAModal.vue'), meta: {} })
@@ -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
  }
@@ -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.4",
3
+ "version": "1.10.6",
4
4
  "description": "AdminForth plugin for two factors authentication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",