@adminforth/two-factors-auth 3.0.4 → 3.1.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.
- package/README.md +26 -2
- package/build.log +2 -2
- package/custom/TwoFAModal.vue +47 -2
- package/custom/package.json +1 -1
- package/dist/custom/TwoFAModal.vue +47 -2
- package/dist/custom/package.json +1 -1
- package/dist/index.js +49 -3
- package/index.ts +51 -3
- package/package.json +4 -4
- package/types.ts +2 -1
package/README.md
CHANGED
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
# AdminForth TwoFactorsAuth Plugin
|
|
2
2
|
|
|
3
|
-
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /> <img src="https://woodpecker.devforth.io/api/badges/3848/status.svg" alt="Build Status" /> <a href="https://www.npmjs.com/package/@adminforth/two-factors-auth"
|
|
3
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /> <img src="https://woodpecker.devforth.io/api/badges/3848/status.svg" alt="Build Status" /> <a href="https://www.npmjs.com/package/@adminforth/two-factors-auth"><img src="https://img.shields.io/npm/dm/@adminforth/two-factors-auth" alt="npm downloads" /></a> <a href="https://www.npmjs.com/package/@adminforth/two-factors-auth"><img src="https://img.shields.io/npm/v/@adminforth/two-factors-auth" alt="npm version" /></a>
|
|
4
|
+
|
|
5
|
+
[](https://tluma.ai/ask-ai/devforth/adminforth)
|
|
4
6
|
|
|
5
7
|
Allows to enable Two-Factor Authentication to an adminforth application.
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Add two-factor authentication to AdminForth login.
|
|
12
|
+
- Improve account security for admin users.
|
|
13
|
+
- Introduce a stronger verification step at sign-in.
|
|
14
|
+
- Integrate 2FA into existing authentication workflows.
|
|
15
|
+
|
|
16
|
+
## Documentation
|
|
17
|
+
|
|
18
|
+
Full setup and configuration guide:
|
|
19
|
+
|
|
20
|
+
[AdminForth TwoFactorsAuth Documentation](https://adminforth.dev/docs/tutorial/Plugins/two-factors-auth/)
|
|
21
|
+
|
|
22
|
+
## About AdminForth
|
|
23
|
+
|
|
24
|
+
AdminForth is an open-source, agent-first admin framework for building robust admin panels and back-office applications faster.
|
|
25
|
+
|
|
26
|
+
## Related links
|
|
27
|
+
|
|
28
|
+
- [AdminForth website](https://adminforth.dev)
|
|
29
|
+
- [npm package](https://www.npmjs.com/package/@adminforth/two-factors-auth)
|
|
30
|
+
- [More AdminForth plugins](https://adminforth.dev/docs/tutorial/ListOfPlugins/)
|
|
31
|
+
- [Built by DevForth](https://devforth.io)
|
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 282,665 bytes received 229 bytes 565,788.00 bytes/sec
|
|
20
|
+
total size is 281,759 speedup is 1.00
|
package/custom/TwoFAModal.vue
CHANGED
|
@@ -79,8 +79,9 @@
|
|
|
79
79
|
import { Link, Button } from '@/afcl';
|
|
80
80
|
import { IconShieldOutline } from '@iconify-prerendered/vue-flowbite';
|
|
81
81
|
import { getPasskey } from './utils.js'
|
|
82
|
-
import
|
|
83
|
-
|
|
82
|
+
import { useAdminforth } from '@/adminforth';
|
|
83
|
+
import websocket from '@/websocket';
|
|
84
|
+
import type { AdminUser } from '@/types/Common';
|
|
84
85
|
|
|
85
86
|
declare global {
|
|
86
87
|
interface Window {
|
|
@@ -94,7 +95,51 @@
|
|
|
94
95
|
}
|
|
95
96
|
const props = defineProps<{
|
|
96
97
|
autoFinishLogin?: boolean
|
|
98
|
+
adminUser?: AdminUser
|
|
97
99
|
}>();
|
|
100
|
+
|
|
101
|
+
const { alert } = useAdminforth();
|
|
102
|
+
|
|
103
|
+
let currentSessionId: string | null = null;
|
|
104
|
+
watch( props, () => {
|
|
105
|
+
if (props.adminUser) {
|
|
106
|
+
websocket.unsubscribeByPrefix(`/user2fa/`);
|
|
107
|
+
websocket.subscribe(`/user2fa/${props.adminUser.pk}`, async (data: {sessionId: string}) => {
|
|
108
|
+
currentSessionId = data.sessionId;
|
|
109
|
+
let confirmationResult;
|
|
110
|
+
try {
|
|
111
|
+
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Error during 2FA confirmation:', error);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const response = await callAdminForthApi({
|
|
117
|
+
method: "POST",
|
|
118
|
+
path: "/plugin/passkeys/resolveVerifyAuto",
|
|
119
|
+
body: { confirmationResult, sessionId: data.sessionId }
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok && response.error === 'No session ID or confirmation result'){
|
|
122
|
+
alert({message: 'Verification session finished or cancelled.', variant: 'warning'});
|
|
123
|
+
} else if (!response.ok) {
|
|
124
|
+
alert({message: 'Verification failed', variant: 'danger'});
|
|
125
|
+
} else if (response.ok) {
|
|
126
|
+
alert({message: 'Verification successful', variant: 'success'});
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Error resolving automatic 2FA verification:', error);
|
|
130
|
+
}
|
|
131
|
+
currentSessionId = null;
|
|
132
|
+
});
|
|
133
|
+
websocket.subscribe(`/user2fa/${props.adminUser.pk}-resolve`, async (data: {sessionId: string}) => {
|
|
134
|
+
if (currentSessionId === data.sessionId && rejectFn && modelShow.value) {
|
|
135
|
+
onCancel();
|
|
136
|
+
currentSessionId = null;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
|
|
98
143
|
const emit = defineEmits<{
|
|
99
144
|
(e: 'resolved', payload: any): void
|
|
100
145
|
(e: 'rejected', err?: any): void
|
package/custom/package.json
CHANGED
|
@@ -79,8 +79,9 @@
|
|
|
79
79
|
import { Link, Button } from '@/afcl';
|
|
80
80
|
import { IconShieldOutline } from '@iconify-prerendered/vue-flowbite';
|
|
81
81
|
import { getPasskey } from './utils.js'
|
|
82
|
-
import
|
|
83
|
-
|
|
82
|
+
import { useAdminforth } from '@/adminforth';
|
|
83
|
+
import websocket from '@/websocket';
|
|
84
|
+
import type { AdminUser } from '@/types/Common';
|
|
84
85
|
|
|
85
86
|
declare global {
|
|
86
87
|
interface Window {
|
|
@@ -94,7 +95,51 @@
|
|
|
94
95
|
}
|
|
95
96
|
const props = defineProps<{
|
|
96
97
|
autoFinishLogin?: boolean
|
|
98
|
+
adminUser?: AdminUser
|
|
97
99
|
}>();
|
|
100
|
+
|
|
101
|
+
const { alert } = useAdminforth();
|
|
102
|
+
|
|
103
|
+
let currentSessionId: string | null = null;
|
|
104
|
+
watch( props, () => {
|
|
105
|
+
if (props.adminUser) {
|
|
106
|
+
websocket.unsubscribeByPrefix(`/user2fa/`);
|
|
107
|
+
websocket.subscribe(`/user2fa/${props.adminUser.pk}`, async (data: {sessionId: string}) => {
|
|
108
|
+
currentSessionId = data.sessionId;
|
|
109
|
+
let confirmationResult;
|
|
110
|
+
try {
|
|
111
|
+
confirmationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult();
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Error during 2FA confirmation:', error);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const response = await callAdminForthApi({
|
|
117
|
+
method: "POST",
|
|
118
|
+
path: "/plugin/passkeys/resolveVerifyAuto",
|
|
119
|
+
body: { confirmationResult, sessionId: data.sessionId }
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok && response.error === 'No session ID or confirmation result'){
|
|
122
|
+
alert({message: 'Verification session finished or cancelled.', variant: 'warning'});
|
|
123
|
+
} else if (!response.ok) {
|
|
124
|
+
alert({message: 'Verification failed', variant: 'danger'});
|
|
125
|
+
} else if (response.ok) {
|
|
126
|
+
alert({message: 'Verification successful', variant: 'success'});
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Error resolving automatic 2FA verification:', error);
|
|
130
|
+
}
|
|
131
|
+
currentSessionId = null;
|
|
132
|
+
});
|
|
133
|
+
websocket.subscribe(`/user2fa/${props.adminUser.pk}-resolve`, async (data: {sessionId: string}) => {
|
|
134
|
+
if (currentSessionId === data.sessionId && rejectFn && modelShow.value) {
|
|
135
|
+
onCancel();
|
|
136
|
+
currentSessionId = null;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
|
|
98
143
|
const emit = defineEmits<{
|
|
99
144
|
(e: 'resolved', payload: any): void
|
|
100
145
|
(e: 'rejected', err?: any): void
|
package/dist/custom/package.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import crypto from 'crypto';
|
|
|
16
16
|
export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
17
17
|
constructor(options) {
|
|
18
18
|
super(options, import.meta.url);
|
|
19
|
+
this.pending = new Map();
|
|
19
20
|
this.options = options;
|
|
20
21
|
this.shouldHaveSingleInstancePerWholeApp = () => true;
|
|
21
22
|
}
|
|
@@ -133,6 +134,27 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
133
134
|
return false;
|
|
134
135
|
});
|
|
135
136
|
}
|
|
137
|
+
waitForResponse(id) {
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
this.pending.set(id, resolve);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
resolveResponse(id, data) {
|
|
143
|
+
const resolve = this.pending.get(id);
|
|
144
|
+
if (resolve) {
|
|
145
|
+
resolve(data);
|
|
146
|
+
this.pending.delete(id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
verifyAuto(adminUser) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
const sessionId = crypto.randomUUID();
|
|
152
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}`, { sessionId });
|
|
153
|
+
const result = yield this.waitForResponse(sessionId);
|
|
154
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}-resolve`, { sessionId });
|
|
155
|
+
return result;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
136
158
|
verify(confirmationResult, opts) {
|
|
137
159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
138
160
|
var _a, _b, _c, _d, _e;
|
|
@@ -695,7 +717,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
695
717
|
method: 'POST',
|
|
696
718
|
path: `/plugin/passkeys/registerPasskeyRequest`,
|
|
697
719
|
noAuth: false,
|
|
698
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies
|
|
720
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies }) {
|
|
699
721
|
var _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
700
722
|
const mode = body === null || body === void 0 ? void 0 : body.mode;
|
|
701
723
|
const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
|
|
@@ -703,8 +725,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
703
725
|
adminUser: adminUser,
|
|
704
726
|
userPk: adminUser.pk,
|
|
705
727
|
cookies: cookies,
|
|
706
|
-
response: response
|
|
707
|
-
extra: Object.assign({}, extra)
|
|
728
|
+
response: response
|
|
708
729
|
});
|
|
709
730
|
if (!verificationResult || !('ok' in verificationResult)) {
|
|
710
731
|
return { ok: false, error: 'Verification failed' };
|
|
@@ -990,5 +1011,30 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
990
1011
|
}
|
|
991
1012
|
})
|
|
992
1013
|
});
|
|
1014
|
+
server.endpoint({
|
|
1015
|
+
method: 'POST',
|
|
1016
|
+
path: `/plugin/passkeys/resolveVerifyAuto`,
|
|
1017
|
+
noAuth: false,
|
|
1018
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies }) {
|
|
1019
|
+
const sessionId = body === null || body === void 0 ? void 0 : body.sessionId;
|
|
1020
|
+
const confirmationResult = body === null || body === void 0 ? void 0 : body.confirmationResult;
|
|
1021
|
+
if (!sessionId || !confirmationResult) {
|
|
1022
|
+
this.resolveResponse(sessionId, { ok: false, error: 'No session ID or confirmation result' });
|
|
1023
|
+
return { ok: false, error: 'No session ID or confirmation result' };
|
|
1024
|
+
}
|
|
1025
|
+
const verificationResult = yield this.verify(confirmationResult, {
|
|
1026
|
+
adminUser: adminUser,
|
|
1027
|
+
userPk: adminUser.pk,
|
|
1028
|
+
cookies: cookies,
|
|
1029
|
+
response: response
|
|
1030
|
+
});
|
|
1031
|
+
if (!verificationResult || !('ok' in verificationResult)) {
|
|
1032
|
+
this.resolveResponse(sessionId, { ok: false, error: 'Verification failed' });
|
|
1033
|
+
return { ok: false, error: 'Verification failed' };
|
|
1034
|
+
}
|
|
1035
|
+
this.resolveResponse(sessionId, { ok: true, passkeyConfirmed: verificationResult });
|
|
1036
|
+
return { ok: true };
|
|
1037
|
+
})
|
|
1038
|
+
});
|
|
993
1039
|
}
|
|
994
1040
|
}
|
package/index.ts
CHANGED
|
@@ -141,6 +141,30 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
private pending = new Map<string, (value: any) => void>();
|
|
145
|
+
|
|
146
|
+
private waitForResponse(id: string): Promise<any> {
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
this.pending.set(id, resolve);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private resolveResponse(id: string, data: any) {
|
|
153
|
+
const resolve = this.pending.get(id);
|
|
154
|
+
if (resolve) {
|
|
155
|
+
resolve(data);
|
|
156
|
+
this.pending.delete(id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public async verifyAuto(adminUser: AdminUser) {
|
|
161
|
+
const sessionId = crypto.randomUUID();
|
|
162
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}`, { sessionId });
|
|
163
|
+
const result = await this.waitForResponse(sessionId);
|
|
164
|
+
this.adminforth.websocket.publish(`/user2fa/${adminUser.pk}-resolve`, { sessionId });
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
144
168
|
public async verify(
|
|
145
169
|
confirmationResult: Record<string, any>,
|
|
146
170
|
opts?: { adminUser?: AdminUser; userPk?: string; cookies?: any, response?: IAdminForthHttpResponse, extra?: HttpExtra }
|
|
@@ -753,7 +777,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
753
777
|
method: 'POST',
|
|
754
778
|
path: `/plugin/passkeys/registerPasskeyRequest`,
|
|
755
779
|
noAuth: false,
|
|
756
|
-
handler: async ({ body, adminUser, response, cookies
|
|
780
|
+
handler: async ({ body, adminUser, response, cookies }) => {
|
|
757
781
|
const mode = body?.mode;
|
|
758
782
|
|
|
759
783
|
const confirmationResult = body?.confirmationResult;
|
|
@@ -761,8 +785,7 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
761
785
|
adminUser: adminUser,
|
|
762
786
|
userPk: adminUser.pk,
|
|
763
787
|
cookies: cookies,
|
|
764
|
-
response: response
|
|
765
|
-
extra: { ...extra }
|
|
788
|
+
response: response
|
|
766
789
|
});
|
|
767
790
|
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
768
791
|
return { ok: false, error: 'Verification failed' };
|
|
@@ -1049,5 +1072,30 @@ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
|
|
|
1049
1072
|
}
|
|
1050
1073
|
}
|
|
1051
1074
|
});
|
|
1075
|
+
server.endpoint({
|
|
1076
|
+
method: 'POST',
|
|
1077
|
+
path: `/plugin/passkeys/resolveVerifyAuto`,
|
|
1078
|
+
noAuth: false,
|
|
1079
|
+
handler: async ({ body, adminUser, response, cookies }) => {
|
|
1080
|
+
const sessionId = body?.sessionId;
|
|
1081
|
+
const confirmationResult = body?.confirmationResult;
|
|
1082
|
+
if (!sessionId || !confirmationResult) {
|
|
1083
|
+
this.resolveResponse(sessionId, { ok: false, error: 'No session ID or confirmation result' });
|
|
1084
|
+
return { ok: false, error: 'No session ID or confirmation result' };
|
|
1085
|
+
}
|
|
1086
|
+
const verificationResult = await this.verify(confirmationResult, {
|
|
1087
|
+
adminUser: adminUser,
|
|
1088
|
+
userPk: adminUser.pk,
|
|
1089
|
+
cookies: cookies,
|
|
1090
|
+
response: response
|
|
1091
|
+
});
|
|
1092
|
+
if ( !verificationResult || !('ok' in verificationResult) ) {
|
|
1093
|
+
this.resolveResponse(sessionId, { ok: false, error: 'Verification failed' });
|
|
1094
|
+
return { ok: false, error: 'Verification failed' };
|
|
1095
|
+
}
|
|
1096
|
+
this.resolveResponse(sessionId, { ok: true, passkeyConfirmed: verificationResult });
|
|
1097
|
+
return { ok: true };
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1052
1100
|
}
|
|
1053
1101
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/two-factors-auth",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "AdminForth plugin for two factors authentication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
|
-
"homepage": "https://adminforth.dev/docs/tutorial/Plugins/
|
|
10
|
+
"homepage": "https://adminforth.dev/docs/tutorial/Plugins/two-factors-auth/",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
13
|
"url": "https://github.com/devforth/adminforth-two-factors-auth.git"
|
|
@@ -21,14 +21,14 @@
|
|
|
21
21
|
"two-factors-auth",
|
|
22
22
|
"2fa"
|
|
23
23
|
],
|
|
24
|
-
"author": "devforth",
|
|
24
|
+
"author": "DevForth (https://devforth.io)",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@simplewebauthn/server": "^13.2.1",
|
|
28
28
|
"node-2fa": "^2.0.3"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"adminforth": "^2.
|
|
31
|
+
"adminforth": "^2.42.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.10.7",
|
package/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AdminUser, KeyValueAdapter } from "adminforth";
|
|
2
|
+
import {type PluginsCommonOptions } from "adminforth";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
+
export interface PluginOptions extends PluginsCommonOptions {
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Name of the field in the auth resource which will store 2FA secret.
|