@vivinkv28/strapi-2fa-admin-plugin 0.1.8 → 0.1.9

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.
Files changed (2) hide show
  1. package/README.md +56 -464
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,32 +1,25 @@
1
1
  # @vivinkv28/strapi-2fa-admin-plugin
2
2
 
3
- `@vivinkv28/strapi-2fa-admin-plugin` is a Strapi 5 plugin that adds the backend side of an OTP-based admin login flow.
3
+ Strapi 5 plugin for OTP-based admin authentication.
4
4
 
5
- It gives your Strapi project:
5
+ This package adds the backend flow for:
6
6
 
7
- - admin credential validation before OTP
7
+ - admin email/password validation
8
8
  - OTP challenge creation
9
9
  - OTP resend and verification
10
10
  - rate limiting for login, verify, and resend
11
- - OTP delivery through Strapi's email plugin
12
11
  - final Strapi admin session creation only after OTP verification
13
12
 
14
- ## What This Package Is
13
+ ## Important
15
14
 
16
- This package is a **backend/admin-auth plugin**.
15
+ This package does not automatically replace the default Strapi admin login UI.
17
16
 
18
- It does **not** automatically replace the default Strapi admin login UI.
17
+ You need two parts:
19
18
 
20
- To use it in a real project, you need two parts:
19
+ 1. this plugin for the backend OTP logic
20
+ 2. a host-project patch for the Strapi admin login UI
21
21
 
22
- 1. this plugin for the backend OTP/auth logic
23
- 2. an admin login screen integration in your Strapi project that calls the plugin endpoints
24
-
25
- That means this package is ideal if you want:
26
-
27
- - a reusable backend OTP engine
28
- - control over how the admin login UI looks
29
- - a project-level patch/customization for the Strapi admin login page
22
+ The good part is that this npm package now includes the host patch source directly, so you can copy it into your Strapi project.
30
23
 
31
24
  ## Endpoints
32
25
 
@@ -48,9 +41,9 @@ After installation, the plugin exposes:
48
41
  npm install @vivinkv28/strapi-2fa-admin-plugin
49
42
  ```
50
43
 
51
- ## Step 1: Enable The Plugin
44
+ ## Enable The Plugin
52
45
 
53
- In your Strapi project, update `config/plugins.ts`:
46
+ Update `config/plugins.ts`:
54
47
 
55
48
  ```ts
56
49
  import type { Core } from "@strapi/strapi";
@@ -81,19 +74,17 @@ const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin =>
81
74
  export default config;
82
75
  ```
83
76
 
84
- ## Step 2: Configure Email
77
+ ## Email Provider
85
78
 
86
79
  This plugin sends OTP emails through Strapi's email plugin.
87
80
 
88
- Your project must have a working email provider configuration in `config/plugins.ts`.
89
-
90
- If email is not configured, login will fail when OTP delivery is attempted.
81
+ If your email provider is not configured correctly, login will fail when OTP delivery is attempted.
91
82
 
92
- ## Step 3: Make Sure Server/Proxy Settings Are Correct
83
+ ## Recommended Server Settings
93
84
 
94
- If your Strapi app runs behind a proxy, configure `config/server.ts` correctly so admin cookies work as expected.
85
+ If your Strapi app runs behind a proxy, make sure `config/server.ts` is configured correctly so admin cookies work as expected.
95
86
 
96
- Typical example:
87
+ Example:
97
88
 
98
89
  ```ts
99
90
  import type { Core } from "@strapi/strapi";
@@ -111,416 +102,59 @@ const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server =>
111
102
  export default config;
112
103
  ```
113
104
 
114
- ## Step 4: Add Admin Login UI Integration
115
-
116
- This package does not inject the login UI automatically.
117
-
118
- Your Strapi project must customize the admin login flow so it works like this:
105
+ ## Host Patch Files Included In The Package
119
106
 
120
- 1. admin enters email and password
121
- 2. frontend calls `POST /api/admin-2fa/login`
122
- 3. frontend shows OTP screen
123
- 4. admin enters OTP
124
- 5. frontend calls `POST /api/admin-2fa/verify`
125
- 6. optional resend button calls `POST /api/admin-2fa/resend`
126
-
127
- ## Recommended Project Structure For The Admin Patch
128
-
129
- In your Strapi project, keep your admin patch files in your own `scripts` folder:
130
-
131
- ```text
132
- your-project/
133
- scripts/
134
- strapi-admin-2fa-patch/
135
- services/
136
- auth.js
137
- auth.mjs
138
- pages/
139
- Auth/
140
- components/
141
- Login.js
142
- Login.mjs
143
- apply-strapi-admin-2fa-patch.js
144
- ```
145
-
146
- This keeps your admin customizations reproducible and easy to reapply after `npm install`.
147
-
148
- ## Full Host Patch Source Included In This Package
149
-
150
- This npm package now includes the ready-to-copy host-side admin patch source directly inside the package under:
107
+ This package includes the admin UI patch source under:
151
108
 
152
109
  ```text
153
110
  host-patch/
154
111
  apply-strapi-admin-2fa-patch.js
155
112
  strapi-admin-2fa-patch/
156
- services/
157
- auth.js
158
- auth.mjs
159
- pages/
160
- Auth/
161
- components/
162
- Login.js
163
- Login.mjs
164
- ```
165
-
166
- Included files:
167
-
168
- - `host-patch/apply-strapi-admin-2fa-patch.js`
169
- - `host-patch/strapi-admin-2fa-patch/services/auth.js`
170
- - `host-patch/strapi-admin-2fa-patch/services/auth.mjs`
171
- - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
172
- - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
173
-
174
- These files are the exact host-side patch source for the admin login UI, OTP screen, and patch script, bundled in the npm package itself so you do not need a separate integration document.
175
-
176
- ## Step 5: Patch The Strapi Admin Auth Service
177
-
178
- Create these files in your Strapi project:
179
-
180
- - `scripts/strapi-admin-2fa-patch/services/auth.js`
181
- - `scripts/strapi-admin-2fa-patch/services/auth.mjs`
182
-
183
- Start from the corresponding Strapi admin auth service file and add the OTP mutations below.
184
-
185
- ### Add these mutations
186
-
187
- ```js
188
- adminLoginWithOtp: builder.mutation({
189
- query: (body) => ({
190
- method: 'POST',
191
- url: '/api/admin-2fa/login',
192
- data: body,
193
- }),
194
- transformResponse(res) {
195
- return res.data;
196
- },
197
- }),
198
-
199
- verifyAdminLoginOtp: builder.mutation({
200
- query: (body) => ({
201
- method: 'POST',
202
- url: '/api/admin-2fa/verify',
203
- data: body,
204
- }),
205
- transformResponse(res) {
206
- return res.data;
207
- },
208
- invalidatesTags: ['Me'],
209
- }),
210
-
211
- resendAdminLoginOtp: builder.mutation({
212
- query: (body) => ({
213
- method: 'POST',
214
- url: '/api/admin-2fa/resend',
215
- data: body,
216
- }),
217
- transformResponse(res) {
218
- return res.data;
219
- },
220
- }),
113
+ services/
114
+ auth.js
115
+ auth.mjs
116
+ pages/
117
+ Auth/
118
+ components/
119
+ Login.js
120
+ Login.mjs
221
121
  ```
222
122
 
223
- ### Export these hooks
123
+ These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
224
124
 
225
- ```js
226
- const {
227
- useAdminLoginWithOtpMutation,
228
- useVerifyAdminLoginOtpMutation,
229
- useResendAdminLoginOtpMutation,
230
- } = authService;
231
- ```
232
-
233
- ## Step 6: Patch The Strapi Login Screen
125
+ ## What The UI Patch Does
234
126
 
235
- Create these files in your Strapi project:
127
+ The bundled host patch changes the Strapi admin login flow to:
236
128
 
237
- - `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
238
- - `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
129
+ 1. enter admin email and password
130
+ 2. call `POST /api/admin-2fa/login`
131
+ 3. show an OTP screen
132
+ 4. enter the OTP code
133
+ 5. call `POST /api/admin-2fa/verify`
134
+ 6. optionally resend OTP with `POST /api/admin-2fa/resend`
239
135
 
240
- This component must replace the normal one-step login with a two-step state:
136
+ The included login patch UI contains:
241
137
 
242
- - credentials step
138
+ - email and password step
243
139
  - OTP step
244
-
245
- ### Minimum state you need
246
-
247
- ```js
248
- const [otpStep, setOtpStep] = React.useState(null);
249
- const [apiError, setApiError] = React.useState();
250
-
251
- const [adminLoginWithOtp, { isLoading: isLoggingIn }] = useAdminLoginWithOtpMutation();
252
- const [verifyAdminLoginOtp, { isLoading: isVerifyingOtp }] = useVerifyAdminLoginOtpMutation();
253
- const [resendAdminLoginOtp, { isLoading: isResendingOtp }] = useResendAdminLoginOtpMutation();
254
- ```
255
-
256
- ### Credentials submit handler
257
-
258
- ```js
259
- const handleLogin = async (body) => {
260
- setApiError(undefined);
261
-
262
- const res = await adminLoginWithOtp({
263
- ...body,
264
- deviceId: crypto.randomUUID(),
265
- });
266
-
267
- if ('error' in res) {
268
- setApiError(res.error.message ?? 'Something went wrong');
269
- return;
270
- }
271
-
272
- setOtpStep({
273
- challengeId: res.data.challengeId,
274
- expiresAt: res.data.expiresAt,
275
- maskedEmail: res.data.maskedEmail,
276
- rememberMe: body.rememberMe,
277
- });
278
- };
279
- ```
280
-
281
- ### OTP verify handler
282
-
283
- ```js
284
- const handleVerifyOtp = async ({ code }) => {
285
- if (!otpStep) return;
286
-
287
- setApiError(undefined);
288
-
289
- const res = await verifyAdminLoginOtp({
290
- challengeId: otpStep.challengeId,
291
- code,
292
- });
293
-
294
- if ('error' in res) {
295
- setApiError(res.error.message ?? 'Something went wrong');
296
- return;
297
- }
298
-
299
- dispatch(
300
- login({
301
- token: res.data.token,
302
- persist: otpStep.rememberMe,
303
- })
304
- );
305
-
306
- navigate('/');
307
- };
308
- ```
309
-
310
- ### OTP resend handler
311
-
312
- ```js
313
- const handleResendOtp = async () => {
314
- if (!otpStep) return;
315
-
316
- setApiError(undefined);
317
-
318
- const res = await resendAdminLoginOtp({
319
- challengeId: otpStep.challengeId,
320
- });
321
-
322
- if ('error' in res) {
323
- setApiError(res.error.message ?? 'Something went wrong');
324
- return;
325
- }
326
-
327
- setOtpStep((current) =>
328
- current
329
- ? {
330
- ...current,
331
- expiresAt: res.data.expiresAt,
332
- maskedEmail: res.data.maskedEmail,
333
- }
334
- : current
335
- );
336
- };
337
- ```
338
-
339
- ### OTP input handling
340
-
341
- At minimum:
342
-
343
- ```js
344
- const OTP_LENGTH = 6;
345
- const sanitizeOtp = (value = '') => value.replace(/\D/g, '').slice(0, OTP_LENGTH);
346
- ```
347
-
348
- The working implementation used in the companion project includes:
349
-
350
- - 6 digit boxes
140
+ - 6-digit OTP box UI
351
141
  - paste support
352
- - backspace handling
353
- - auto focus
354
- - inline error state
355
-
356
- ## Exact Admin UI Pieces You Need
142
+ - backspace and focus handling
143
+ - resend OTP button
144
+ - back button
145
+ - inline error handling
357
146
 
358
- If you want the admin OTP screen to look and behave like the working portfolio backend, these are the UI pieces that must exist in your patched `Login` component:
359
-
360
- ### 1. Shared OTP constants and helpers
361
-
362
- Use:
363
-
364
- ```js
365
- const OTP_LENGTH = 6;
366
- const sanitizeOtp = (value = '') => value.replace(/\D/g, '').slice(0, OTP_LENGTH);
367
- const createOtpDigits = (value = '') =>
368
- Array.from({ length: OTP_LENGTH }, (_, index) => value[index] ?? '');
369
- ```
147
+ ## How To Use The Included Host Patch
370
148
 
371
- Why this matters:
149
+ Copy these files from the package into your Strapi project:
372
150
 
373
- - forces numeric-only OTP input
374
- - keeps the code fixed to 6 digits
375
- - makes the 6-box UI easy to control
151
+ - `host-patch/apply-strapi-admin-2fa-patch.js` -> `scripts/apply-strapi-admin-2fa-patch.js`
152
+ - `host-patch/strapi-admin-2fa-patch/services/auth.js` -> `scripts/strapi-admin-2fa-patch/services/auth.js`
153
+ - `host-patch/strapi-admin-2fa-patch/services/auth.mjs` -> `scripts/strapi-admin-2fa-patch/services/auth.mjs`
154
+ - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.js` -> `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
155
+ - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs` -> `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
376
156
 
377
- ### 2. Two screen states inside one login component
378
-
379
- Your login screen should switch between:
380
-
381
- - credentials screen
382
- - OTP verification screen
383
-
384
- The state shape used in the working backend is:
385
-
386
- ```js
387
- const [apiError, setApiError] = React.useState();
388
- const [otpStep, setOtpStep] = React.useState(null);
389
- ```
390
-
391
- `otpStep` stores:
392
-
393
- - `challengeId`
394
- - `expiresAt`
395
- - `maskedEmail`
396
- - `rememberMe`
397
-
398
- When `otpStep` is `null`, show email/password fields.
399
-
400
- When `otpStep` exists, show the OTP UI.
401
-
402
- ### 3. Patched auth hooks
403
-
404
- Your patched auth service must export:
405
-
406
- ```js
407
- const {
408
- useAdminLoginWithOtpMutation,
409
- useVerifyAdminLoginOtpMutation,
410
- useResendAdminLoginOtpMutation,
411
- } = authService;
412
- ```
413
-
414
- Then the patched login screen uses:
415
-
416
- ```js
417
- const [adminLoginWithOtp, { isLoading: isLoggingIn }] = useAdminLoginWithOtpMutation();
418
- const [verifyAdminLoginOtp, { isLoading: isVerifyingOtp }] =
419
- useVerifyAdminLoginOtpMutation();
420
- const [resendAdminLoginOtp, { isLoading: isResendingOtp }] =
421
- useResendAdminLoginOtpMutation();
422
- ```
423
-
424
- ### 4. Credentials form step
425
-
426
- In the first step, keep the normal Strapi fields:
427
-
428
- - `email`
429
- - `password`
430
- - `rememberMe`
431
-
432
- On submit:
433
-
434
- 1. call the OTP login endpoint
435
- 2. do not create a Strapi session yet
436
- 3. store the returned `challengeId`, `expiresAt`, and `maskedEmail`
437
- 4. switch to the OTP step
438
-
439
- ### 5. OTP visual layout
440
-
441
- The working backend UI uses:
442
-
443
- - a heading such as `Enter your OTP code`
444
- - a subtitle showing the masked email
445
- - an expiry message
446
- - a centered 6-digit input row
447
- - a primary `Verify OTP` button
448
- - a secondary `Back` button
449
- - a tertiary `Resend OTP` button
450
- - an inline error message area
451
-
452
- ### 6. OTP input box behavior
453
-
454
- The working UI is not a single text input. It is a 6-box OTP component with:
455
-
456
- - one visible input per digit
457
- - automatic focus movement
458
- - paste support for full OTP values
459
- - backspace support to move backward
460
- - left/right arrow key navigation
461
- - red error styling when validation fails
462
-
463
- That behavior lives inside a dedicated `OtpField` component inside the patched `Login.js` / `Login.mjs` files.
464
-
465
- ### 7. Verify flow
466
-
467
- On OTP submit:
468
-
469
- 1. call the verify endpoint with `challengeId` and `code`
470
- 2. if it succeeds, dispatch the normal Strapi admin login action
471
- 3. navigate to `/` or the `redirectTo` query target
472
-
473
- ### 8. Resend flow
474
-
475
- On resend:
476
-
477
- 1. call the resend endpoint with `challengeId`
478
- 2. keep the user on the OTP screen
479
- 3. update `expiresAt`
480
- 4. update `maskedEmail` if needed
481
- 5. show a success notification
482
-
483
- ### 9. Back button behavior
484
-
485
- The working backend keeps a `Back` button on the OTP screen that simply resets:
486
-
487
- ```js
488
- setOtpStep(null);
489
- ```
490
-
491
- That sends the admin back to the email/password screen without refreshing the page.
492
-
493
- ### 10. Why both `.js` and `.mjs` files are needed
494
-
495
- Strapi ships both CommonJS and ESM admin build files inside `node_modules/@strapi/admin/...`.
496
-
497
- To keep the admin patch stable, the same logic should be copied into both:
498
-
499
- - `Login.js`
500
- - `Login.mjs`
501
- - `auth.js`
502
- - `auth.mjs`
503
-
504
- If you patch only one side, the admin build can drift or break depending on how Strapi resolves the files.
505
-
506
- ## Step 7: Add A Patch Apply Script
507
-
508
- Create:
509
-
510
- - `scripts/apply-strapi-admin-2fa-patch.js`
511
-
512
- This script should:
513
-
514
- 1. copy `scripts/strapi-admin-2fa-patch/services/auth.js`
515
- 2. copy `scripts/strapi-admin-2fa-patch/services/auth.mjs`
516
- 3. copy `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
517
- 4. copy `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
518
- 5. overwrite the matching files in `node_modules/@strapi/admin/...`
519
- 6. clear stale Strapi admin cache directories
520
-
521
- ## Step 8: Wire The Patch Script In `package.json`
522
-
523
- In your Strapi project `package.json`, add:
157
+ Then wire the patch script in your Strapi project's `package.json`:
524
158
 
525
159
  ```json
526
160
  {
@@ -533,12 +167,6 @@ In your Strapi project `package.json`, add:
533
167
  }
534
168
  ```
535
169
 
536
- This ensures the login patch is reapplied:
537
-
538
- - after dependency install
539
- - before build
540
- - before dev
541
-
542
170
  ## Request Flow
543
171
 
544
172
  ### Login
@@ -548,8 +176,6 @@ POST /api/admin-2fa/login
548
176
  Content-Type: application/json
549
177
  ```
550
178
 
551
- Example body:
552
-
553
179
  ```json
554
180
  {
555
181
  "email": "admin@example.com",
@@ -559,19 +185,6 @@ Example body:
559
185
  }
560
186
  ```
561
187
 
562
- Example response:
563
-
564
- ```json
565
- {
566
- "data": {
567
- "challengeId": "***",
568
- "expiresAt": "2026-04-05T18:30:00.000Z",
569
- "maskedEmail": "admin@example.com",
570
- "rememberMe": true
571
- }
572
- }
573
- ```
574
-
575
188
  ### Verify
576
189
 
577
190
  ```http
@@ -579,8 +192,6 @@ POST /api/admin-2fa/verify
579
192
  Content-Type: application/json
580
193
  ```
581
194
 
582
- Example body:
583
-
584
195
  ```json
585
196
  {
586
197
  "challengeId": "***",
@@ -595,8 +206,6 @@ POST /api/admin-2fa/resend
595
206
  Content-Type: application/json
596
207
  ```
597
208
 
598
- Example body:
599
-
600
209
  ```json
601
210
  {
602
211
  "challengeId": "***"
@@ -622,34 +231,17 @@ ADMIN_OTP_RESEND_EMAIL_LIMIT=5
622
231
  ADMIN_OTP_DEBUG_TIMINGS=false
623
232
  ```
624
233
 
625
- ## Testing Checklist
234
+ ## Test Checklist
626
235
 
627
- After setup, test these cases:
236
+ After setup, test:
628
237
 
629
238
  1. correct email/password shows OTP screen
630
- 2. correct OTP logs into admin successfully
239
+ 2. correct OTP logs in successfully
631
240
  3. resend OTP works
632
241
  4. invalid OTP shows an error
633
242
  5. expired OTP restarts the flow properly
634
- 6. wrong email/password still fails safely
635
-
636
- ## Admin UI Documentation Summary
637
-
638
- If you want your project README or integration guide to document the admin UI clearly, include these sections in order:
639
-
640
- 1. exact patch folder structure
641
- 2. exact file names
642
- 3. purpose of each file
643
- 4. auth service mutations to add
644
- 5. login component state you need
645
- 6. OTP UI behavior details
646
- 7. patch apply script behavior
647
- 8. package.json hooks
648
- 9. request/response examples
649
- 10. testing checklist
243
+ 6. wrong email/password fails safely
650
244
 
651
- ## Production Notes
245
+ ## Security Note
652
246
 
653
- - This improves admin security, but email OTP is still weaker than TOTP or WebAuthn.
654
- - If the admin email account is compromised, the second factor can still be bypassed.
655
- - For stronger future versions, consider TOTP, backup codes, trusted devices, or passkeys.
247
+ Email OTP improves admin security, but it is still weaker than TOTP, WebAuthn, or passkeys.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivinkv28/strapi-2fa-admin-plugin",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Reusable Strapi admin 2FA plugin",
5
5
  "type": "commonjs",
6
6
  "keywords": [