Skip to content

Commit a5defaf

Browse files
authored
Update 02-TwoFactorsAuth.md
1 parent e65f705 commit a5defaf

File tree

1 file changed

+97
-24
lines changed

1 file changed

+97
-24
lines changed

adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -207,52 +207,125 @@ plugins: [
207207
...
208208
```
209209
210-
## Trigger 2FA from Actions via a Custom Component
210+
## Request 2FA on custom Actions
211211
212-
Enable a one‑time 2FA prompt before running any AdminForth action by attaching a tiny Vue wrapper via `customComponent`. The plugin exposes a global modal: `window.adminforthTwoFaModal.getCode(cb?)`.
212+
You might want to to allow to call some custom critical/money related actions with additional 2FA approval. This eliminates risk that user cookies might be stolen by some virous/doorway software after login.
213213
214-
### Minimal Wrapper Component
214+
To do it you first need to create custom component which will call `window.adminforthTwoFaModal.getCode(cb?)` frontend API exposed by this plugin. This is awaitable call wich shows 2FA popup and asks user to enter a code.
215215
216216
```ts title='/custom/RequireTwoFaGate.vue'
217217
<template>
218-
<div class="contents" @click.stop.prevent="onClick"><slot /></div>
218+
<div class="contents" @click.stop.prevent="onClick">
219+
<slot /> <!-- render action defgault contend - button/icon -->
220+
</div>
219221
</template>
222+
220223
<script setup lang="ts">
221224
import { callAdminForthApi } from '@/utils';
222225
const emit = defineEmits<{ (e: 'callAction'): void }>();
223226
const props = defineProps<{ disabled?: boolean; meta?: { verifyPath?: string; [k: string]: any } }>();
224227

225-
async function verify2fa(code: string) {
226-
const path = props.meta?.verifyPath ?? '/plugin/twofa/verify';
227-
const resp = await callAdminForthApi({ method: 'POST', path, body: { code } });
228-
return !!resp?.ok;
229-
}
230-
231228
async function onClick() {
232-
if (props.disabled) return;
233-
if (!window.adminforthTwoFaModal?.getCode) { emit('callAction'); return; }
234-
await window.adminforthTwoFaModal.getCode(verify2fa);
235-
emit('callAction');
229+
if (props.disabled) {
230+
return;
231+
}
232+
const code = await window.adminforthTwoFaModal.getCode(); // this will ask user to enter code
233+
emit('callAction', { code }); // then we pass this code to action (from fronted to backend)
236234
}
237235
</script>
238236
```
239237
240-
### Attach to an Action
238+
Now we need to read code entered on fronted on backend and verify that is is valid and not expired, on backend action handler:
241239
242240
```ts title='/adminuser.ts'
243241
options: {
244242
actions: [
245243
{
246-
name: 'Auto submit',
247-
icon: 'flowbite:play-solid',
248-
allowed: () => true,
249-
action: async ({ recordId, adminUser }) => ({ ok: true, successMessage: 'Auto submitted' }),
250-
showIn: { showButton: true, showThreeDotsMenu: true, list: true },
251-
//diff-add
252-
customComponent: '@@/RequireTwoFaGate.vue',
253-
// or with runtime config:
254-
// customComponent: { name: '@@/RequireTwoFaGate.vue', meta: { verifyPath: '/plugin/twofa/verify' } },
244+
name: 'Auto submit',
245+
icon: 'flowbite:play-solid',
246+
allowed: () => true,
247+
action: async ({ recordId, adminUser, payload, adminforth }) => {
248+
const { code } = payload;
249+
const totpIsValid = adminforth.getPluginByClassName<>('T2FAPlug').verify(code);
250+
if (!totpIsValid) {
251+
return { ok: false, error: 'TOTP code is invalid' }
252+
}
253+
// we will also register fact of ussage of this critical action using audit log Plugin
254+
getPluginBYClassName<auditlog>.logCustomAction()...
255+
.... your critical action logic ....
256+
return { ok: true, successMessage: 'Auto submitted' }
257+
},
258+
showIn: { showButton: true, showThreeDotsMenu: true, list: true },
259+
//diff-add
260+
customComponent: '@@/RequireTwoFaGate.vue',
255261
},
256262
],
257263
}
258264
```
265+
266+
## Request 2FA from custom components
267+
268+
Imagine you have some button which does some API call
269+
270+
```ts
271+
<Button @click="callApi">Call critical api</Button>
272+
273+
274+
<script>
275+
276+
async function callAPI() {
277+
const res = await callAdminForthAPI('/myCriticalAction', { param: 1 })
278+
}
279+
</scrip>
280+
```
281+
282+
On backend you have simple express api
283+
284+
```
285+
286+
app.post(
287+
adminforth.authorize(
288+
() => {
289+
req.body.param
290+
... some custom action
291+
}
292+
))
293+
```
294+
295+
You might want to protect this call with a TOTP code. To do it, we need to make this change
296+
297+
```ts
298+
<Button @click="callApi">Call critical api</Button>
299+
300+
301+
<script>
302+
303+
function callAPI() {
304+
// diff-remove
305+
const res = callAdminForthAPI('/myCriticalAction', { param: 1 })
306+
//diff-add
307+
const code = await window.adminforthTwoFaModal.getCode(async (code) => {
308+
//diff-add
309+
const res = await callAdminForthAPI('/myCriticalAction', { param: 1, code })
310+
//diff-add
311+
return !res.totpError
312+
}); // this will ask user to enter code
313+
}
314+
</scrip>
315+
```
316+
317+
And oin API call we need to verify it:
318+
319+
320+
```
321+
322+
app.post(
323+
adminforth.authorize(
324+
() => {
325+
//diff-add
326+
getPBCNM
327+
.. log some critical action
328+
... some custom action
329+
}
330+
))
331+
```

0 commit comments

Comments
 (0)