@adminforth/two-factors-auth 3.0.5 → 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 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"> <img src="https://img.shields.io/npm/dt/@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> <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
+ [![Ask AI](https://tluma.ai/badge)](https://tluma.ai/ask-ai/devforth/adminforth)
4
6
 
5
7
  Allows to enable Two-Factor Authentication to an adminforth application.
6
8
 
7
- ## For usage, see [AdminForth TwoFactorsAuth Documentation](https://adminforth.dev/docs/tutorial/Plugins/TwoFactorsAuth/)
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 280,853 bytes received 229 bytes 562,164.00 bytes/sec
20
- total size is 279,947 speedup is 1.00
19
+ sent 282,665 bytes received 229 bytes 565,788.00 bytes/sec
20
+ total size is 281,759 speedup is 1.00
@@ -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 adminforth from '@/adminforth';
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
@@ -6,7 +6,7 @@
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
7
7
  },
8
8
  "keywords": [],
9
- "author": "",
9
+ "author": "DevForth (https://devforth.io)",
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "dependencies": {
@@ -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 adminforth from '@/adminforth';
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
@@ -6,7 +6,7 @@
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
7
7
  },
8
8
  "keywords": [],
9
- "author": "",
9
+ "author": "DevForth (https://devforth.io)",
10
10
  "license": "ISC",
11
11
  "description": "",
12
12
  "dependencies": {
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, extra }) {
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, extra }) => {
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.5",
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/TwoFactorsAuth/",
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.30.0"
31
+ "adminforth": "^2.42.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^22.10.7",