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

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 +66 -460
  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,73 @@ 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
113
+ services/
114
+ auth.js
115
+ auth.mjs
116
+ pages/
117
+ Auth/
118
+ components/
119
+ Login.js
120
+ Login.mjs
164
121
  ```
165
122
 
166
- Included files:
123
+ These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
167
124
 
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`
125
+ If you want to view the same host patch files on GitHub, use:
173
126
 
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.
127
+ - [host-patch folder](https://github.com/vivinkv6/strapi-admin-2fa-plugin/tree/master/host-patch)
128
+ - [apply-strapi-admin-2fa-patch.js](https://github.com/vivinkv6/strapi-admin-2fa-plugin/blob/master/host-patch/apply-strapi-admin-2fa-patch.js)
129
+ - [services/auth.js](https://github.com/vivinkv6/strapi-admin-2fa-plugin/blob/master/host-patch/strapi-admin-2fa-patch/services/auth.js)
130
+ - [services/auth.mjs](https://github.com/vivinkv6/strapi-admin-2fa-plugin/blob/master/host-patch/strapi-admin-2fa-patch/services/auth.mjs)
131
+ - [pages/Auth/components/Login.js](https://github.com/vivinkv6/strapi-admin-2fa-plugin/blob/master/host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.js)
132
+ - [pages/Auth/components/Login.mjs](https://github.com/vivinkv6/strapi-admin-2fa-plugin/blob/master/host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs)
175
133
 
176
- ## Step 5: Patch The Strapi Admin Auth Service
134
+ That gives users both options:
177
135
 
178
- Create these files in your Strapi project:
136
+ - install from npm and copy the bundled files
137
+ - inspect the admin UI source directly on GitHub before integrating it
179
138
 
180
- - `scripts/strapi-admin-2fa-patch/services/auth.js`
181
- - `scripts/strapi-admin-2fa-patch/services/auth.mjs`
139
+ ## What The UI Patch Does
182
140
 
183
- Start from the corresponding Strapi admin auth service file and add the OTP mutations below.
141
+ The bundled host patch changes the Strapi admin login flow to:
184
142
 
185
- ### Add these mutations
143
+ 1. enter admin email and password
144
+ 2. call `POST /api/admin-2fa/login`
145
+ 3. show an OTP screen
146
+ 4. enter the OTP code
147
+ 5. call `POST /api/admin-2fa/verify`
148
+ 6. optionally resend OTP with `POST /api/admin-2fa/resend`
186
149
 
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
- }),
221
- ```
150
+ The included login patch UI contains:
222
151
 
223
- ### Export these hooks
224
-
225
- ```js
226
- const {
227
- useAdminLoginWithOtpMutation,
228
- useVerifyAdminLoginOtpMutation,
229
- useResendAdminLoginOtpMutation,
230
- } = authService;
231
- ```
232
-
233
- ## Step 6: Patch The Strapi Login Screen
234
-
235
- Create these files in your Strapi project:
236
-
237
- - `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
238
- - `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
239
-
240
- This component must replace the normal one-step login with a two-step state:
241
-
242
- - credentials step
152
+ - email and password step
243
153
  - 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
154
+ - 6-digit OTP box UI
351
155
  - paste support
352
- - backspace handling
353
- - auto focus
354
- - inline error state
355
-
356
- ## Exact Admin UI Pieces You Need
357
-
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
- ```
370
-
371
- Why this matters:
156
+ - backspace and focus handling
157
+ - resend OTP button
158
+ - back button
159
+ - inline error handling
372
160
 
373
- - forces numeric-only OTP input
374
- - keeps the code fixed to 6 digits
375
- - makes the 6-box UI easy to control
161
+ ## How To Use The Included Host Patch
376
162
 
377
- ### 2. Two screen states inside one login component
163
+ Copy these files from the package into your Strapi project:
378
164
 
379
- Your login screen should switch between:
165
+ - `host-patch/apply-strapi-admin-2fa-patch.js` -> `scripts/apply-strapi-admin-2fa-patch.js`
166
+ - `host-patch/strapi-admin-2fa-patch/services/auth.js` -> `scripts/strapi-admin-2fa-patch/services/auth.js`
167
+ - `host-patch/strapi-admin-2fa-patch/services/auth.mjs` -> `scripts/strapi-admin-2fa-patch/services/auth.mjs`
168
+ - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.js` -> `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
169
+ - `host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs` -> `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
380
170
 
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:
171
+ Then wire the patch script in your Strapi project's `package.json`:
524
172
 
525
173
  ```json
526
174
  {
@@ -533,12 +181,6 @@ In your Strapi project `package.json`, add:
533
181
  }
534
182
  ```
535
183
 
536
- This ensures the login patch is reapplied:
537
-
538
- - after dependency install
539
- - before build
540
- - before dev
541
-
542
184
  ## Request Flow
543
185
 
544
186
  ### Login
@@ -548,8 +190,6 @@ POST /api/admin-2fa/login
548
190
  Content-Type: application/json
549
191
  ```
550
192
 
551
- Example body:
552
-
553
193
  ```json
554
194
  {
555
195
  "email": "admin@example.com",
@@ -559,19 +199,6 @@ Example body:
559
199
  }
560
200
  ```
561
201
 
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
202
  ### Verify
576
203
 
577
204
  ```http
@@ -579,8 +206,6 @@ POST /api/admin-2fa/verify
579
206
  Content-Type: application/json
580
207
  ```
581
208
 
582
- Example body:
583
-
584
209
  ```json
585
210
  {
586
211
  "challengeId": "***",
@@ -595,8 +220,6 @@ POST /api/admin-2fa/resend
595
220
  Content-Type: application/json
596
221
  ```
597
222
 
598
- Example body:
599
-
600
223
  ```json
601
224
  {
602
225
  "challengeId": "***"
@@ -622,34 +245,17 @@ ADMIN_OTP_RESEND_EMAIL_LIMIT=5
622
245
  ADMIN_OTP_DEBUG_TIMINGS=false
623
246
  ```
624
247
 
625
- ## Testing Checklist
248
+ ## Test Checklist
626
249
 
627
- After setup, test these cases:
250
+ After setup, test:
628
251
 
629
252
  1. correct email/password shows OTP screen
630
- 2. correct OTP logs into admin successfully
253
+ 2. correct OTP logs in successfully
631
254
  3. resend OTP works
632
255
  4. invalid OTP shows an error
633
256
  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
257
+ 6. wrong email/password fails safely
650
258
 
651
- ## Production Notes
259
+ ## Security Note
652
260
 
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.
261
+ 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.10",
4
4
  "description": "Reusable Strapi admin 2FA plugin",
5
5
  "type": "commonjs",
6
6
  "keywords": [