@adminforth/two-factors-auth 3.3.3 → 3.3.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.
- package/build.log +2 -2
- package/custom/TwoFAModal.vue +36 -21
- package/custom/TwoFactorsPasskeysSettings.vue +17 -1
- package/custom/utils.js +1 -1
- package/dist/custom/TwoFAModal.vue +36 -21
- package/dist/custom/TwoFactorsPasskeysSettings.vue +17 -1
- package/dist/custom/utils.js +1 -1
- package/dist/index.js +34 -26
- package/index.ts +36 -29
- package/package.json +2 -2
package/build.log
CHANGED
|
@@ -16,5 +16,5 @@ custom/pnpm-lock.yaml
|
|
|
16
16
|
custom/tsconfig.json
|
|
17
17
|
custom/utils.js
|
|
18
18
|
|
|
19
|
-
sent
|
|
20
|
-
total size is
|
|
19
|
+
sent 286,569 bytes received 229 bytes 573,596.00 bytes/sec
|
|
20
|
+
total size is 285,663 speedup is 1.00
|
package/custom/TwoFAModal.vue
CHANGED
|
@@ -164,7 +164,9 @@
|
|
|
164
164
|
isAwaiting2FAResult.value = true;
|
|
165
165
|
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
166
166
|
} catch (error) {
|
|
167
|
-
|
|
167
|
+
if (!isTwoFaCancel(error)) {
|
|
168
|
+
console.error('[AdminForth 2FA] Error during 2FA confirmation', error);
|
|
169
|
+
}
|
|
168
170
|
}
|
|
169
171
|
isAwaiting2FAResult.value = false;
|
|
170
172
|
try {
|
|
@@ -226,28 +228,41 @@
|
|
|
226
228
|
let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
|
|
227
229
|
let rejectFn: ((err?: any) => void) | null = null;
|
|
228
230
|
|
|
231
|
+
function isTwoFaCancel(error: unknown) {
|
|
232
|
+
return error === 'Cancel';
|
|
233
|
+
}
|
|
234
|
+
|
|
229
235
|
|
|
230
236
|
window.adminforthTwoFaModal = {
|
|
231
237
|
get2FaConfirmationResult: (title?: string, verifyingCallback?: (confirmationResult: string) => Promise<boolean>) =>
|
|
232
|
-
new Promise(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
new Promise((resolve, reject) => {
|
|
239
|
+
(async () => {
|
|
240
|
+
if (modelShow.value) {
|
|
241
|
+
throw new Error(t('Modal is already open'));
|
|
242
|
+
}
|
|
243
|
+
const skipAllowModal = await checkIfSkipAllowModal();
|
|
244
|
+
if (skipAllowModal) {
|
|
245
|
+
resolve({ code: "123456" }); // dummy code
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
await checkIfUserHasPasskeys();
|
|
249
|
+
if (title) {
|
|
250
|
+
customDialogTitle.value = title;
|
|
251
|
+
}
|
|
252
|
+
resolveFn = resolve;
|
|
253
|
+
rejectFn = reject;
|
|
254
|
+
verifyFn = verifyingCallback ?? null;
|
|
255
|
+
modelShow.value = true;
|
|
256
|
+
if (modalMode.value === 'totp') {
|
|
257
|
+
await addEventListenerForOTPInput();
|
|
258
|
+
}
|
|
259
|
+
})().catch((error) => {
|
|
260
|
+
if (!isTwoFaCancel(error)) {
|
|
261
|
+
console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
|
|
262
|
+
}
|
|
263
|
+
reject(error);
|
|
264
|
+
});
|
|
265
|
+
}),
|
|
251
266
|
};
|
|
252
267
|
|
|
253
268
|
const { t } = useI18n();
|
|
@@ -471,4 +486,4 @@
|
|
|
471
486
|
display: flex;
|
|
472
487
|
gap: 0.75rem;
|
|
473
488
|
}
|
|
474
|
-
</style>
|
|
489
|
+
</style>
|
|
@@ -241,7 +241,23 @@
|
|
|
241
241
|
return JSON.stringify((credential as PublicKeyCredential).toJSON());
|
|
242
242
|
} catch (error) {
|
|
243
243
|
console.error(t('Error creating WebAuthn credential:', error));
|
|
244
|
-
|
|
244
|
+
|
|
245
|
+
let message = t('Failed to create passkey.');
|
|
246
|
+
|
|
247
|
+
if (error?.name === 'InvalidStateError') {
|
|
248
|
+
message = t('A passkey for this account already exists on this device.');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (error?.name === 'NotAllowedError') {
|
|
252
|
+
message = t('Passkey creation was cancelled.');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (error?.name === 'NotSupportedError') {
|
|
256
|
+
message = t('Passkeys are not supported on this device or browser.');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
adminforth.alert({ message, variant: 'danger'});
|
|
260
|
+
|
|
245
261
|
return null;
|
|
246
262
|
}
|
|
247
263
|
}
|
package/custom/utils.js
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
codeError.value = t('A previous passkey attempt was still pending. Please try again.');
|
|
123
123
|
return null;
|
|
124
124
|
} else if (name === 'NotAllowedError') {
|
|
125
|
-
adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: '
|
|
125
|
+
adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: 'danger' });
|
|
126
126
|
codeError.value = 'The operation either timed out or was not allowed.';
|
|
127
127
|
return null;
|
|
128
128
|
} else {
|
|
@@ -164,7 +164,9 @@
|
|
|
164
164
|
isAwaiting2FAResult.value = true;
|
|
165
165
|
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
166
166
|
} catch (error) {
|
|
167
|
-
|
|
167
|
+
if (!isTwoFaCancel(error)) {
|
|
168
|
+
console.error('[AdminForth 2FA] Error during 2FA confirmation', error);
|
|
169
|
+
}
|
|
168
170
|
}
|
|
169
171
|
isAwaiting2FAResult.value = false;
|
|
170
172
|
try {
|
|
@@ -226,28 +228,41 @@
|
|
|
226
228
|
let verifyFn: null | ((confirmationResult: string) => Promise<boolean> | boolean) = null;
|
|
227
229
|
let rejectFn: ((err?: any) => void) | null = null;
|
|
228
230
|
|
|
231
|
+
function isTwoFaCancel(error: unknown) {
|
|
232
|
+
return error === 'Cancel';
|
|
233
|
+
}
|
|
234
|
+
|
|
229
235
|
|
|
230
236
|
window.adminforthTwoFaModal = {
|
|
231
237
|
get2FaConfirmationResult: (title?: string, verifyingCallback?: (confirmationResult: string) => Promise<boolean>) =>
|
|
232
|
-
new Promise(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
new Promise((resolve, reject) => {
|
|
239
|
+
(async () => {
|
|
240
|
+
if (modelShow.value) {
|
|
241
|
+
throw new Error(t('Modal is already open'));
|
|
242
|
+
}
|
|
243
|
+
const skipAllowModal = await checkIfSkipAllowModal();
|
|
244
|
+
if (skipAllowModal) {
|
|
245
|
+
resolve({ code: "123456" }); // dummy code
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
await checkIfUserHasPasskeys();
|
|
249
|
+
if (title) {
|
|
250
|
+
customDialogTitle.value = title;
|
|
251
|
+
}
|
|
252
|
+
resolveFn = resolve;
|
|
253
|
+
rejectFn = reject;
|
|
254
|
+
verifyFn = verifyingCallback ?? null;
|
|
255
|
+
modelShow.value = true;
|
|
256
|
+
if (modalMode.value === 'totp') {
|
|
257
|
+
await addEventListenerForOTPInput();
|
|
258
|
+
}
|
|
259
|
+
})().catch((error) => {
|
|
260
|
+
if (!isTwoFaCancel(error)) {
|
|
261
|
+
console.error('[AdminForth 2FA] Failed to open 2FA modal', error);
|
|
262
|
+
}
|
|
263
|
+
reject(error);
|
|
264
|
+
});
|
|
265
|
+
}),
|
|
251
266
|
};
|
|
252
267
|
|
|
253
268
|
const { t } = useI18n();
|
|
@@ -471,4 +486,4 @@
|
|
|
471
486
|
display: flex;
|
|
472
487
|
gap: 0.75rem;
|
|
473
488
|
}
|
|
474
|
-
</style>
|
|
489
|
+
</style>
|
|
@@ -241,7 +241,23 @@
|
|
|
241
241
|
return JSON.stringify((credential as PublicKeyCredential).toJSON());
|
|
242
242
|
} catch (error) {
|
|
243
243
|
console.error(t('Error creating WebAuthn credential:', error));
|
|
244
|
-
|
|
244
|
+
|
|
245
|
+
let message = t('Failed to create passkey.');
|
|
246
|
+
|
|
247
|
+
if (error?.name === 'InvalidStateError') {
|
|
248
|
+
message = t('A passkey for this account already exists on this device.');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (error?.name === 'NotAllowedError') {
|
|
252
|
+
message = t('Passkey creation was cancelled.');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (error?.name === 'NotSupportedError') {
|
|
256
|
+
message = t('Passkeys are not supported on this device or browser.');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
adminforth.alert({ message, variant: 'danger'});
|
|
260
|
+
|
|
245
261
|
return null;
|
|
246
262
|
}
|
|
247
263
|
}
|
package/dist/custom/utils.js
CHANGED
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
codeError.value = t('A previous passkey attempt was still pending. Please try again.');
|
|
123
123
|
return null;
|
|
124
124
|
} else if (name === 'NotAllowedError') {
|
|
125
|
-
adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: '
|
|
125
|
+
adminforth.alert({ message: `The operation either timed out or was not allowed`, variant: 'danger' });
|
|
126
126
|
codeError.value = 'The operation either timed out or was not allowed.';
|
|
127
127
|
return null;
|
|
128
128
|
} else {
|
package/dist/index.js
CHANGED
|
@@ -575,7 +575,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
575
575
|
return { status: 'ok', allowedLogin: true };
|
|
576
576
|
}
|
|
577
577
|
else {
|
|
578
|
-
|
|
578
|
+
response.setStatus(403);
|
|
579
|
+
return { error: 'Wrong or expired TOTP code', };
|
|
579
580
|
}
|
|
580
581
|
}
|
|
581
582
|
})
|
|
@@ -1034,42 +1035,49 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1034
1035
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
|
|
1035
1036
|
const sessionsIds = body === null || body === void 0 ? void 0 : body.sessionsIds;
|
|
1036
1037
|
const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
|
|
1037
|
-
const idsToResolve = sessionsIds;
|
|
1038
|
+
const idsToResolve = Array.isArray(sessionsIds) ? sessionsIds : [];
|
|
1038
1039
|
const resolveAllIdsAsFailed = (message) => {
|
|
1039
1040
|
for (const id of idsToResolve) {
|
|
1040
1041
|
this.resolveResponse(id, { ok: false, error: message });
|
|
1041
1042
|
}
|
|
1042
1043
|
return { ok: false, error: message };
|
|
1043
1044
|
};
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
for (const id of idsToResolve) {
|
|
1048
|
-
const validationResult = yield this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1049
|
-
if (!validationResult) {
|
|
1050
|
-
return (resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
|
|
1045
|
+
try {
|
|
1046
|
+
if (!idsToResolve.length || !confirmationResult) {
|
|
1047
|
+
return (resolveAllIdsAsFailed('Confirmation window was closed or did not return required data'));
|
|
1051
1048
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1049
|
+
for (const id of idsToResolve) {
|
|
1050
|
+
const validationResult = yield this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1051
|
+
if (!validationResult) {
|
|
1052
|
+
return (resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
|
|
1053
|
+
}
|
|
1054
|
+
if (validationResult.adminUserPk !== adminUser.pk) {
|
|
1055
|
+
return (resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
|
|
1056
|
+
}
|
|
1054
1057
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1058
|
+
const verificationResult = yield this.verify(confirmationResult, {
|
|
1059
|
+
adminUser: adminUser,
|
|
1060
|
+
userPk: adminUser.pk,
|
|
1061
|
+
cookies: cookies,
|
|
1062
|
+
response: response,
|
|
1063
|
+
extra: {
|
|
1064
|
+
headers: headers,
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
if (!verificationResult || !('ok' in verificationResult)) {
|
|
1068
|
+
return (resolveAllIdsAsFailed('Verification failed'));
|
|
1069
|
+
}
|
|
1070
|
+
if ('ok' in verificationResult && verificationResult.ok) {
|
|
1071
|
+
for (const id of idsToResolve) {
|
|
1072
|
+
this.resolveResponse(id, { ok: true, passkeyConfirmed: verificationResult });
|
|
1073
|
+
}
|
|
1074
|
+
return { ok: true };
|
|
1063
1075
|
}
|
|
1064
|
-
});
|
|
1065
|
-
if (!verificationResult || !('ok' in verificationResult)) {
|
|
1066
1076
|
return (resolveAllIdsAsFailed('Verification failed'));
|
|
1067
1077
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1072
|
-
return { ok: true };
|
|
1078
|
+
catch (error) {
|
|
1079
|
+
console.error('[AdminForth 2FA] Error resolving automatic 2FA verification', error);
|
|
1080
|
+
return (resolveAllIdsAsFailed(error instanceof Error ? error.message : 'Verification failed'));
|
|
1073
1081
|
}
|
|
1074
1082
|
})
|
|
1075
1083
|
});
|
package/index.ts
CHANGED
|
@@ -617,7 +617,8 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
617
617
|
this.adminforth.auth.setAuthCookie({expireInDuration: decoded.sessionDuration, response, username:decoded.userName, pk:decoded.pk})
|
|
618
618
|
return { status: 'ok', allowedLogin: true }
|
|
619
619
|
} else {
|
|
620
|
-
|
|
620
|
+
response.setStatus(403);
|
|
621
|
+
return {error: 'Wrong or expired TOTP code', }
|
|
621
622
|
}
|
|
622
623
|
}
|
|
623
624
|
}
|
|
@@ -1099,7 +1100,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1099
1100
|
handler: async ({ body, adminUser, response, cookies, headers }) => {
|
|
1100
1101
|
const sessionsIds = body?.sessionsIds;
|
|
1101
1102
|
const confirmationResult = body?.confirmationResult;
|
|
1102
|
-
const idsToResolve = sessionsIds;
|
|
1103
|
+
const idsToResolve = Array.isArray(sessionsIds) ? sessionsIds : [];
|
|
1103
1104
|
|
|
1104
1105
|
const resolveAllIdsAsFailed = (message) => {
|
|
1105
1106
|
for (const id of idsToResolve) {
|
|
@@ -1108,37 +1109,43 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1108
1109
|
return { ok: false, error: message };
|
|
1109
1110
|
}
|
|
1110
1111
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
for (const id of idsToResolve) {
|
|
1116
|
-
const validationResult = await this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1117
|
-
if (!validationResult) {
|
|
1118
|
-
return(resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
|
|
1119
|
-
}
|
|
1120
|
-
if (validationResult.adminUserPk !== adminUser.pk) {
|
|
1121
|
-
return(resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
|
|
1112
|
+
try {
|
|
1113
|
+
if (!idsToResolve.length || !confirmationResult) {
|
|
1114
|
+
return(resolveAllIdsAsFailed('Confirmation window was closed or did not return required data'));
|
|
1122
1115
|
}
|
|
1123
|
-
}
|
|
1124
1116
|
|
|
1125
|
-
const verificationResult = await this.verify(confirmationResult, {
|
|
1126
|
-
adminUser: adminUser,
|
|
1127
|
-
userPk: adminUser.pk,
|
|
1128
|
-
cookies: cookies,
|
|
1129
|
-
response: response,
|
|
1130
|
-
extra: {
|
|
1131
|
-
headers: headers,
|
|
1132
|
-
} as HttpExtra
|
|
1133
|
-
});
|
|
1134
|
-
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
1135
|
-
return(resolveAllIdsAsFailed('Verification failed'));
|
|
1136
|
-
}
|
|
1137
|
-
if ('ok' in verificationResult && verificationResult.ok){
|
|
1138
1117
|
for (const id of idsToResolve) {
|
|
1139
|
-
this.
|
|
1118
|
+
const validationResult = await this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1119
|
+
if (!validationResult) {
|
|
1120
|
+
return(resolveAllIdsAsFailed('Invalid session ID or confirmation result'));
|
|
1121
|
+
}
|
|
1122
|
+
if (validationResult.adminUserPk !== adminUser.pk) {
|
|
1123
|
+
return(resolveAllIdsAsFailed('Session does not belong to the authenticated user'));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const verificationResult = await this.verify(confirmationResult, {
|
|
1128
|
+
adminUser: adminUser,
|
|
1129
|
+
userPk: adminUser.pk,
|
|
1130
|
+
cookies: cookies,
|
|
1131
|
+
response: response,
|
|
1132
|
+
extra: {
|
|
1133
|
+
headers: headers,
|
|
1134
|
+
} as HttpExtra
|
|
1135
|
+
});
|
|
1136
|
+
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
1137
|
+
return(resolveAllIdsAsFailed('Verification failed'));
|
|
1138
|
+
}
|
|
1139
|
+
if ('ok' in verificationResult && verificationResult.ok){
|
|
1140
|
+
for (const id of idsToResolve) {
|
|
1141
|
+
this.resolveResponse(id, { ok: true, passkeyConfirmed: verificationResult });
|
|
1142
|
+
}
|
|
1143
|
+
return { ok: true };
|
|
1140
1144
|
}
|
|
1141
|
-
return
|
|
1145
|
+
return(resolveAllIdsAsFailed('Verification failed'));
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error('[AdminForth 2FA] Error resolving automatic 2FA verification', error);
|
|
1148
|
+
return(resolveAllIdsAsFailed(error instanceof Error ? error.message : 'Verification failed'));
|
|
1142
1149
|
}
|
|
1143
1150
|
}
|
|
1144
1151
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/two-factors-auth",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.5",
|
|
4
4
|
"description": "AdminForth plugin for two factors authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.10.7",
|
|
35
|
-
"adminforth": "^2.
|
|
35
|
+
"adminforth": "^2.66.0",
|
|
36
36
|
"semantic-release": "^24.2.1",
|
|
37
37
|
"semantic-release-slack-bot": "^4.0.2",
|
|
38
38
|
"typescript": "^5.7.3"
|