From edcd2bd0df0e3b74b2300f472b58d12a7addf807 Mon Sep 17 00:00:00 2001 From: yaroslav8765 Date: Mon, 13 Oct 2025 13:20:01 +0300 Subject: [PATCH 1/3] docs: add docs for the 2fa modal --- .../tutorial/07-Plugins/02-TwoFactorsAuth.md | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md b/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md index 00a7e23a..520dd1ea 100644 --- a/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md +++ b/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md @@ -207,6 +207,247 @@ plugins: [ ... ``` +## Request 2FA on custom Actions + +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. + +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. + +```ts title='/custom/RequireTwoFaGate.vue' + + + +``` + +Now we need to use code which we got from user on frontend, inside of backend action handler and verify that is is valid and not expired: + +```ts title='/adminuser.ts' +options: { + actions: [ + { + name: 'Auto submit', + icon: 'flowbite:play-solid', + allowed: () => true, + action: async ({ recordId, adminUser, adminforth, extra, cookies }) => { + //diff-add + const verificationResult = extra?.verificationResult + //diff-add + if (!verificationResult) { + //diff-add + return { ok: false, error: 'No verification result provided' }; + //diff-add + } + //diff-add + const t2fa = adminforth.getPluginByClassName('TwoFactorsAuthPlugin'); + //diff-add + const result = await t2fa.verify(verificationResult, { + //diff-add + adminUser: adminUser, + //diff-add + userPk: adminUser.pk, + //diff-add + cookies: cookies + //diff-add + }); + + //diff-add + if (!result?.ok) { + //diff-add + return { ok: false, error: result?.error ?? 'Provided data is invalid' }; + //diff-add + } + //diff-add + await adminforth + //diff-add + .getPluginByClassName('AuditLogPlugin') + //diff-add + .logCustomAction({ + //diff-add + resourceId: 'aparts', + //diff-add + recordId: null, + //diff-add + actionId: 'visitedDashboard', + //diff-add + oldData: null, + //diff-add + data: { dashboard: 'main' }, + //diff-add + user: adminUser, + //diff-add + }); + + //your critical action logic + + return { ok: true, successMessage: 'Auto submitted' }; + }, + showIn: { showButton: true, showThreeDotsMenu: true, list: true }, + //diff-add + customComponent: '@@/RequireTwoFaGate.vue', + }, + ], +} +``` + +## Request 2FA from custom components + +Imagine you have some button which does some API call + +```ts + + + + +``` + +On backend you have simple express api + +```ts +app.post(`${ADMIN_BASE_URL}/myCriticalAction`, + admin.express.authorize( + async (req: any, res: any) => { + + // ... your critical logic ... + + return res.json({ ok: true, successMessage: 'Action executed' }); + } + ) +); +``` + +You might want to protect this call with a TOTP code. To do it, we need to make this change + +```ts + + + + + +``` + +And oin API call we need to verify it: + + +```ts +app.post(`${ADMIN_BASE_URL}/myCriticalAction`, + admin.express.authorize( + async (req: any, res: any) => { + + // diff-add + const { adminUser } = req; + // diff-add + const { param, verificationResult } = req.body ?? {}; + // diff-add + const t2fa = admin.getPluginByClassName('TwoFactorsAuthPlugin'); + // diff-add + const verifyRes = await t2fa.verify(verificationResult, { + // diff-add + adminUser: adminUser, + // diff-add + userPk: adminUser.pk, + // diff-add + cookies: cookies + // diff-add + }); + // diff-add + if (!('ok' in verifyRes)) { + // diff-add + return res.status(400).json({ ok: false, error: verifyRes.error || 'Verification failed' }); + // diff-add + } + // diff-add + await admin.getPluginByClassName('AuditLogPlugin').logCustomAction({ + // diff-add + resourceId: 'aparts', + // diff-add + recordId: null, + // diff-add + actionId: 'myCriticalAction', + // diff-add + oldData: null, + // diff-add + data: { param }, + // diff-add + user: adminUser, + // diff-add + }); + + // ... your critical logic ... + + return res.json({ ok: true, successMessage: 'Action executed' }); + } + ) +); +``` + + ## Custom label prefix in authenticator app By default label prefix in Authenticator app is formed from Adminforth [brandName setting](/docs/tutorial/Customization/branding/) which is best behaviour for most admin apps (always remember to configure brandName correctly e.g. "RoyalFinTech Admin") From 58efa3ce2c697d8c57fdec43229433da5bfdf4cd Mon Sep 17 00:00:00 2001 From: Ivan Borshchov Date: Tue, 14 Oct 2025 14:44:09 +0300 Subject: [PATCH 2/3] docs: improve 2fa plugin docs --- .../docs/tutorial/07-Plugins/02-TwoFactorsAuth.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md b/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md index 520dd1ea..ceab0bd0 100644 --- a/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md +++ b/adminforth/documentation/docs/tutorial/07-Plugins/02-TwoFactorsAuth.md @@ -209,14 +209,14 @@ plugins: [ ## Request 2FA on custom Actions -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. +You might want to to allow to call some custom critical/money related actions with additional 2FA approval. This eliminates risks caused by user cookies theft by some virous/doorway software after login. -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. +To do it, first, create frontend custom component which wraps and intercepts click event to menu item, and in click handler do a call to `window.adminforthTwoFaModal.getCode(cb?)` frontend API exposed by this plugin. This is awaitable call wich shows 2FA popup and asks user to authenticate with 2nd factor (if passkey is enabled it will be suggested first, with ability to fallback to TOTP) ```ts title='/custom/RequireTwoFaGate.vue' @@ -228,12 +228,12 @@ To do it you first need to create custom component which will call `window.admin if (props.disabled) return; const verificationResult = await window.adminforthTwoFaModal.get2FaConfirmationResult(); // this will ask user to enter code - emit('callAction', { verificationResult }); // then we pass this code to action (from fronted to backend) + emit('callAction', { verificationResult }); // then we pass this verification result to action (from fronted to backend) } ``` -Now we need to use code which we got from user on frontend, inside of backend action handler and verify that is is valid and not expired: +Now we need to use verification result which we got from user on frontend, inside of backend action handler and verify that it is valid (and not expired): ```ts title='/adminuser.ts' options: { @@ -267,7 +267,7 @@ options: { //diff-add if (!result?.ok) { //diff-add - return { ok: false, error: result?.error ?? 'Provided data is invalid' }; + return { ok: false, error: result?.error ?? 'Provided 2fa verification data is invalid' }; //diff-add } //diff-add @@ -344,7 +344,7 @@ app.post(`${ADMIN_BASE_URL}/myCriticalAction`, ); ``` -You might want to protect this call with a TOTP code. To do it, we need to make this change +You might want to protect this call with a second factor also. To do it, we need to make this change ```ts