@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/docs/INTEGRATION.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
# Integration Guide
|
|
2
|
-
|
|
3
|
-
This guide explains how to use `@vivinkv28/strapi-2fa-admin-plugin` inside an existing Strapi 5 project.
|
|
4
|
-
|
|
5
|
-
## What The Plugin Does
|
|
6
|
-
|
|
7
|
-
The plugin provides the backend endpoints and auth logic for an OTP-based admin login flow.
|
|
8
|
-
|
|
9
|
-
It does not replace the Strapi admin login UI by itself.
|
|
10
|
-
|
|
11
|
-
Your host project must provide a login integration layer that:
|
|
12
|
-
|
|
13
|
-
1. collects admin email and password
|
|
14
|
-
2. calls the plugin login endpoint
|
|
15
|
-
3. shows an OTP input screen
|
|
16
|
-
4. calls the plugin verify endpoint
|
|
17
|
-
5. optionally allows OTP resend
|
|
18
|
-
|
|
19
|
-
## Endpoints
|
|
20
|
-
|
|
21
|
-
The plugin registers these routes:
|
|
22
|
-
|
|
23
|
-
- `POST /api/admin-2fa/login`
|
|
24
|
-
- `POST /api/admin-2fa/verify`
|
|
25
|
-
- `POST /api/admin-2fa/resend`
|
|
26
|
-
|
|
27
|
-
## Install And Enable
|
|
28
|
-
|
|
29
|
-
### Published npm package
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
npm install @vivinkv28/strapi-2fa-admin-plugin
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Local external plugin
|
|
36
|
-
|
|
37
|
-
In your Strapi app:
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
// config/plugins.ts
|
|
41
|
-
import type { Core } from "@strapi/strapi";
|
|
42
|
-
|
|
43
|
-
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Plugin => ({
|
|
44
|
-
"admin-2fa": {
|
|
45
|
-
enabled: true,
|
|
46
|
-
resolve: "../strapi-2fa-admin-plugin",
|
|
47
|
-
config: {
|
|
48
|
-
otpDigits: env.int("ADMIN_OTP_DIGITS", 6),
|
|
49
|
-
otpTtlSeconds: env.int("ADMIN_OTP_TTL_SECONDS", 300),
|
|
50
|
-
maxAttempts: env.int("ADMIN_OTP_MAX_ATTEMPTS", 5),
|
|
51
|
-
maxResends: env.int("ADMIN_OTP_MAX_RESENDS", 3),
|
|
52
|
-
rateLimitWindowSeconds: env.int("ADMIN_OTP_RATE_LIMIT_WINDOW_SECONDS", 900),
|
|
53
|
-
loginIpLimit: env.int("ADMIN_OTP_LOGIN_IP_LIMIT", 10),
|
|
54
|
-
loginEmailLimit: env.int("ADMIN_OTP_LOGIN_EMAIL_LIMIT", 5),
|
|
55
|
-
verifyIpLimit: env.int("ADMIN_OTP_VERIFY_IP_LIMIT", 20),
|
|
56
|
-
verifyEmailLimit: env.int("ADMIN_OTP_VERIFY_EMAIL_LIMIT", 10),
|
|
57
|
-
resendIpLimit: env.int("ADMIN_OTP_RESEND_IP_LIMIT", 10),
|
|
58
|
-
resendEmailLimit: env.int("ADMIN_OTP_RESEND_EMAIL_LIMIT", 5),
|
|
59
|
-
debugTimings: env.bool(
|
|
60
|
-
"ADMIN_OTP_DEBUG_TIMINGS",
|
|
61
|
-
env("NODE_ENV", "development") !== "production"
|
|
62
|
-
),
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export default config;
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Required Host App Setup
|
|
71
|
-
|
|
72
|
-
### Email provider
|
|
73
|
-
|
|
74
|
-
The plugin calls `strapi.plugin("email").service("email").send(...)`.
|
|
75
|
-
|
|
76
|
-
Your Strapi project must configure an email provider or OTP delivery will fail.
|
|
77
|
-
|
|
78
|
-
### Proxy and HTTPS
|
|
79
|
-
|
|
80
|
-
If your project runs behind a reverse proxy, configure `config/server.ts` correctly so Strapi recognizes secure requests and admin cookies are set with the right options.
|
|
81
|
-
|
|
82
|
-
Typical example:
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
import type { Core } from "@strapi/strapi";
|
|
86
|
-
|
|
87
|
-
const config = ({ env }: Core.Config.Shared.ConfigParams): Core.Config.Server => ({
|
|
88
|
-
host: env("HOST", "0.0.0.0"),
|
|
89
|
-
port: env.int("PORT", 1337),
|
|
90
|
-
url: env("URL", "http://localhost:1337"),
|
|
91
|
-
proxy: env.bool("IS_PROXIED", env("NODE_ENV", "development") === "production"),
|
|
92
|
-
app: {
|
|
93
|
-
keys: env.array("APP_KEYS"),
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
export default config;
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
## Request Flow
|
|
101
|
-
|
|
102
|
-
### 1. Start login
|
|
103
|
-
|
|
104
|
-
Send admin email and password to:
|
|
105
|
-
|
|
106
|
-
```http
|
|
107
|
-
POST /api/admin-2fa/login
|
|
108
|
-
Content-Type: application/json
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Example payload:
|
|
112
|
-
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"email": "admin@example.com",
|
|
116
|
-
"password": "super-secret-password",
|
|
117
|
-
"rememberMe": true,
|
|
118
|
-
"deviceId": "browser-device-id"
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Example success response:
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"data": {
|
|
127
|
-
"challengeId": "0d3af6fd-b351-4d1e-bb81-2a8201d8a0f4",
|
|
128
|
-
"expiresAt": "2026-04-05T18:30:00.000Z",
|
|
129
|
-
"maskedEmail": "admin@example.com",
|
|
130
|
-
"rememberMe": true
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
What happens here:
|
|
136
|
-
|
|
137
|
-
- Strapi validates the admin credentials
|
|
138
|
-
- the plugin creates an OTP challenge
|
|
139
|
-
- the plugin emails the OTP
|
|
140
|
-
- no final admin session is created yet
|
|
141
|
-
|
|
142
|
-
### 2. Verify OTP
|
|
143
|
-
|
|
144
|
-
Send the OTP code to:
|
|
145
|
-
|
|
146
|
-
```http
|
|
147
|
-
POST /api/admin-2fa/verify
|
|
148
|
-
Content-Type: application/json
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Example payload:
|
|
152
|
-
|
|
153
|
-
```json
|
|
154
|
-
{
|
|
155
|
-
"challengeId": "0d3af6fd-b351-4d1e-bb81-2a8201d8a0f4",
|
|
156
|
-
"code": "123456"
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Example success response:
|
|
161
|
-
|
|
162
|
-
```json
|
|
163
|
-
{
|
|
164
|
-
"data": {
|
|
165
|
-
"token": "<access-token>",
|
|
166
|
-
"accessToken": "<access-token>",
|
|
167
|
-
"user": {
|
|
168
|
-
"id": 1,
|
|
169
|
-
"email": "admin@example.com"
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
What happens here:
|
|
176
|
-
|
|
177
|
-
- the plugin reloads the OTP challenge
|
|
178
|
-
- the submitted code is hashed and compared
|
|
179
|
-
- the real Strapi admin session is created only after OTP verification
|
|
180
|
-
- the refresh cookie is set by the plugin controller
|
|
181
|
-
|
|
182
|
-
### 3. Resend OTP
|
|
183
|
-
|
|
184
|
-
Send:
|
|
185
|
-
|
|
186
|
-
```http
|
|
187
|
-
POST /api/admin-2fa/resend
|
|
188
|
-
Content-Type: application/json
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Example payload:
|
|
192
|
-
|
|
193
|
-
```json
|
|
194
|
-
{
|
|
195
|
-
"challengeId": "0d3af6fd-b351-4d1e-bb81-2a8201d8a0f4"
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
This generates a new OTP for the same challenge and extends the expiry.
|
|
200
|
-
|
|
201
|
-
## Error Cases To Handle In The UI
|
|
202
|
-
|
|
203
|
-
Your login integration should handle these cases cleanly:
|
|
204
|
-
|
|
205
|
-
- invalid email or password
|
|
206
|
-
- OTP session not found
|
|
207
|
-
- OTP expired
|
|
208
|
-
- invalid OTP code
|
|
209
|
-
- too many authentication attempts
|
|
210
|
-
- maximum resend attempts exceeded
|
|
211
|
-
|
|
212
|
-
These are returned as normal Strapi error responses, so the frontend should surface the message to the admin user in a safe and friendly way.
|
|
213
|
-
|
|
214
|
-
## Recommended Frontend Behavior
|
|
215
|
-
|
|
216
|
-
- Keep login and OTP entry as two separate UI states.
|
|
217
|
-
- Store `challengeId` in memory for the current login attempt.
|
|
218
|
-
- Do not create your own session after password validation; wait for `/verify`.
|
|
219
|
-
- After successful OTP verification, treat the returned user/token exactly like a successful admin login.
|
|
220
|
-
- If resend fails due to limits or expiry, restart the login flow.
|
|
221
|
-
|
|
222
|
-
## Environment Variables
|
|
223
|
-
|
|
224
|
-
Recommended defaults:
|
|
225
|
-
|
|
226
|
-
```env
|
|
227
|
-
ADMIN_OTP_DIGITS=6
|
|
228
|
-
ADMIN_OTP_TTL_SECONDS=300
|
|
229
|
-
ADMIN_OTP_MAX_ATTEMPTS=5
|
|
230
|
-
ADMIN_OTP_MAX_RESENDS=3
|
|
231
|
-
ADMIN_OTP_RATE_LIMIT_WINDOW_SECONDS=900
|
|
232
|
-
ADMIN_OTP_LOGIN_IP_LIMIT=10
|
|
233
|
-
ADMIN_OTP_LOGIN_EMAIL_LIMIT=5
|
|
234
|
-
ADMIN_OTP_VERIFY_IP_LIMIT=20
|
|
235
|
-
ADMIN_OTP_VERIFY_EMAIL_LIMIT=10
|
|
236
|
-
ADMIN_OTP_RESEND_IP_LIMIT=10
|
|
237
|
-
ADMIN_OTP_RESEND_EMAIL_LIMIT=5
|
|
238
|
-
ADMIN_OTP_DEBUG_TIMINGS=false
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Production Notes
|
|
242
|
-
|
|
243
|
-
- Email OTP is better than password-only login, but weaker than TOTP or WebAuthn.
|
|
244
|
-
- If the admin mailbox is compromised, the second factor can still be bypassed.
|
|
245
|
-
- Use app passwords or provider-managed SMTP credentials for OTP delivery.
|
|
246
|
-
- Make sure your host project sets `URL` and proxy settings correctly in production.
|