@vivinkv28/strapi-2fa-admin-plugin 0.1.4 → 0.1.6
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 +328 -110
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
# @vivinkv28/strapi-2fa-admin-plugin
|
|
2
2
|
|
|
3
|
-
`@vivinkv28/strapi-2fa-admin-plugin` is a Strapi 5 plugin that
|
|
3
|
+
`@vivinkv28/strapi-2fa-admin-plugin` is a Strapi 5 plugin that adds the backend side of an OTP-based admin login flow.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
It gives your Strapi project:
|
|
6
6
|
|
|
7
|
-
- admin credential validation
|
|
8
|
-
- OTP challenge
|
|
7
|
+
- admin credential validation before OTP
|
|
8
|
+
- OTP challenge creation
|
|
9
9
|
- OTP resend and verification
|
|
10
10
|
- rate limiting for login, verify, and resend
|
|
11
11
|
- OTP delivery through Strapi's email plugin
|
|
12
|
-
- final Strapi admin session creation after OTP verification
|
|
12
|
+
- final Strapi admin session creation only after OTP verification
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## What This Package Is
|
|
15
15
|
|
|
16
|
-
This package is a backend/admin-auth
|
|
16
|
+
This package is a **backend/admin-auth plugin**.
|
|
17
17
|
|
|
18
|
-
It does **not** replace the Strapi admin login UI
|
|
18
|
+
It does **not** automatically replace the default Strapi admin login UI.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
To use it in a real project, you need two parts:
|
|
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
|
|
25
30
|
|
|
26
31
|
## Endpoints
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
After installation, the plugin exposes:
|
|
29
34
|
|
|
30
35
|
- `POST /api/admin-2fa/login`
|
|
31
36
|
- `POST /api/admin-2fa/verify`
|
|
@@ -43,12 +48,11 @@ The plugin exposes these routes:
|
|
|
43
48
|
npm install @vivinkv28/strapi-2fa-admin-plugin
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
##
|
|
51
|
+
## Step 1: Enable The Plugin
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
In your Strapi project, update `config/plugins.ts`:
|
|
49
54
|
|
|
50
55
|
```ts
|
|
51
|
-
// config/plugins.ts
|
|
52
56
|
import type { Core } from "@strapi/strapi";
|
|
53
57
|
|
|
54
58
|
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({
|
|
@@ -77,42 +81,296 @@ const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin =>
|
|
|
77
81
|
export default config;
|
|
78
82
|
```
|
|
79
83
|
|
|
80
|
-
##
|
|
84
|
+
## Step 2: Configure Email
|
|
85
|
+
|
|
86
|
+
This plugin sends OTP emails through Strapi's email plugin.
|
|
87
|
+
|
|
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.
|
|
91
|
+
|
|
92
|
+
## Step 3: Make Sure Server/Proxy Settings Are Correct
|
|
93
|
+
|
|
94
|
+
If your Strapi app runs behind a proxy, configure `config/server.ts` correctly so admin cookies work as expected.
|
|
95
|
+
|
|
96
|
+
Typical example:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import type { Core } from "@strapi/strapi";
|
|
100
|
+
|
|
101
|
+
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
|
|
102
|
+
host: env("HOST", "0.0.0.0"),
|
|
103
|
+
port: env.int("PORT", 1337),
|
|
104
|
+
url: env("URL", "http://localhost:1337"),
|
|
105
|
+
proxy: env.bool("IS_PROXIED", env("NODE_ENV", "development") === "production"),
|
|
106
|
+
app: {
|
|
107
|
+
keys: env.array("APP_KEYS"),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export default config;
|
|
112
|
+
```
|
|
113
|
+
|
|
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:
|
|
119
|
+
|
|
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`.
|
|
81
147
|
|
|
82
|
-
|
|
148
|
+
## Step 5: Patch The Strapi Admin Auth Service
|
|
83
149
|
|
|
84
|
-
|
|
150
|
+
Create these files in your Strapi project:
|
|
85
151
|
|
|
86
|
-
|
|
152
|
+
- `scripts/strapi-admin-2fa-patch/services/auth.js`
|
|
153
|
+
- `scripts/strapi-admin-2fa-patch/services/auth.mjs`
|
|
87
154
|
|
|
88
|
-
|
|
89
|
-
- send them to `POST /api/admin-2fa/login`
|
|
90
|
-
- if successful, store `challengeId` and switch to OTP mode
|
|
155
|
+
Start from the corresponding Strapi admin auth service file and add the OTP mutations below.
|
|
91
156
|
|
|
92
|
-
|
|
157
|
+
### Add these mutations
|
|
93
158
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
+
```
|
|
98
194
|
|
|
99
|
-
###
|
|
195
|
+
### Export these hooks
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
const {
|
|
199
|
+
useAdminLoginWithOtpMutation,
|
|
200
|
+
useVerifyAdminLoginOtpMutation,
|
|
201
|
+
useResendAdminLoginOtpMutation,
|
|
202
|
+
} = authService;
|
|
203
|
+
```
|
|
100
204
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
205
|
+
## Step 6: Patch The Strapi Login Screen
|
|
206
|
+
|
|
207
|
+
Create these files in your Strapi project:
|
|
208
|
+
|
|
209
|
+
- `scripts/strapi-admin-2fa-patch/pages/Auth/components/Login.js`
|
|
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
|
|
215
|
+
- 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
|
+
```
|
|
105
281
|
|
|
106
|
-
|
|
282
|
+
### OTP resend handler
|
|
107
283
|
|
|
108
|
-
|
|
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
|
|
323
|
+
- paste support
|
|
324
|
+
- backspace handling
|
|
325
|
+
- auto focus
|
|
326
|
+
- inline error state
|
|
327
|
+
|
|
328
|
+
## Step 7: Add A Patch Apply Script
|
|
329
|
+
|
|
330
|
+
Create:
|
|
331
|
+
|
|
332
|
+
- `scripts/apply-strapi-admin-2fa-patch.js`
|
|
333
|
+
|
|
334
|
+
This script should:
|
|
335
|
+
|
|
336
|
+
1. copy `scripts/strapi-admin-2fa-patch/services/auth.js`
|
|
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
|
|
342
|
+
|
|
343
|
+
## Step 8: Wire The Patch Script In `package.json`
|
|
344
|
+
|
|
345
|
+
In your Strapi project `package.json`, add:
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{
|
|
349
|
+
"scripts": {
|
|
350
|
+
"prebuild": "node scripts/apply-strapi-admin-2fa-patch.js",
|
|
351
|
+
"predev": "node scripts/apply-strapi-admin-2fa-patch.js",
|
|
352
|
+
"predevelop": "node scripts/apply-strapi-admin-2fa-patch.js",
|
|
353
|
+
"postinstall": "node scripts/apply-strapi-admin-2fa-patch.js"
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
This ensures the login patch is reapplied:
|
|
359
|
+
|
|
360
|
+
- after dependency install
|
|
361
|
+
- before build
|
|
362
|
+
- before dev
|
|
363
|
+
|
|
364
|
+
## Request Flow
|
|
365
|
+
|
|
366
|
+
### Login
|
|
109
367
|
|
|
110
368
|
```http
|
|
111
369
|
POST /api/admin-2fa/login
|
|
112
370
|
Content-Type: application/json
|
|
113
371
|
```
|
|
114
372
|
|
|
115
|
-
Example
|
|
373
|
+
Example body:
|
|
116
374
|
|
|
117
375
|
```json
|
|
118
376
|
{
|
|
@@ -123,12 +381,12 @@ Example payload:
|
|
|
123
381
|
}
|
|
124
382
|
```
|
|
125
383
|
|
|
126
|
-
Example
|
|
384
|
+
Example response:
|
|
127
385
|
|
|
128
386
|
```json
|
|
129
387
|
{
|
|
130
388
|
"data": {
|
|
131
|
-
"challengeId": "
|
|
389
|
+
"challengeId": "***",
|
|
132
390
|
"expiresAt": "2026-04-05T18:30:00.000Z",
|
|
133
391
|
"maskedEmail": "admin@example.com",
|
|
134
392
|
"rememberMe": true
|
|
@@ -136,89 +394,37 @@ Example success response:
|
|
|
136
394
|
}
|
|
137
395
|
```
|
|
138
396
|
|
|
139
|
-
### Verify
|
|
397
|
+
### Verify
|
|
140
398
|
|
|
141
399
|
```http
|
|
142
400
|
POST /api/admin-2fa/verify
|
|
143
401
|
Content-Type: application/json
|
|
144
402
|
```
|
|
145
403
|
|
|
146
|
-
Example
|
|
404
|
+
Example body:
|
|
147
405
|
|
|
148
406
|
```json
|
|
149
407
|
{
|
|
150
|
-
"challengeId": "
|
|
408
|
+
"challengeId": "***",
|
|
151
409
|
"code": "123456"
|
|
152
410
|
}
|
|
153
411
|
```
|
|
154
412
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```json
|
|
158
|
-
{
|
|
159
|
-
"data": {
|
|
160
|
-
"token": "<access-token>",
|
|
161
|
-
"accessToken": "<access-token>",
|
|
162
|
-
"user": {
|
|
163
|
-
"id": 1,
|
|
164
|
-
"email": "admin@example.com"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### Resend request
|
|
413
|
+
### Resend
|
|
171
414
|
|
|
172
415
|
```http
|
|
173
416
|
POST /api/admin-2fa/resend
|
|
174
417
|
Content-Type: application/json
|
|
175
418
|
```
|
|
176
419
|
|
|
177
|
-
Example
|
|
420
|
+
Example body:
|
|
178
421
|
|
|
179
422
|
```json
|
|
180
423
|
{
|
|
181
|
-
"challengeId": "
|
|
424
|
+
"challengeId": "***"
|
|
182
425
|
}
|
|
183
426
|
```
|
|
184
427
|
|
|
185
|
-
### UI error states to handle
|
|
186
|
-
|
|
187
|
-
- invalid email or password
|
|
188
|
-
- OTP expired
|
|
189
|
-
- OTP session not found
|
|
190
|
-
- invalid OTP code
|
|
191
|
-
- too many authentication attempts
|
|
192
|
-
- maximum resend attempts exceeded
|
|
193
|
-
|
|
194
|
-
## Host Project Requirements
|
|
195
|
-
|
|
196
|
-
### Email provider
|
|
197
|
-
|
|
198
|
-
The plugin sends OTP emails through Strapi's email plugin, so the host project must configure an email provider.
|
|
199
|
-
|
|
200
|
-
### Proxy and HTTPS
|
|
201
|
-
|
|
202
|
-
If the project runs behind a reverse proxy, configure `config/server.ts` correctly so secure admin cookies work.
|
|
203
|
-
|
|
204
|
-
Typical example:
|
|
205
|
-
|
|
206
|
-
```ts
|
|
207
|
-
import type { Core } from "@strapi/strapi";
|
|
208
|
-
|
|
209
|
-
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
|
|
210
|
-
host: env("HOST", "0.0.0.0"),
|
|
211
|
-
port: env.int("PORT", 1337),
|
|
212
|
-
url: env("URL", "http://localhost:1337"),
|
|
213
|
-
proxy: env.bool("IS_PROXIED", env("NODE_ENV", "development") === "production"),
|
|
214
|
-
app: {
|
|
215
|
-
keys: env.array("APP_KEYS"),
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
export default config;
|
|
220
|
-
```
|
|
221
|
-
|
|
222
428
|
## Environment Variables
|
|
223
429
|
|
|
224
430
|
Suggested defaults:
|
|
@@ -238,9 +444,20 @@ ADMIN_OTP_RESEND_EMAIL_LIMIT=5
|
|
|
238
444
|
ADMIN_OTP_DEBUG_TIMINGS=false
|
|
239
445
|
```
|
|
240
446
|
|
|
241
|
-
##
|
|
447
|
+
## Testing Checklist
|
|
448
|
+
|
|
449
|
+
After setup, test these cases:
|
|
450
|
+
|
|
451
|
+
1. correct email/password shows OTP screen
|
|
452
|
+
2. correct OTP logs into admin successfully
|
|
453
|
+
3. resend OTP works
|
|
454
|
+
4. invalid OTP shows an error
|
|
455
|
+
5. expired OTP restarts the flow properly
|
|
456
|
+
6. wrong email/password still fails safely
|
|
457
|
+
|
|
458
|
+
## Code-Level Overview
|
|
242
459
|
|
|
243
|
-
Main files:
|
|
460
|
+
Main plugin files:
|
|
244
461
|
|
|
245
462
|
```text
|
|
246
463
|
admin/src/index.js
|
|
@@ -257,23 +474,24 @@ Responsibilities:
|
|
|
257
474
|
Minimal admin plugin stub required by the Strapi Plugin SDK.
|
|
258
475
|
|
|
259
476
|
- `server/src/routes/index.js`
|
|
260
|
-
Declares
|
|
477
|
+
Declares `/login`, `/verify`, and `/resend`.
|
|
261
478
|
|
|
262
479
|
- `server/src/controllers/auth.js`
|
|
263
|
-
|
|
480
|
+
Extracts request data, resolves client IP, sets refresh cookies after verification.
|
|
264
481
|
|
|
265
482
|
- `server/src/services/auth.js`
|
|
266
|
-
Core OTP
|
|
483
|
+
Core OTP engine: credentials, challenge lifecycle, rate limits, email sending, and session creation.
|
|
267
484
|
|
|
268
485
|
- `server/src/utils/strapi-session-auth.js`
|
|
269
|
-
|
|
486
|
+
Resolves Strapi's internal admin session helper at runtime.
|
|
270
487
|
|
|
271
|
-
##
|
|
488
|
+
## Deeper Docs
|
|
272
489
|
|
|
273
|
-
If you
|
|
490
|
+
If you want more detail from the repository:
|
|
274
491
|
|
|
275
492
|
- `docs/INTEGRATION.md`
|
|
276
493
|
- `docs/ARCHITECTURE.md`
|
|
494
|
+
- `admin-screen.md`
|
|
277
495
|
|
|
278
496
|
## Development
|
|
279
497
|
|
|
@@ -294,12 +512,12 @@ Useful commands:
|
|
|
294
512
|
1. run `npm install`
|
|
295
513
|
2. run `npm run build`
|
|
296
514
|
3. run `npm run verify`
|
|
297
|
-
4.
|
|
515
|
+
4. test in a real Strapi app
|
|
298
516
|
5. bump the version
|
|
299
|
-
6.
|
|
517
|
+
6. run `npm publish --access public`
|
|
300
518
|
|
|
301
519
|
## Production Notes
|
|
302
520
|
|
|
303
|
-
-
|
|
304
|
-
- If the admin
|
|
305
|
-
- For stronger
|
|
521
|
+
- This improves admin security, but email OTP is still weaker than TOTP or WebAuthn.
|
|
522
|
+
- If the admin email account is compromised, the second factor can still be bypassed.
|
|
523
|
+
- For stronger future versions, consider TOTP, backup codes, trusted devices, or passkeys.
|