@@ -221,18 +221,16 @@ To do it you first need to create custom component which will call `window.admin
221221< / template >
222222
223223< script setup lang = " ts" >
224- import { callAdminForthApi } from ' @/utils' ;
225- const emit = defineEmits <{ (e : ' callAction' ): void }>();
226- const props = defineProps <{ disabled? : boolean ; meta? : { verifyPath? : string ; [k : string ]: any } }>();
224+ const emit = defineEmits <{ (e : ' callAction' , payload ? : any ): void }>();
225+ const props = defineProps <{ disabled? : boolean ; meta? : Record <string , any > }>();
227226
228227 async function onClick() {
229- if (props .disabled ) {
230- return ;
231- }
228+ if (props .disabled ) return ;
229+
232230 const code = await window .adminforthTwoFaModal .getCode (); // this will ask user to enter code
233231 emit (' callAction' , { code }); // then we pass this code to action (from fronted to backend)
234232 }
235- < / script >
233+ < / script >
236234` ` `
237235
238236Now we need to read code entered on fronted on backend and verify that is is valid and not expired, on backend action handler:
@@ -244,16 +242,50 @@ options: {
244242 name: ' Auto submit' ,
245243 icon: ' flowbite:play-solid' ,
246244 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' }
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' };
257289 },
258290 showIn: { showButton: true , showThreeDotsMenu: true , list: true },
259291 // diff-add
@@ -268,64 +300,149 @@ options: {
268300Imagine you have some button which does some API call
269301
270302` ` ` ts
271- < Button @click = " callApi" > Call critical api < / Button >
303+ <template >
304+ < Button @click = " callAdminAPI" > Call critical API < / Button >
305+ < / template >
306+
272307
308+ < script setup lang = " ts" >
309+ import { callApi } from ' @/utils' ;
310+ import adminforth from ' @/adminforth' ;
273311
274- <script >
312+ async function callAdminAPI() {
313+ const code = await window .adminforthTwoFaModal .getCode ();
275314
276- async function callAPI() {
277- const res = await callAdminForthAPI (' /myCriticalAction' , { param: 1 })
315+ const res = await callApi ({
316+ path: ' /myCriticalAction' ,
317+ method: ' POST' ,
318+ body: { param: 1 },
319+ });
278320}
279- < / scrip >
321+ < / script >
280322` ` `
281323
282324On backend you have simple express api
283325
284- ` ` `
326+ ` ` ` ts
327+ app .post (` ${ADMIN_BASE_URL }/myCriticalAction ` ,
328+ admin .express .authorize (
329+ async (req : any , res : any ) => {
330+
331+ // ... your critical logic ...
285332
286- app .post (
287- adminforth .authorize (
288- () => {
289- req .body .param
290- ... some custom action
333+ return res .json ({ ok: true , successMessage: ' Action executed' });
291334 }
292- ))
335+ )
336+ );
293337` ` `
294338
295339You might want to protect this call with a TOTP code. To do it, we need to make this change
296340
297341` ` ` ts
298- < Button @ click = " callApi " > Call critical api < / Button >
299-
300-
301- < script >
342+ <template >
343+ < Button @ click = " callAdminAPI " > Call critical API < / Button >
344+ < / template >
345+
302346
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
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+ }
313382}
314- < / scrip >
383+ < / script >
384+
315385` ` `
316386
317387And oin API call we need to verify it:
318388
319389
320- ` ` `
321-
322- app .post (
323- adminforth .authorize (
324- () => {
325- // diff-add
326- getPBCNM
327- .. log some critical action
328- ... some custom action
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' });
329445 }
330- ))
446+ )
447+ );
331448` ` `
0 commit comments