@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.
- package/README.md +56 -464
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
1
|
# @vivinkv28/strapi-2fa-admin-plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Strapi 5 plugin for OTP-based admin authentication.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This package adds the backend flow for:
|
|
6
6
|
|
|
7
|
-
- admin
|
|
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
|
-
##
|
|
13
|
+
## Important
|
|
15
14
|
|
|
16
|
-
This package
|
|
15
|
+
This package does not automatically replace the default Strapi admin login UI.
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
You need two parts:
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
1. this plugin for the backend OTP logic
|
|
20
|
+
2. a host-project patch for the Strapi admin login UI
|
|
21
21
|
|
|
22
|
-
|
|
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
|
-
##
|
|
44
|
+
## Enable The Plugin
|
|
52
45
|
|
|
53
|
-
|
|
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
|
-
##
|
|
77
|
+
## Email Provider
|
|
85
78
|
|
|
86
79
|
This plugin sends OTP emails through Strapi's email plugin.
|
|
87
80
|
|
|
88
|
-
|
|
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
|
-
##
|
|
83
|
+
## Recommended Server Settings
|
|
93
84
|
|
|
94
|
-
If your Strapi app runs behind a proxy,
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
123
|
+
These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
|
|
224
124
|
|
|
225
|
-
|
|
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
|
-
|
|
127
|
+
The bundled host patch changes the Strapi admin login flow to:
|
|
236
128
|
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
136
|
+
The included login patch UI contains:
|
|
241
137
|
|
|
242
|
-
-
|
|
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
|
-
-
|
|
354
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
Copy these files from the package into your Strapi project:
|
|
372
150
|
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
234
|
+
## Test Checklist
|
|
626
235
|
|
|
627
|
-
After setup, test
|
|
236
|
+
After setup, test:
|
|
628
237
|
|
|
629
238
|
1. correct email/password shows OTP screen
|
|
630
|
-
2. correct OTP logs
|
|
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
|
|
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
|
-
##
|
|
245
|
+
## Security Note
|
|
652
246
|
|
|
653
|
-
|
|
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.
|