@vivinkv28/strapi-2fa-admin-plugin 0.1.9 → 0.1.12

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 CHANGED
@@ -122,6 +122,20 @@ host-patch/
122
122
 
123
123
  These are the files you can copy into your Strapi project to get the same admin OTP UI pattern.
124
124
 
125
+ If you want to view the same host patch files on GitHub, use:
126
+
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)
133
+
134
+ That gives users both options:
135
+
136
+ - install from npm and copy the bundled files
137
+ - inspect the admin UI source directly on GitHub before integrating it
138
+
125
139
  ## What The UI Patch Does
126
140
 
127
141
  The bundled host patch changes the Strapi admin login flow to:
@@ -19149,21 +19149,21 @@ class ForbiddenError extends ApplicationError$1 {
19149
19149
  this.message = message;
19150
19150
  }
19151
19151
  }
19152
- class UnauthorizedError extends ApplicationError$1 {
19152
+ let UnauthorizedError$1 = class UnauthorizedError extends ApplicationError$1 {
19153
19153
  constructor(message = "Unauthorized", details) {
19154
19154
  super(message, details);
19155
19155
  this.name = "UnauthorizedError";
19156
19156
  this.message = message;
19157
19157
  }
19158
- }
19159
- class RateLimitError extends ApplicationError$1 {
19158
+ };
19159
+ let RateLimitError$1 = class RateLimitError extends ApplicationError$1 {
19160
19160
  constructor(message = "Too many requests, please try again later.", details) {
19161
19161
  super(message, details);
19162
19162
  this.name = "RateLimitError";
19163
19163
  this.message = message;
19164
19164
  this.details = details || {};
19165
19165
  }
19166
- }
19166
+ };
19167
19167
  class PayloadTooLargeError extends ApplicationError$1 {
19168
19168
  constructor(message = "Entity too large", details) {
19169
19169
  super(message, details);
@@ -19196,8 +19196,8 @@ const errors$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
19196
19196
  PaginationError,
19197
19197
  PayloadTooLargeError,
19198
19198
  PolicyError,
19199
- RateLimitError,
19200
- UnauthorizedError,
19199
+ RateLimitError: RateLimitError$1,
19200
+ UnauthorizedError: UnauthorizedError$1,
19201
19201
  ValidationError: ValidationError$1,
19202
19202
  YupValidationError
19203
19203
  }, Symbol.toStringTag, { value: "Module" }));
@@ -43795,7 +43795,7 @@ const require$$1 = /* @__PURE__ */ getAugmentedNamespace(dist);
43795
43795
  const crypto = require$$1__default$1.default;
43796
43796
  const { errors } = require$$1;
43797
43797
  const sessionAuth = strapiSessionAuth;
43798
- const { ApplicationError: ApplicationError2, ValidationError: ValidationError3 } = errors;
43798
+ const { ApplicationError: ApplicationError2, RateLimitError: RateLimitError2, UnauthorizedError: UnauthorizedError2, ValidationError: ValidationError3 } = errors;
43799
43799
  const STORE_NAME = "admin-otp-login";
43800
43800
  const STORE_KEY_PREFIX = "challenge:";
43801
43801
  const RATE_LIMIT_KEY_PREFIX = "rate-limit:";
@@ -43914,11 +43914,11 @@ const deleteChallenge = async (store, challengeId) => {
43914
43914
  const getChallenge = async (store, challengeId) => {
43915
43915
  const challenge = await store.get({ key: getStoreKey(challengeId) });
43916
43916
  if (!challenge) {
43917
- throw new ApplicationError2("OTP session not found. Please log in again.");
43917
+ throw new UnauthorizedError2("OTP session not found. Please log in again.");
43918
43918
  }
43919
43919
  if (new Date(challenge.expiresAt).getTime() <= Date.now()) {
43920
43920
  await deleteChallenge(store, challengeId);
43921
- throw new ApplicationError2("OTP expired. Please log in again.");
43921
+ throw new UnauthorizedError2("OTP expired. Please log in again.");
43922
43922
  }
43923
43923
  return challenge;
43924
43924
  };
@@ -43941,7 +43941,7 @@ const registerRateLimitHit = async (store, config2, action, scope, identifier) =
43941
43941
  return;
43942
43942
  }
43943
43943
  if (existing.count >= limit) {
43944
- throw new ApplicationError2("Too many authentication attempts. Please wait a few minutes and try again.");
43944
+ throw new RateLimitError2("Too many authentication attempts. Please wait a few minutes and try again.");
43945
43945
  }
43946
43946
  await store.set({
43947
43947
  key,
@@ -44020,7 +44020,7 @@ var auth$1 = () => ({
44020
44020
  });
44021
44021
  logDuration(config2, "checkCredentials", credentialsStartedAt);
44022
44022
  if (!user) {
44023
- throw new ApplicationError2(info?.message ?? "Invalid credentials");
44023
+ throw new UnauthorizedError2(info?.message ?? "Invalid credentials");
44024
44024
  }
44025
44025
  const challengeId = crypto.randomUUID();
44026
44026
  const code = createOtpCode(config2.otpDigits);
@@ -44069,7 +44069,7 @@ var auth$1 = () => ({
44069
44069
  await registerRateLimitHit(store, config2, "resend", "email", current.email);
44070
44070
  if (current.resendCount >= config2.maxResends) {
44071
44071
  await deleteChallenge(store, challengeId);
44072
- throw new ApplicationError2("Maximum OTP resend attempts exceeded. Please log in again.");
44072
+ throw new RateLimitError2("Maximum OTP resend attempts exceeded. Please log in again.");
44073
44073
  }
44074
44074
  const code = createOtpCode(config2.otpDigits);
44075
44075
  const salt = crypto.randomBytes(16).toString("hex");
@@ -44112,7 +44112,7 @@ var auth$1 = () => ({
44112
44112
  await registerRateLimitHit(store, config2, "verify", "email", challenge.email);
44113
44113
  if (challenge.attempts >= config2.maxAttempts) {
44114
44114
  await deleteChallenge(store, challengeId);
44115
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44115
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44116
44116
  }
44117
44117
  const hashStartedAt = now();
44118
44118
  const computedHash = await createOtpHash(challengeId, code, challenge.salt);
@@ -44125,7 +44125,7 @@ var auth$1 = () => ({
44125
44125
  const nextAttempts = challenge.attempts + 1;
44126
44126
  if (nextAttempts >= config2.maxAttempts) {
44127
44127
  await deleteChallenge(store, challengeId);
44128
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44128
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44129
44129
  }
44130
44130
  const storeStartedAt = now();
44131
44131
  await store.set({
@@ -44139,7 +44139,7 @@ var auth$1 = () => ({
44139
44139
  challengeId,
44140
44140
  attempts: nextAttempts
44141
44141
  });
44142
- throw new ApplicationError2("Invalid OTP code");
44142
+ throw new UnauthorizedError2("Invalid OTP code");
44143
44143
  }
44144
44144
  const deleteStartedAt = now();
44145
44145
  await deleteChallenge(store, challengeId);
@@ -19135,21 +19135,21 @@ class ForbiddenError extends ApplicationError$1 {
19135
19135
  this.message = message;
19136
19136
  }
19137
19137
  }
19138
- class UnauthorizedError extends ApplicationError$1 {
19138
+ let UnauthorizedError$1 = class UnauthorizedError extends ApplicationError$1 {
19139
19139
  constructor(message = "Unauthorized", details) {
19140
19140
  super(message, details);
19141
19141
  this.name = "UnauthorizedError";
19142
19142
  this.message = message;
19143
19143
  }
19144
- }
19145
- class RateLimitError extends ApplicationError$1 {
19144
+ };
19145
+ let RateLimitError$1 = class RateLimitError extends ApplicationError$1 {
19146
19146
  constructor(message = "Too many requests, please try again later.", details) {
19147
19147
  super(message, details);
19148
19148
  this.name = "RateLimitError";
19149
19149
  this.message = message;
19150
19150
  this.details = details || {};
19151
19151
  }
19152
- }
19152
+ };
19153
19153
  class PayloadTooLargeError extends ApplicationError$1 {
19154
19154
  constructor(message = "Entity too large", details) {
19155
19155
  super(message, details);
@@ -19182,8 +19182,8 @@ const errors$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
19182
19182
  PaginationError,
19183
19183
  PayloadTooLargeError,
19184
19184
  PolicyError,
19185
- RateLimitError,
19186
- UnauthorizedError,
19185
+ RateLimitError: RateLimitError$1,
19186
+ UnauthorizedError: UnauthorizedError$1,
19187
19187
  ValidationError: ValidationError$1,
19188
19188
  YupValidationError
19189
19189
  }, Symbol.toStringTag, { value: "Module" }));
@@ -43781,7 +43781,7 @@ const require$$1 = /* @__PURE__ */ getAugmentedNamespace(dist);
43781
43781
  const crypto = require$$1$2;
43782
43782
  const { errors } = require$$1;
43783
43783
  const sessionAuth = strapiSessionAuth;
43784
- const { ApplicationError: ApplicationError2, ValidationError: ValidationError3 } = errors;
43784
+ const { ApplicationError: ApplicationError2, RateLimitError: RateLimitError2, UnauthorizedError: UnauthorizedError2, ValidationError: ValidationError3 } = errors;
43785
43785
  const STORE_NAME = "admin-otp-login";
43786
43786
  const STORE_KEY_PREFIX = "challenge:";
43787
43787
  const RATE_LIMIT_KEY_PREFIX = "rate-limit:";
@@ -43900,11 +43900,11 @@ const deleteChallenge = async (store, challengeId) => {
43900
43900
  const getChallenge = async (store, challengeId) => {
43901
43901
  const challenge = await store.get({ key: getStoreKey(challengeId) });
43902
43902
  if (!challenge) {
43903
- throw new ApplicationError2("OTP session not found. Please log in again.");
43903
+ throw new UnauthorizedError2("OTP session not found. Please log in again.");
43904
43904
  }
43905
43905
  if (new Date(challenge.expiresAt).getTime() <= Date.now()) {
43906
43906
  await deleteChallenge(store, challengeId);
43907
- throw new ApplicationError2("OTP expired. Please log in again.");
43907
+ throw new UnauthorizedError2("OTP expired. Please log in again.");
43908
43908
  }
43909
43909
  return challenge;
43910
43910
  };
@@ -43927,7 +43927,7 @@ const registerRateLimitHit = async (store, config2, action, scope, identifier) =
43927
43927
  return;
43928
43928
  }
43929
43929
  if (existing.count >= limit) {
43930
- throw new ApplicationError2("Too many authentication attempts. Please wait a few minutes and try again.");
43930
+ throw new RateLimitError2("Too many authentication attempts. Please wait a few minutes and try again.");
43931
43931
  }
43932
43932
  await store.set({
43933
43933
  key,
@@ -44006,7 +44006,7 @@ var auth$1 = () => ({
44006
44006
  });
44007
44007
  logDuration(config2, "checkCredentials", credentialsStartedAt);
44008
44008
  if (!user) {
44009
- throw new ApplicationError2(info?.message ?? "Invalid credentials");
44009
+ throw new UnauthorizedError2(info?.message ?? "Invalid credentials");
44010
44010
  }
44011
44011
  const challengeId = crypto.randomUUID();
44012
44012
  const code = createOtpCode(config2.otpDigits);
@@ -44055,7 +44055,7 @@ var auth$1 = () => ({
44055
44055
  await registerRateLimitHit(store, config2, "resend", "email", current.email);
44056
44056
  if (current.resendCount >= config2.maxResends) {
44057
44057
  await deleteChallenge(store, challengeId);
44058
- throw new ApplicationError2("Maximum OTP resend attempts exceeded. Please log in again.");
44058
+ throw new RateLimitError2("Maximum OTP resend attempts exceeded. Please log in again.");
44059
44059
  }
44060
44060
  const code = createOtpCode(config2.otpDigits);
44061
44061
  const salt = crypto.randomBytes(16).toString("hex");
@@ -44098,7 +44098,7 @@ var auth$1 = () => ({
44098
44098
  await registerRateLimitHit(store, config2, "verify", "email", challenge.email);
44099
44099
  if (challenge.attempts >= config2.maxAttempts) {
44100
44100
  await deleteChallenge(store, challengeId);
44101
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44101
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44102
44102
  }
44103
44103
  const hashStartedAt = now();
44104
44104
  const computedHash = await createOtpHash(challengeId, code, challenge.salt);
@@ -44111,7 +44111,7 @@ var auth$1 = () => ({
44111
44111
  const nextAttempts = challenge.attempts + 1;
44112
44112
  if (nextAttempts >= config2.maxAttempts) {
44113
44113
  await deleteChallenge(store, challengeId);
44114
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44114
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44115
44115
  }
44116
44116
  const storeStartedAt = now();
44117
44117
  await store.set({
@@ -44125,7 +44125,7 @@ var auth$1 = () => ({
44125
44125
  challengeId,
44126
44126
  attempts: nextAttempts
44127
44127
  });
44128
- throw new ApplicationError2("Invalid OTP code");
44128
+ throw new UnauthorizedError2("Invalid OTP code");
44129
44129
  }
44130
44130
  const deleteStartedAt = now();
44131
44131
  await deleteChallenge(store, challengeId);
@@ -35,8 +35,23 @@ function _interopNamespaceDefault(e) {
35
35
  return Object.freeze(n);
36
36
  }
37
37
 
38
- var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
39
- var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup);
38
+ var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
39
+ var yup__namespace = /*#__PURE__*/_interopNamespaceDefault(yup);
40
+
41
+ const getApiErrorMessage = (error, fallback = 'Something went wrong')=>{
42
+ if (!error || typeof error !== 'object') {
43
+ return fallback;
44
+ }
45
+ const candidates = [
46
+ error?.data?.error?.message,
47
+ error?.data?.message,
48
+ error?.error?.message,
49
+ error?.message,
50
+ typeof error?.error === 'string' ? error.error : undefined
51
+ ];
52
+ const message = candidates.find((value)=>typeof value === 'string' && value.trim().length > 0);
53
+ return message ?? fallback;
54
+ };
40
55
 
41
56
  const OTP_LENGTH = 6;
42
57
  const OTP_DIGIT_INPUT_STYLE = {
@@ -240,18 +255,18 @@ const Login = ({ children })=>{
240
255
  React__namespace.useEffect(()=>{
241
256
  document.title = 'Admin Dashboard';
242
257
  }, []);
243
- const handleLogin = async (body)=>{
244
- setApiError(undefined);
245
- const res = await adminLoginWithOtp({
246
- ...body,
247
- deviceId: deviceId.getOrCreateDeviceId()
248
- });
249
- if ('error' in res) {
250
- const message = res.error.message ?? 'Something went wrong';
251
- if (camelCase(message).toLowerCase() === 'usernotactive') {
252
- navigate('/auth/oops');
253
- return;
254
- }
258
+ const handleLogin = async (body)=>{
259
+ setApiError(undefined);
260
+ const res = await adminLoginWithOtp({
261
+ ...body,
262
+ deviceId: deviceId.getOrCreateDeviceId()
263
+ });
264
+ if ('error' in res) {
265
+ const message = getApiErrorMessage(res.error);
266
+ if (camelCase(message).toLowerCase() === 'usernotactive') {
267
+ navigate('/auth/oops');
268
+ return;
269
+ }
255
270
  setApiError(message);
256
271
  } else {
257
272
  setOtpStep({
@@ -267,15 +282,15 @@ const Login = ({ children })=>{
267
282
  return;
268
283
  }
269
284
  setApiError(undefined);
270
- const res = await verifyAdminLoginOtp({
271
- challengeId: otpStep.challengeId,
272
- code
273
- });
274
- if ('error' in res) {
275
- setApiError(res.error.message ?? 'Something went wrong');
276
- } else {
277
- toggleNotification({
278
- type: 'success',
285
+ const res = await verifyAdminLoginOtp({
286
+ challengeId: otpStep.challengeId,
287
+ code
288
+ });
289
+ if ('error' in res) {
290
+ setApiError(getApiErrorMessage(res.error));
291
+ } else {
292
+ toggleNotification({
293
+ type: 'success',
279
294
  title: formatMessage({
280
295
  id: 'Auth.notification.authenticated.title',
281
296
  defaultMessage: 'Successfully authenticated'
@@ -295,14 +310,14 @@ const Login = ({ children })=>{
295
310
  return;
296
311
  }
297
312
  setApiError(undefined);
298
- const res = await resendAdminLoginOtp({
299
- challengeId: otpStep.challengeId
300
- });
301
- if ('error' in res) {
302
- setApiError(res.error.message ?? 'Something went wrong');
303
- } else {
304
- setOtpStep({
305
- ...otpStep,
313
+ const res = await resendAdminLoginOtp({
314
+ challengeId: otpStep.challengeId
315
+ });
316
+ if ('error' in res) {
317
+ setApiError(getApiErrorMessage(res.error));
318
+ } else {
319
+ setOtpStep({
320
+ ...otpStep,
306
321
  expiresAt: res.data.expiresAt,
307
322
  maskedEmail: res.data.maskedEmail
308
323
  });
@@ -13,8 +13,23 @@ import { useTypedDispatch } from '../../../core/store/hooks.mjs';
13
13
  import { useNotification } from '../../../features/Notifications.mjs';
14
14
  import { login as loginAction } from '../../../reducer.mjs';
15
15
  import { useAdminLoginWithOtpMutation, useVerifyAdminLoginOtpMutation, useResendAdminLoginOtpMutation } from '../../../services/auth.mjs';
16
- import { getOrCreateDeviceId } from '../../../utils/deviceId.mjs';
17
- import { translatedErrors as errorsTrads } from '../../../utils/translatedErrors.mjs';
16
+ import { getOrCreateDeviceId } from '../../../utils/deviceId.mjs';
17
+ import { translatedErrors as errorsTrads } from '../../../utils/translatedErrors.mjs';
18
+
19
+ const getApiErrorMessage = (error, fallback = 'Something went wrong')=>{
20
+ if (!error || typeof error !== 'object') {
21
+ return fallback;
22
+ }
23
+ const candidates = [
24
+ error?.data?.error?.message,
25
+ error?.data?.message,
26
+ error?.error?.message,
27
+ error?.message,
28
+ typeof error?.error === 'string' ? error.error : undefined
29
+ ];
30
+ const message = candidates.find((value)=>typeof value === 'string' && value.trim().length > 0);
31
+ return message ?? fallback;
32
+ };
18
33
 
19
34
  const OTP_LENGTH = 6;
20
35
  const OTP_DIGIT_INPUT_STYLE = {
@@ -218,18 +233,18 @@ const Login = ({ children })=>{
218
233
  React.useEffect(()=>{
219
234
  document.title = 'Admin Dashboard';
220
235
  }, []);
221
- const handleLogin = async (body)=>{
222
- setApiError(undefined);
223
- const res = await adminLoginWithOtp({
224
- ...body,
225
- deviceId: getOrCreateDeviceId()
226
- });
227
- if ('error' in res) {
228
- const message = res.error.message ?? 'Something went wrong';
229
- if (camelCase(message).toLowerCase() === 'usernotactive') {
230
- navigate('/auth/oops');
231
- return;
232
- }
236
+ const handleLogin = async (body)=>{
237
+ setApiError(undefined);
238
+ const res = await adminLoginWithOtp({
239
+ ...body,
240
+ deviceId: getOrCreateDeviceId()
241
+ });
242
+ if ('error' in res) {
243
+ const message = getApiErrorMessage(res.error);
244
+ if (camelCase(message).toLowerCase() === 'usernotactive') {
245
+ navigate('/auth/oops');
246
+ return;
247
+ }
233
248
  setApiError(message);
234
249
  } else {
235
250
  setOtpStep({
@@ -245,15 +260,15 @@ const Login = ({ children })=>{
245
260
  return;
246
261
  }
247
262
  setApiError(undefined);
248
- const res = await verifyAdminLoginOtp({
249
- challengeId: otpStep.challengeId,
250
- code
251
- });
252
- if ('error' in res) {
253
- setApiError(res.error.message ?? 'Something went wrong');
254
- } else {
255
- toggleNotification({
256
- type: 'success',
263
+ const res = await verifyAdminLoginOtp({
264
+ challengeId: otpStep.challengeId,
265
+ code
266
+ });
267
+ if ('error' in res) {
268
+ setApiError(getApiErrorMessage(res.error));
269
+ } else {
270
+ toggleNotification({
271
+ type: 'success',
257
272
  title: formatMessage({
258
273
  id: 'Auth.notification.authenticated.title',
259
274
  defaultMessage: 'Successfully authenticated'
@@ -273,14 +288,14 @@ const Login = ({ children })=>{
273
288
  return;
274
289
  }
275
290
  setApiError(undefined);
276
- const res = await resendAdminLoginOtp({
277
- challengeId: otpStep.challengeId
278
- });
279
- if ('error' in res) {
280
- setApiError(res.error.message ?? 'Something went wrong');
281
- } else {
282
- setOtpStep({
283
- ...otpStep,
291
+ const res = await resendAdminLoginOtp({
292
+ challengeId: otpStep.challengeId
293
+ });
294
+ if ('error' in res) {
295
+ setApiError(getApiErrorMessage(res.error));
296
+ } else {
297
+ setOtpStep({
298
+ ...otpStep,
284
299
  expiresAt: res.data.expiresAt,
285
300
  maskedEmail: res.data.maskedEmail
286
301
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivinkv28/strapi-2fa-admin-plugin",
3
- "version": "0.1.9",
3
+ "version": "0.1.12",
4
4
  "description": "Reusable Strapi admin 2FA plugin",
5
5
  "type": "commonjs",
6
6
  "keywords": [