@vivinkv28/strapi-2fa-admin-plugin 0.1.6 → 0.1.8
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.
- package/README.md +190 -58
- package/host-patch/apply-strapi-admin-2fa-patch.js +115 -0
- package/host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.js +509 -0
- package/host-patch/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs +487 -0
- package/host-patch/strapi-admin-2fa-patch/services/auth.js +245 -0
- package/host-patch/strapi-admin-2fa-patch/services/auth.mjs +217 -0
- package/package.json +3 -2
- package/docs/INTEGRATION.md +0 -246
package/README.md
CHANGED
|
@@ -145,6 +145,34 @@ your-project/
|
|
|
145
145
|
|
|
146
146
|
This keeps your admin customizations reproducible and easy to reapply after `npm install`.
|
|
147
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:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
host-patch/
|
|
154
|
+
apply-strapi-admin-2fa-patch.js
|
|
155
|
+
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
|
+
|
|
148
176
|
## Step 5: Patch The Strapi Admin Auth Service
|
|
149
177
|
|
|
150
178
|
Create these files in your Strapi project:
|
|
@@ -325,6 +353,156 @@ The working implementation used in the companion project includes:
|
|
|
325
353
|
- auto focus
|
|
326
354
|
- inline error state
|
|
327
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:
|
|
372
|
+
|
|
373
|
+
- forces numeric-only OTP input
|
|
374
|
+
- keeps the code fixed to 6 digits
|
|
375
|
+
- makes the 6-box UI easy to control
|
|
376
|
+
|
|
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
|
+
|
|
328
506
|
## Step 7: Add A Patch Apply Script
|
|
329
507
|
|
|
330
508
|
Create:
|
|
@@ -455,66 +633,20 @@ After setup, test these cases:
|
|
|
455
633
|
5. expired OTP restarts the flow properly
|
|
456
634
|
6. wrong email/password still fails safely
|
|
457
635
|
|
|
458
|
-
##
|
|
459
|
-
|
|
460
|
-
Main plugin files:
|
|
461
|
-
|
|
462
|
-
```text
|
|
463
|
-
admin/src/index.js
|
|
464
|
-
server/src/index.js
|
|
465
|
-
server/src/routes/index.js
|
|
466
|
-
server/src/controllers/auth.js
|
|
467
|
-
server/src/services/auth.js
|
|
468
|
-
server/src/utils/strapi-session-auth.js
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
Responsibilities:
|
|
472
|
-
|
|
473
|
-
- `admin/src/index.js`
|
|
474
|
-
Minimal admin plugin stub required by the Strapi Plugin SDK.
|
|
475
|
-
|
|
476
|
-
- `server/src/routes/index.js`
|
|
477
|
-
Declares `/login`, `/verify`, and `/resend`.
|
|
478
|
-
|
|
479
|
-
- `server/src/controllers/auth.js`
|
|
480
|
-
Extracts request data, resolves client IP, sets refresh cookies after verification.
|
|
481
|
-
|
|
482
|
-
- `server/src/services/auth.js`
|
|
483
|
-
Core OTP engine: credentials, challenge lifecycle, rate limits, email sending, and session creation.
|
|
484
|
-
|
|
485
|
-
- `server/src/utils/strapi-session-auth.js`
|
|
486
|
-
Resolves Strapi's internal admin session helper at runtime.
|
|
487
|
-
|
|
488
|
-
## Deeper Docs
|
|
489
|
-
|
|
490
|
-
If you want more detail from the repository:
|
|
491
|
-
|
|
492
|
-
- `docs/INTEGRATION.md`
|
|
493
|
-
- `docs/ARCHITECTURE.md`
|
|
494
|
-
- `admin-screen.md`
|
|
495
|
-
|
|
496
|
-
## Development
|
|
497
|
-
|
|
498
|
-
```bash
|
|
499
|
-
npm install
|
|
500
|
-
npm run build
|
|
501
|
-
```
|
|
502
|
-
|
|
503
|
-
Useful commands:
|
|
504
|
-
|
|
505
|
-
- `npm run build`
|
|
506
|
-
- `npm run watch`
|
|
507
|
-
- `npm run watch:link`
|
|
508
|
-
- `npm run verify`
|
|
636
|
+
## Admin UI Documentation Summary
|
|
509
637
|
|
|
510
|
-
|
|
638
|
+
If you want your project README or integration guide to document the admin UI clearly, include these sections in order:
|
|
511
639
|
|
|
512
|
-
1.
|
|
513
|
-
2.
|
|
514
|
-
3.
|
|
515
|
-
4.
|
|
516
|
-
5.
|
|
517
|
-
6.
|
|
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
|
|
518
650
|
|
|
519
651
|
## Production Notes
|
|
520
652
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const rootDir = process.cwd();
|
|
5
|
+
|
|
6
|
+
const patchFiles = [
|
|
7
|
+
{
|
|
8
|
+
source: path.join(
|
|
9
|
+
rootDir,
|
|
10
|
+
"scripts",
|
|
11
|
+
"strapi-admin-2fa-patch",
|
|
12
|
+
"pages",
|
|
13
|
+
"Auth",
|
|
14
|
+
"components",
|
|
15
|
+
"Login.js"
|
|
16
|
+
),
|
|
17
|
+
target: path.join(
|
|
18
|
+
rootDir,
|
|
19
|
+
"node_modules",
|
|
20
|
+
"@strapi",
|
|
21
|
+
"admin",
|
|
22
|
+
"dist",
|
|
23
|
+
"admin",
|
|
24
|
+
"admin",
|
|
25
|
+
"src",
|
|
26
|
+
"pages",
|
|
27
|
+
"Auth",
|
|
28
|
+
"components",
|
|
29
|
+
"Login.js"
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
source: path.join(
|
|
34
|
+
rootDir,
|
|
35
|
+
"scripts",
|
|
36
|
+
"strapi-admin-2fa-patch",
|
|
37
|
+
"pages",
|
|
38
|
+
"Auth",
|
|
39
|
+
"components",
|
|
40
|
+
"Login.mjs"
|
|
41
|
+
),
|
|
42
|
+
target: path.join(
|
|
43
|
+
rootDir,
|
|
44
|
+
"node_modules",
|
|
45
|
+
"@strapi",
|
|
46
|
+
"admin",
|
|
47
|
+
"dist",
|
|
48
|
+
"admin",
|
|
49
|
+
"admin",
|
|
50
|
+
"src",
|
|
51
|
+
"pages",
|
|
52
|
+
"Auth",
|
|
53
|
+
"components",
|
|
54
|
+
"Login.mjs"
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
source: path.join(rootDir, "scripts", "strapi-admin-2fa-patch", "services", "auth.js"),
|
|
59
|
+
target: path.join(
|
|
60
|
+
rootDir,
|
|
61
|
+
"node_modules",
|
|
62
|
+
"@strapi",
|
|
63
|
+
"admin",
|
|
64
|
+
"dist",
|
|
65
|
+
"admin",
|
|
66
|
+
"admin",
|
|
67
|
+
"src",
|
|
68
|
+
"services",
|
|
69
|
+
"auth.js"
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
source: path.join(rootDir, "scripts", "strapi-admin-2fa-patch", "services", "auth.mjs"),
|
|
74
|
+
target: path.join(
|
|
75
|
+
rootDir,
|
|
76
|
+
"node_modules",
|
|
77
|
+
"@strapi",
|
|
78
|
+
"admin",
|
|
79
|
+
"dist",
|
|
80
|
+
"admin",
|
|
81
|
+
"admin",
|
|
82
|
+
"src",
|
|
83
|
+
"services",
|
|
84
|
+
"auth.mjs"
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const generatedCacheDirs = [
|
|
90
|
+
path.join(rootDir, "node_modules", ".strapi", "vite"),
|
|
91
|
+
path.join(rootDir, ".strapi", "client"),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const missing = patchFiles.find(
|
|
95
|
+
({ source, target }) => !fs.existsSync(source) || !fs.existsSync(target)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (missing) {
|
|
99
|
+
console.warn("[admin-2fa-patch] Skipping Strapi admin patch because a source or target file is missing.");
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const { source, target } of patchFiles) {
|
|
104
|
+
fs.copyFileSync(source, target);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const generatedDir of generatedCacheDirs) {
|
|
108
|
+
if (fs.existsSync(generatedDir)) {
|
|
109
|
+
fs.rmSync(generatedDir, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log(
|
|
114
|
+
"[admin-2fa-patch] Applied Strapi admin OTP login patch and cleared Strapi admin caches."
|
|
115
|
+
);
|