@adminforth/two-factors-auth 3.1.2 → 3.1.4
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 +33 -6
- package/dist/custom/TwoFAModal.vue +33 -6
- package/dist/index.js +27 -10
- package/index.ts +32 -10
- package/package.json +1 -1
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 283,900 bytes received 229 bytes 568,258.00 bytes/sec
|
|
20
|
+
total size is 282,994 speedup is 1.00
|
package/custom/TwoFAModal.vue
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<div v-if="modalMode === 'totp'" class="af-two-factor-modal-totp flex flex-col items-center relative bg-white dark:bg-gray-700 rounded-lg shadow p-6 w-full max-w-md">
|
|
5
5
|
<div id="mfaCode-label" class="mb-4 text-gray-700 dark:text-gray-100 text-center">
|
|
6
6
|
<p> {{ customDialogTitle }} </p>
|
|
7
|
+
<p class="text-red-500 font-semibold text-xl" v-if="sessionsIdsToResolve.length > 1"> You are confirming {{ sessionsIdsToResolve.length }} actions</p>
|
|
7
8
|
<p>{{ $t('Please enter your authenticator code') }}</p>
|
|
8
9
|
</div>
|
|
9
10
|
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
<p class="text-4xl font-semibold mb-4 text:gray-900 dark:text-gray-200 ">{{$t('Passkey')}}</p>
|
|
52
53
|
<div class="mb-2 max-w-[300px] text:gray-900 dark:text-gray-200">
|
|
53
54
|
<p class="mb-2">{{customDialogTitle}} </p>
|
|
55
|
+
<p class="text-red-500 font-semibold text-xl text-center mb-12" v-if="sessionsIdsToResolve.length > 1"> You are confirming {{ sessionsIdsToResolve.length }} actions</p>
|
|
54
56
|
<p>{{$t('Authenticate yourself using the button below')}}</p>
|
|
55
57
|
</div>
|
|
56
58
|
<Button @click="usePasskeyButtonClick" :disabled="isFetchingPasskey" :loader="isFetchingPasskey" class="w-full mx-16">
|
|
@@ -100,23 +102,47 @@
|
|
|
100
102
|
|
|
101
103
|
const { alert } = useAdminforth();
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
const isAwaiting2FAResult = ref(false);
|
|
106
|
+
let allowAddNewSessions = true;
|
|
107
|
+
const ALLOW_NEW_SESSIONS_PERIOD = 1000;
|
|
108
|
+
const sessionsIdsToResolve = ref<string[]>([]);
|
|
109
|
+
|
|
110
|
+
watch(isAwaiting2FAResult, (awaiting) => {
|
|
111
|
+
if (awaiting) {
|
|
112
|
+
allowAddNewSessions = true;
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
if (isAwaiting2FAResult.value) {
|
|
115
|
+
allowAddNewSessions = false;
|
|
116
|
+
}
|
|
117
|
+
}, ALLOW_NEW_SESSIONS_PERIOD);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
104
121
|
watch( props, () => {
|
|
105
122
|
if (props.adminUser) {
|
|
106
123
|
websocket.unsubscribeByPrefix(`/user2fa/`);
|
|
107
124
|
websocket.subscribe(`/user2fa/${props.adminUser.pk}`, async (data: {sessionId: string}) => {
|
|
108
|
-
|
|
125
|
+
if (!allowAddNewSessions) {
|
|
126
|
+
alert({message: 'Some process or user tries to add new actions to confirm. Action was blocked', variant: 'warning'});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
sessionsIdsToResolve.value.push(data.sessionId);
|
|
109
130
|
let confirmationResult;
|
|
131
|
+
if (isAwaiting2FAResult.value) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
110
134
|
try {
|
|
135
|
+
isAwaiting2FAResult.value = true;
|
|
111
136
|
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
112
137
|
} catch (error) {
|
|
113
138
|
console.error('Error during 2FA confirmation:', error);
|
|
114
139
|
}
|
|
140
|
+
isAwaiting2FAResult.value = false;
|
|
115
141
|
try {
|
|
116
142
|
const response = await callAdminForthApi({
|
|
117
143
|
method: "POST",
|
|
118
144
|
path: "/plugin/passkeys/resolveVerifyAuto",
|
|
119
|
-
body: { confirmationResult,
|
|
145
|
+
body: { confirmationResult, sessionsIds: sessionsIdsToResolve.value }
|
|
120
146
|
});
|
|
121
147
|
if (!response.ok && response.error === 'No session ID or confirmation result'){
|
|
122
148
|
alert({message: 'Verification session finished or cancelled.', variant: 'warning'});
|
|
@@ -125,15 +151,16 @@
|
|
|
125
151
|
} else if (response.ok) {
|
|
126
152
|
alert({message: 'Verification successful', variant: 'success'});
|
|
127
153
|
}
|
|
154
|
+
sessionsIdsToResolve.value = [];
|
|
128
155
|
} catch (error) {
|
|
129
156
|
console.error('Error resolving automatic 2FA verification:', error);
|
|
130
157
|
}
|
|
131
|
-
|
|
158
|
+
allowAddNewSessions = true;
|
|
132
159
|
});
|
|
133
160
|
websocket.subscribe(`/user2fa/${props.adminUser.pk}-resolve`, async (data: {sessionId: string}) => {
|
|
134
|
-
if (
|
|
161
|
+
if (sessionsIdsToResolve.value.includes(data.sessionId) && rejectFn && modelShow.value) {
|
|
135
162
|
onCancel();
|
|
136
|
-
|
|
163
|
+
sessionsIdsToResolve.value = sessionsIdsToResolve.value.filter(id => id !== data.sessionId);
|
|
137
164
|
}
|
|
138
165
|
});
|
|
139
166
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<div v-if="modalMode === 'totp'" class="af-two-factor-modal-totp flex flex-col items-center relative bg-white dark:bg-gray-700 rounded-lg shadow p-6 w-full max-w-md">
|
|
5
5
|
<div id="mfaCode-label" class="mb-4 text-gray-700 dark:text-gray-100 text-center">
|
|
6
6
|
<p> {{ customDialogTitle }} </p>
|
|
7
|
+
<p class="text-red-500 font-semibold text-xl" v-if="sessionsIdsToResolve.length > 1"> You are confirming {{ sessionsIdsToResolve.length }} actions</p>
|
|
7
8
|
<p>{{ $t('Please enter your authenticator code') }}</p>
|
|
8
9
|
</div>
|
|
9
10
|
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
<p class="text-4xl font-semibold mb-4 text:gray-900 dark:text-gray-200 ">{{$t('Passkey')}}</p>
|
|
52
53
|
<div class="mb-2 max-w-[300px] text:gray-900 dark:text-gray-200">
|
|
53
54
|
<p class="mb-2">{{customDialogTitle}} </p>
|
|
55
|
+
<p class="text-red-500 font-semibold text-xl text-center mb-12" v-if="sessionsIdsToResolve.length > 1"> You are confirming {{ sessionsIdsToResolve.length }} actions</p>
|
|
54
56
|
<p>{{$t('Authenticate yourself using the button below')}}</p>
|
|
55
57
|
</div>
|
|
56
58
|
<Button @click="usePasskeyButtonClick" :disabled="isFetchingPasskey" :loader="isFetchingPasskey" class="w-full mx-16">
|
|
@@ -100,23 +102,47 @@
|
|
|
100
102
|
|
|
101
103
|
const { alert } = useAdminforth();
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
const isAwaiting2FAResult = ref(false);
|
|
106
|
+
let allowAddNewSessions = true;
|
|
107
|
+
const ALLOW_NEW_SESSIONS_PERIOD = 1000;
|
|
108
|
+
const sessionsIdsToResolve = ref<string[]>([]);
|
|
109
|
+
|
|
110
|
+
watch(isAwaiting2FAResult, (awaiting) => {
|
|
111
|
+
if (awaiting) {
|
|
112
|
+
allowAddNewSessions = true;
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
if (isAwaiting2FAResult.value) {
|
|
115
|
+
allowAddNewSessions = false;
|
|
116
|
+
}
|
|
117
|
+
}, ALLOW_NEW_SESSIONS_PERIOD);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
104
121
|
watch( props, () => {
|
|
105
122
|
if (props.adminUser) {
|
|
106
123
|
websocket.unsubscribeByPrefix(`/user2fa/`);
|
|
107
124
|
websocket.subscribe(`/user2fa/${props.adminUser.pk}`, async (data: {sessionId: string}) => {
|
|
108
|
-
|
|
125
|
+
if (!allowAddNewSessions) {
|
|
126
|
+
alert({message: 'Some process or user tries to add new actions to confirm. Action was blocked', variant: 'warning'});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
sessionsIdsToResolve.value.push(data.sessionId);
|
|
109
130
|
let confirmationResult;
|
|
131
|
+
if (isAwaiting2FAResult.value) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
110
134
|
try {
|
|
135
|
+
isAwaiting2FAResult.value = true;
|
|
111
136
|
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
112
137
|
} catch (error) {
|
|
113
138
|
console.error('Error during 2FA confirmation:', error);
|
|
114
139
|
}
|
|
140
|
+
isAwaiting2FAResult.value = false;
|
|
115
141
|
try {
|
|
116
142
|
const response = await callAdminForthApi({
|
|
117
143
|
method: "POST",
|
|
118
144
|
path: "/plugin/passkeys/resolveVerifyAuto",
|
|
119
|
-
body: { confirmationResult,
|
|
145
|
+
body: { confirmationResult, sessionsIds: sessionsIdsToResolve.value }
|
|
120
146
|
});
|
|
121
147
|
if (!response.ok && response.error === 'No session ID or confirmation result'){
|
|
122
148
|
alert({message: 'Verification session finished or cancelled.', variant: 'warning'});
|
|
@@ -125,15 +151,16 @@
|
|
|
125
151
|
} else if (response.ok) {
|
|
126
152
|
alert({message: 'Verification successful', variant: 'success'});
|
|
127
153
|
}
|
|
154
|
+
sessionsIdsToResolve.value = [];
|
|
128
155
|
} catch (error) {
|
|
129
156
|
console.error('Error resolving automatic 2FA verification:', error);
|
|
130
157
|
}
|
|
131
|
-
|
|
158
|
+
allowAddNewSessions = true;
|
|
132
159
|
});
|
|
133
160
|
websocket.subscribe(`/user2fa/${props.adminUser.pk}-resolve`, async (data: {sessionId: string}) => {
|
|
134
|
-
if (
|
|
161
|
+
if (sessionsIdsToResolve.value.includes(data.sessionId) && rejectFn && modelShow.value) {
|
|
135
162
|
onCancel();
|
|
136
|
-
|
|
163
|
+
sessionsIdsToResolve.value = sessionsIdsToResolve.value.filter(id => id !== data.sessionId);
|
|
137
164
|
}
|
|
138
165
|
});
|
|
139
166
|
}
|
package/dist/index.js
CHANGED
|
@@ -149,9 +149,10 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
149
149
|
verifyAuto(adminUser) {
|
|
150
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
151
151
|
const sessionId = crypto.randomUUID();
|
|
152
|
-
this.adminforth.
|
|
153
|
-
|
|
154
|
-
this.
|
|
152
|
+
const jwt = this.adminforth.auth.issueJWT({ sessionId, adminUserPk: adminUser.pk }, 'auto2FA', '5m');
|
|
153
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}`, { sessionId: jwt });
|
|
154
|
+
const result = yield this.waitForResponse(jwt);
|
|
155
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}-resolve`, { sessionId: jwt });
|
|
155
156
|
return result;
|
|
156
157
|
});
|
|
157
158
|
}
|
|
@@ -1019,11 +1020,26 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1019
1020
|
path: `/plugin/passkeys/resolveVerifyAuto`,
|
|
1020
1021
|
noAuth: false,
|
|
1021
1022
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies, headers }) {
|
|
1022
|
-
const
|
|
1023
|
+
const sessionsIds = body === null || body === void 0 ? void 0 : body.sessionsIds;
|
|
1023
1024
|
const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1025
|
+
const idsToResolve = sessionsIds;
|
|
1026
|
+
const resolveAllIdsAsFailed = (message) => {
|
|
1027
|
+
for (const id of idsToResolve) {
|
|
1028
|
+
this.resolveResponse(id, { ok: false, error: message });
|
|
1029
|
+
}
|
|
1030
|
+
return { ok: false, error: message };
|
|
1031
|
+
};
|
|
1032
|
+
if (!(sessionsIds) || !confirmationResult) {
|
|
1033
|
+
resolveAllIdsAsFailed('Confirmation window was closed or did not return required data');
|
|
1034
|
+
}
|
|
1035
|
+
for (const id of idsToResolve) {
|
|
1036
|
+
const validationResult = yield this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1037
|
+
if (!validationResult) {
|
|
1038
|
+
resolveAllIdsAsFailed('Invalid session ID or confirmation result');
|
|
1039
|
+
}
|
|
1040
|
+
if (validationResult.adminUserPk !== adminUser.pk) {
|
|
1041
|
+
resolveAllIdsAsFailed('Session does not belong to the authenticated user');
|
|
1042
|
+
}
|
|
1027
1043
|
}
|
|
1028
1044
|
const verificationResult = yield this.verify(confirmationResult, {
|
|
1029
1045
|
adminUser: adminUser,
|
|
@@ -1035,10 +1051,11 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1035
1051
|
}
|
|
1036
1052
|
});
|
|
1037
1053
|
if (!verificationResult || !('ok' in verificationResult)) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1054
|
+
resolveAllIdsAsFailed('Verification failed');
|
|
1055
|
+
}
|
|
1056
|
+
for (const id of idsToResolve) {
|
|
1057
|
+
this.resolveResponse(id, { ok: true, passkeyConfirmed: verificationResult });
|
|
1040
1058
|
}
|
|
1041
|
-
this.resolveResponse(sessionId, { ok: true, passkeyConfirmed: verificationResult });
|
|
1042
1059
|
return { ok: true };
|
|
1043
1060
|
})
|
|
1044
1061
|
});
|
package/index.ts
CHANGED
|
@@ -159,9 +159,10 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
159
159
|
|
|
160
160
|
public async verifyAuto(adminUser: AdminUser) {
|
|
161
161
|
const sessionId = crypto.randomUUID();
|
|
162
|
-
this.adminforth.
|
|
163
|
-
|
|
164
|
-
this.
|
|
162
|
+
const jwt = this.adminforth.auth.issueJWT({sessionId, adminUserPk: adminUser.pk}, 'auto2FA', '5m');
|
|
163
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}`, { sessionId: jwt });
|
|
164
|
+
const result = await this.waitForResponse(jwt);
|
|
165
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}-resolve`, { sessionId: jwt });
|
|
165
166
|
return result;
|
|
166
167
|
}
|
|
167
168
|
|
|
@@ -1080,12 +1081,31 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1080
1081
|
path: `/plugin/passkeys/resolveVerifyAuto`,
|
|
1081
1082
|
noAuth: false,
|
|
1082
1083
|
handler: async ({ body, adminUser, response, cookies, headers }) => {
|
|
1083
|
-
const
|
|
1084
|
+
const sessionsIds = body?.sessionsIds;
|
|
1084
1085
|
const confirmationResult = body?.confirmationResult;
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1086
|
+
const idsToResolve = sessionsIds;
|
|
1087
|
+
|
|
1088
|
+
const resolveAllIdsAsFailed = (message) => {
|
|
1089
|
+
for (const id of idsToResolve) {
|
|
1090
|
+
this.resolveResponse(id, { ok: false, error: message });
|
|
1091
|
+
}
|
|
1092
|
+
return { ok: false, error: message };
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (!(sessionsIds) || !confirmationResult) {
|
|
1096
|
+
resolveAllIdsAsFailed('Confirmation window was closed or did not return required data');
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
for (const id of idsToResolve) {
|
|
1100
|
+
const validationResult = await this.adminforth.auth.verify(id, 'auto2FA', false);
|
|
1101
|
+
if (!validationResult) {
|
|
1102
|
+
resolveAllIdsAsFailed('Invalid session ID or confirmation result');
|
|
1103
|
+
}
|
|
1104
|
+
if (validationResult.adminUserPk !== adminUser.pk) {
|
|
1105
|
+
resolveAllIdsAsFailed('Session does not belong to the authenticated user');
|
|
1106
|
+
}
|
|
1088
1107
|
}
|
|
1108
|
+
|
|
1089
1109
|
const verificationResult = await this.verify(confirmationResult, {
|
|
1090
1110
|
adminUser: adminUser,
|
|
1091
1111
|
userPk: adminUser.pk,
|
|
@@ -1096,10 +1116,12 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1096
1116
|
} as HttpExtra
|
|
1097
1117
|
});
|
|
1098
1118
|
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1119
|
+
resolveAllIdsAsFailed('Verification failed');
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
for (const id of idsToResolve) {
|
|
1123
|
+
this.resolveResponse(id, { ok: true, passkeyConfirmed: verificationResult });
|
|
1101
1124
|
}
|
|
1102
|
-
this.resolveResponse(sessionId, { ok: true, passkeyConfirmed: verificationResult });
|
|
1103
1125
|
return { ok: true };
|
|
1104
1126
|
}
|
|
1105
1127
|
});
|