@@ -206,243 +206,3 @@ plugins: [
206206],
207207...
208208` ` `
209-
210- ## Request 2FA on custom Actions
211-
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.
213-
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.
215-
216- ` ` ` ts title = ' /custom/RequireTwoFaGate.vue'
217- <template >
218- < div class = " contents" @click .stop .prevent = " onClick" >
219- < slot / > <!-- render action defgault contend - button /icon -->
220- < / div >
221- < / template >
222-
223- < script setup lang = " ts" >
224- const emit = defineEmits <{ (e : ' callAction' , payload ? : any ): void }>();
225- const props = defineProps <{ disabled? : boolean ; meta? : Record <string , any > }>();
226-
227- async function onClick() {
228- if (props .disabled ) return ;
229-
230- const code = await window .adminforthTwoFaModal .getCode (); // this will ask user to enter code
231- emit (' callAction' , { code }); // then we pass this code to action (from fronted to backend)
232- }
233- < / script >
234- ` ` `
235-
236- Now we need to read code entered on fronted on backend and verify that is is valid and not expired, on backend action handler:
237-
238- ` ` ` ts title = ' /adminuser.ts'
239- options : {
240- actions: [
241- {
242- name: ' Auto submit' ,
243- icon: ' flowbite:play-solid' ,
244- allowed : () => true ,
245- action : async ({ recordId , adminUser , adminforth , extra }) => {
246- // diff-add
247- const code = extra ?.code
248- // diff-add
249- if (! code ) {
250- // diff-add
251- return { ok: false , error: ' No TOTP code provided' };
252- // diff-add
253- }
254- // diff-add
255- const t2fa = adminforth .getPluginByClassName <TwoFactorsAuthPlugin >(' TwoFactorsAuthPlugin' );
256- // diff-add
257- const result = await t2fa .verify (code , { adminUser });
258-
259- // diff-add
260- if (! result ?.ok ) {
261- // diff-add
262- return { ok: false , error: result ?.error ?? ' TOTP code is invalid' };
263- // diff-add
264- }
265- // diff-add
266- await adminforth
267- // diff-add
268- .getPluginByClassName <AuditLogPlugin >(' AuditLogPlugin' )
269- // diff-add
270- .logCustomAction ({
271- // diff-add
272- resourceId: ' aparts' ,
273- // diff-add
274- recordId: null ,
275- // diff-add
276- actionId: ' visitedDashboard' ,
277- // diff-add
278- oldData: null ,
279- // diff-add
280- data: { dashboard: ' main' },
281- // diff-add
282- user: adminUser ,
283- // diff-add
284- });
285-
286- // your critical action logic
287-
288- return { ok: true , successMessage: ' Auto submitted' };
289- },
290- showIn: { showButton: true , showThreeDotsMenu: true , list: true },
291- // diff-add
292- customComponent: ' @@/RequireTwoFaGate.vue' ,
293- },
294- ],
295- }
296- ` ` `
297-
298- ## Request 2FA from custom components
299-
300- Imagine you have some button which does some API call
301-
302- ` ` ` ts
303- <template >
304- < Button @click = " callAdminAPI" > Call critical API < / Button >
305- < / template >
306-
307-
308- < script setup lang = " ts" >
309- import { callApi } from ' @/utils' ;
310- import adminforth from ' @/adminforth' ;
311-
312- async function callAdminAPI() {
313- const code = await window .adminforthTwoFaModal .getCode ();
314-
315- const res = await callApi ({
316- path: ' /myCriticalAction' ,
317- method: ' POST' ,
318- body: { param: 1 },
319- });
320- }
321- < / script >
322- ` ` `
323-
324- On backend you have simple express api
325-
326- ` ` ` ts
327- app .post (` ${ADMIN_BASE_URL }/myCriticalAction ` ,
328- admin .express .authorize (
329- async (req : any , res : any ) => {
330-
331- // ... your critical logic ...
332-
333- return res .json ({ ok: true , successMessage: ' Action executed' });
334- }
335- )
336- );
337- ` ` `
338-
339- You might want to protect this call with a TOTP code. To do it, we need to make this change
340-
341- ` ` ` ts
342- <template >
343- < Button @click = " callAdminAPI" > Call critical API < / Button >
344- < / template >
345-
346-
347- < script setup lang = " ts" >
348- import { callApi } from ' @/utils' ;
349- import adminforth from ' @/adminforth' ;
350-
351- async function callAdminAPI() {
352- const code = await window .adminforthTwoFaModal .getCode ();
353-
354- // diff-remove
355- const res = await callApi ({
356- // diff-remove
357- path: ' /myCriticalAction' ,
358- // diff-remove
359- method: ' POST' ,
360- // diff-remove
361- body: { param: 1 },
362- // diff-remove
363- });
364-
365- // diff-add
366- const res = await callApi ({
367- // diff-add
368- path: ' /myCriticalAction' ,
369- // diff-add
370- method: ' POST' ,
371- // diff-add
372- body: { param: 1 , code: String (code ) },
373- // diff-add
374- });
375-
376- // diff-add
377- if (! res ?.ok ) {
378- // diff-add
379- adminforth .alert ({ message: res .error , variant: ' danger' });
380- // diff-add
381- }
382- }
383- < / script >
384-
385- ` ` `
386-
387- And oin API call we need to verify it:
388-
389-
390- ` ` ` ts
391- app .post (` ${ADMIN_BASE_URL }/myCriticalAction ` ,
392- admin .express .authorize (
393- async (req : any , res : any ) => {
394-
395- // diff-remove
396- // ... your critical logic ...
397-
398- // diff-remove
399- return res .json ({ ok: true , successMessage: ' Action executed' });
400-
401- // diff-add
402- const { adminUser } = req ;
403- // diff-add
404- const { param, code } = req .body ?? {};
405- // diff-add
406- const token = String (code ?? ' ' ).replace (/ \D / g , ' ' );
407- // diff-add
408- if (token .length !== 6 ) {
409- // diff-add
410- return res .status (401 ).json ({ ok: false , error: ' TOTP must be 6 digits' });
411- // diff-add
412- }
413- // diff-add
414- const t2fa = admin .getPluginByClassName <TwoFactorsAuthPlugin >(' TwoFactorsAuthPlugin' );
415- // diff-add
416- const verifyRes = await t2fa .verify (token , { adminUser });
417- // diff-add
418- if (! (' ok' in verifyRes )) {
419- // diff-add
420- return res .status (400 ).json ({ ok: false , error: verifyRes .error || ' Wrong or expired OTP code' });
421- // diff-add
422- }
423- // diff-add
424- await admin .getPluginByClassName <AuditLogPlugin >(' AuditLogPlugin' ).logCustomAction ({
425- // diff-add
426- resourceId: ' aparts' ,
427- // diff-add
428- recordId: null ,
429- // diff-add
430- actionId: ' myCriticalAction' ,
431- // diff-add
432- oldData: null ,
433- // diff-add
434- data: { param },
435- // diff-add
436- user: adminUser ,
437- // diff-add
438- });
439-
440- // diff-add
441- // ... your critical logic ...
442-
443- // diff-add
444- return res .json ({ ok: true , successMessage: ' Action executed' });
445- }
446- )
447- );
448- ` ` `
0 commit comments