@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.
- package/README.md +66 -460
- 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,73 @@ 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
|
-
|
|
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
|
-
|
|
123
|
+
These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
|
|
167
124
|
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
That gives users both options:
|
|
177
135
|
|
|
178
|
-
|
|
136
|
+
- install from npm and copy the bundled files
|
|
137
|
+
- inspect the admin UI source directly on GitHub before integrating it
|
|
179
138
|
|
|
180
|
-
|
|
181
|
-
- `scripts/strapi-admin-2fa-patch/services/auth.mjs`
|
|
139
|
+
## What The UI Patch Does
|
|
182
140
|
|
|
183
|
-
|
|
141
|
+
The bundled host patch changes the Strapi admin login flow to:
|
|
184
142
|
|
|
185
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
354
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
+
Copy these files from the package into your Strapi project:
|
|
378
164
|
|
|
379
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
248
|
+
## Test Checklist
|
|
626
249
|
|
|
627
|
-
After setup, test
|
|
250
|
+
After setup, test:
|
|
628
251
|
|
|
629
252
|
1. correct email/password shows OTP screen
|
|
630
|
-
2. correct OTP logs
|
|
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
|
|
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
|
-
##
|
|
259
|
+
## Security Note
|
|
652
260
|
|
|
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.
|
|
261
|
+
Email OTP improves admin security, but it is still weaker than TOTP, WebAuthn, or passkeys.
|