@vivinkv28/strapi-2fa-admin-plugin 0.1.7 → 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 +59 -274
- 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
|
@@ -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,238 +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:
|
|
107
|
+
This package includes the admin UI patch source under:
|
|
130
108
|
|
|
131
109
|
```text
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
apply-strapi-admin-2fa-patch.js
|
|
110
|
+
host-patch/
|
|
111
|
+
apply-strapi-admin-2fa-patch.js
|
|
112
|
+
strapi-admin-2fa-patch/
|
|
113
|
+
services/
|
|
114
|
+
auth.js
|
|
115
|
+
auth.mjs
|
|
116
|
+
pages/
|
|
117
|
+
Auth/
|
|
118
|
+
components/
|
|
119
|
+
Login.js
|
|
120
|
+
Login.mjs
|
|
144
121
|
```
|
|
145
122
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
## Step 5: Patch The Strapi Admin Auth Service
|
|
149
|
-
|
|
150
|
-
Create these files in your Strapi project:
|
|
123
|
+
These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
|
|
151
124
|
|
|
152
|
-
|
|
153
|
-
- `scripts/strapi-admin-2fa-patch/services/auth.mjs`
|
|
125
|
+
## What The UI Patch Does
|
|
154
126
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### Add these mutations
|
|
158
|
-
|
|
159
|
-
```js
|
|
160
|
-
adminLoginWithOtp: builder.mutation({
|
|
161
|
-
query: (body) => ({
|
|
162
|
-
method: 'POST',
|
|
163
|
-
url: '/api/admin-2fa/login',
|
|
164
|
-
data: body,
|
|
165
|
-
}),
|
|
166
|
-
transformResponse(res) {
|
|
167
|
-
return res.data;
|
|
168
|
-
},
|
|
169
|
-
}),
|
|
170
|
-
|
|
171
|
-
verifyAdminLoginOtp: builder.mutation({
|
|
172
|
-
query: (body) => ({
|
|
173
|
-
method: 'POST',
|
|
174
|
-
url: '/api/admin-2fa/verify',
|
|
175
|
-
data: body,
|
|
176
|
-
}),
|
|
177
|
-
transformResponse(res) {
|
|
178
|
-
return res.data;
|
|
179
|
-
},
|
|
180
|
-
invalidatesTags: ['Me'],
|
|
181
|
-
}),
|
|
182
|
-
|
|
183
|
-
resendAdminLoginOtp: builder.mutation({
|
|
184
|
-
query: (body) => ({
|
|
185
|
-
method: 'POST',
|
|
186
|
-
url: '/api/admin-2fa/resend',
|
|
187
|
-
data: body,
|
|
188
|
-
}),
|
|
189
|
-
transformResponse(res) {
|
|
190
|
-
return res.data;
|
|
191
|
-
},
|
|
192
|
-
}),
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Export these hooks
|
|
196
|
-
|
|
197
|
-
```js
|
|
198
|
-
const {
|
|
199
|
-
useAdminLoginWithOtpMutation,
|
|
200
|
-
useVerifyAdminLoginOtpMutation,
|
|
201
|
-
useResendAdminLoginOtpMutation,
|
|
202
|
-
} = authService;
|
|
203
|
-
```
|
|
127
|
+
The bundled host patch changes the Strapi admin login flow to:
|
|
204
128
|
|
|
205
|
-
|
|
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`
|
|
206
135
|
|
|
207
|
-
|
|
136
|
+
The included login patch UI contains:
|
|
208
137
|
|
|
209
|
-
-
|
|
210
|
-
- `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
|
|
211
|
-
|
|
212
|
-
This component must replace the normal one-step login with a two-step state:
|
|
213
|
-
|
|
214
|
-
- credentials step
|
|
138
|
+
- email and password step
|
|
215
139
|
- OTP step
|
|
216
|
-
|
|
217
|
-
### Minimum state you need
|
|
218
|
-
|
|
219
|
-
```js
|
|
220
|
-
const [otpStep, setOtpStep] = React.useState(null);
|
|
221
|
-
const [apiError, setApiError] = React.useState();
|
|
222
|
-
|
|
223
|
-
const [adminLoginWithOtp, { isLoading: isLoggingIn }] = useAdminLoginWithOtpMutation();
|
|
224
|
-
const [verifyAdminLoginOtp, { isLoading: isVerifyingOtp }] = useVerifyAdminLoginOtpMutation();
|
|
225
|
-
const [resendAdminLoginOtp, { isLoading: isResendingOtp }] = useResendAdminLoginOtpMutation();
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Credentials submit handler
|
|
229
|
-
|
|
230
|
-
```js
|
|
231
|
-
const handleLogin = async (body) => {
|
|
232
|
-
setApiError(undefined);
|
|
233
|
-
|
|
234
|
-
const res = await adminLoginWithOtp({
|
|
235
|
-
...body,
|
|
236
|
-
deviceId: crypto.randomUUID(),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
if ('error' in res) {
|
|
240
|
-
setApiError(res.error.message ?? 'Something went wrong');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
setOtpStep({
|
|
245
|
-
challengeId: res.data.challengeId,
|
|
246
|
-
expiresAt: res.data.expiresAt,
|
|
247
|
-
maskedEmail: res.data.maskedEmail,
|
|
248
|
-
rememberMe: body.rememberMe,
|
|
249
|
-
});
|
|
250
|
-
};
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### OTP verify handler
|
|
254
|
-
|
|
255
|
-
```js
|
|
256
|
-
const handleVerifyOtp = async ({ code }) => {
|
|
257
|
-
if (!otpStep) return;
|
|
258
|
-
|
|
259
|
-
setApiError(undefined);
|
|
260
|
-
|
|
261
|
-
const res = await verifyAdminLoginOtp({
|
|
262
|
-
challengeId: otpStep.challengeId,
|
|
263
|
-
code,
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
if ('error' in res) {
|
|
267
|
-
setApiError(res.error.message ?? 'Something went wrong');
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
dispatch(
|
|
272
|
-
login({
|
|
273
|
-
token: res.data.token,
|
|
274
|
-
persist: otpStep.rememberMe,
|
|
275
|
-
})
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
navigate('/');
|
|
279
|
-
};
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### OTP resend handler
|
|
283
|
-
|
|
284
|
-
```js
|
|
285
|
-
const handleResendOtp = async () => {
|
|
286
|
-
if (!otpStep) return;
|
|
287
|
-
|
|
288
|
-
setApiError(undefined);
|
|
289
|
-
|
|
290
|
-
const res = await resendAdminLoginOtp({
|
|
291
|
-
challengeId: otpStep.challengeId,
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
if ('error' in res) {
|
|
295
|
-
setApiError(res.error.message ?? 'Something went wrong');
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
setOtpStep((current) =>
|
|
300
|
-
current
|
|
301
|
-
? {
|
|
302
|
-
...current,
|
|
303
|
-
expiresAt: res.data.expiresAt,
|
|
304
|
-
maskedEmail: res.data.maskedEmail,
|
|
305
|
-
}
|
|
306
|
-
: current
|
|
307
|
-
);
|
|
308
|
-
};
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### OTP input handling
|
|
312
|
-
|
|
313
|
-
At minimum:
|
|
314
|
-
|
|
315
|
-
```js
|
|
316
|
-
const OTP_LENGTH = 6;
|
|
317
|
-
const sanitizeOtp = (value = '') => value.replace(/\D/g, '').slice(0, OTP_LENGTH);
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
The working implementation used in the companion project includes:
|
|
321
|
-
|
|
322
|
-
- 6 digit boxes
|
|
140
|
+
- 6-digit OTP box UI
|
|
323
141
|
- paste support
|
|
324
|
-
- backspace handling
|
|
325
|
-
-
|
|
326
|
-
-
|
|
327
|
-
|
|
328
|
-
## Step 7: Add A Patch Apply Script
|
|
329
|
-
|
|
330
|
-
Create:
|
|
331
|
-
|
|
332
|
-
- `scripts/apply-strapi-admin-2fa-patch.js`
|
|
142
|
+
- backspace and focus handling
|
|
143
|
+
- resend OTP button
|
|
144
|
+
- back button
|
|
145
|
+
- inline error handling
|
|
333
146
|
|
|
334
|
-
|
|
147
|
+
## How To Use The Included Host Patch
|
|
335
148
|
|
|
336
|
-
|
|
337
|
-
2. copy `scripts/strapi-admin-2fa-patch/services/auth.mjs`
|
|
338
|
-
3. copy `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
|
|
339
|
-
4. copy `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.mjs`
|
|
340
|
-
5. overwrite the matching files in `node_modules/@strapi/admin/...`
|
|
341
|
-
6. clear stale Strapi admin cache directories
|
|
149
|
+
Copy these files from the package into your Strapi project:
|
|
342
150
|
|
|
343
|
-
|
|
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`
|
|
344
156
|
|
|
345
|
-
|
|
157
|
+
Then wire the patch script in your Strapi project's `package.json`:
|
|
346
158
|
|
|
347
159
|
```json
|
|
348
160
|
{
|
|
@@ -355,12 +167,6 @@ In your Strapi project `package.json`, add:
|
|
|
355
167
|
}
|
|
356
168
|
```
|
|
357
169
|
|
|
358
|
-
This ensures the login patch is reapplied:
|
|
359
|
-
|
|
360
|
-
- after dependency install
|
|
361
|
-
- before build
|
|
362
|
-
- before dev
|
|
363
|
-
|
|
364
170
|
## Request Flow
|
|
365
171
|
|
|
366
172
|
### Login
|
|
@@ -370,8 +176,6 @@ POST /api/admin-2fa/login
|
|
|
370
176
|
Content-Type: application/json
|
|
371
177
|
```
|
|
372
178
|
|
|
373
|
-
Example body:
|
|
374
|
-
|
|
375
179
|
```json
|
|
376
180
|
{
|
|
377
181
|
"email": "admin@example.com",
|
|
@@ -381,19 +185,6 @@ Example body:
|
|
|
381
185
|
}
|
|
382
186
|
```
|
|
383
187
|
|
|
384
|
-
Example response:
|
|
385
|
-
|
|
386
|
-
```json
|
|
387
|
-
{
|
|
388
|
-
"data": {
|
|
389
|
-
"challengeId": "***",
|
|
390
|
-
"expiresAt": "2026-04-05T18:30:00.000Z",
|
|
391
|
-
"maskedEmail": "admin@example.com",
|
|
392
|
-
"rememberMe": true
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
```
|
|
396
|
-
|
|
397
188
|
### Verify
|
|
398
189
|
|
|
399
190
|
```http
|
|
@@ -401,8 +192,6 @@ POST /api/admin-2fa/verify
|
|
|
401
192
|
Content-Type: application/json
|
|
402
193
|
```
|
|
403
194
|
|
|
404
|
-
Example body:
|
|
405
|
-
|
|
406
195
|
```json
|
|
407
196
|
{
|
|
408
197
|
"challengeId": "***",
|
|
@@ -417,8 +206,6 @@ POST /api/admin-2fa/resend
|
|
|
417
206
|
Content-Type: application/json
|
|
418
207
|
```
|
|
419
208
|
|
|
420
|
-
Example body:
|
|
421
|
-
|
|
422
209
|
```json
|
|
423
210
|
{
|
|
424
211
|
"challengeId": "***"
|
|
@@ -444,19 +231,17 @@ ADMIN_OTP_RESEND_EMAIL_LIMIT=5
|
|
|
444
231
|
ADMIN_OTP_DEBUG_TIMINGS=false
|
|
445
232
|
```
|
|
446
233
|
|
|
447
|
-
##
|
|
234
|
+
## Test Checklist
|
|
448
235
|
|
|
449
|
-
After setup, test
|
|
236
|
+
After setup, test:
|
|
450
237
|
|
|
451
238
|
1. correct email/password shows OTP screen
|
|
452
|
-
2. correct OTP logs
|
|
239
|
+
2. correct OTP logs in successfully
|
|
453
240
|
3. resend OTP works
|
|
454
241
|
4. invalid OTP shows an error
|
|
455
242
|
5. expired OTP restarts the flow properly
|
|
456
|
-
6. wrong email/password
|
|
243
|
+
6. wrong email/password fails safely
|
|
457
244
|
|
|
458
|
-
##
|
|
245
|
+
## Security Note
|
|
459
246
|
|
|
460
|
-
|
|
461
|
-
- If the admin email account is compromised, the second factor can still be bypassed.
|
|
462
|
-
- 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.
|
|
@@ -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
|
+
);
|