@vivinkv28/strapi-2fa-admin-plugin 0.1.10 → 0.1.13

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.
@@ -74,6 +74,16 @@ const runtimeRequire = createRequire(__filename);
74
74
  var strapiSessionAuth = runtimeRequire(resolveSessionAuthPath());
75
75
  const sessionAuth$1 = strapiSessionAuth;
76
76
  const getService = () => strapi.plugin("admin-2fa").service("auth");
77
+ const APPLICATION_ERROR_STATUS = {
78
+ ApplicationError: 400,
79
+ ValidationError: 400,
80
+ UnauthorizedError: 401,
81
+ ForbiddenError: 403,
82
+ NotFoundError: 404,
83
+ PayloadTooLargeError: 413,
84
+ RateLimitError: 429,
85
+ NotImplementedError: 501
86
+ };
77
87
  const setRefreshCookie = (ctx, refreshToken, cookieOptions) => {
78
88
  ctx.cookies.set(sessionAuth$1.REFRESH_COOKIE_NAME, refreshToken, cookieOptions);
79
89
  };
@@ -87,32 +97,69 @@ const getClientIp = (ctx) => {
87
97
  }
88
98
  return String(ctx.request.ip ?? ctx.ip ?? "").trim();
89
99
  };
100
+ const sendApplicationError = (ctx, error2) => {
101
+ const derivedStatus = typeof error2?.status === "number" && error2.status >= 400 && error2.status < 500 ? error2.status : APPLICATION_ERROR_STATUS[error2?.name] ?? 400;
102
+ ctx.status = derivedStatus;
103
+ ctx.body = {
104
+ data: null,
105
+ error: {
106
+ status: derivedStatus,
107
+ name: error2?.name ?? "ApplicationError",
108
+ message: error2?.message ?? "Request failed",
109
+ details: error2?.details ?? {}
110
+ }
111
+ };
112
+ };
90
113
  var auth$3 = {
91
114
  async login(ctx) {
92
- const result = await getService().createChallenge(ctx.request.body ?? {}, {
93
- clientIp: getClientIp(ctx)
94
- });
95
- ctx.body = { data: result };
115
+ try {
116
+ const result = await getService().createChallenge(ctx.request.body ?? {}, {
117
+ clientIp: getClientIp(ctx)
118
+ });
119
+ ctx.body = { data: result };
120
+ } catch (error2) {
121
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
122
+ sendApplicationError(ctx, error2);
123
+ return;
124
+ }
125
+ throw error2;
126
+ }
96
127
  },
97
128
  async resend(ctx) {
98
- const result = await getService().resendChallenge(ctx.request.body ?? {}, {
99
- clientIp: getClientIp(ctx)
100
- });
101
- ctx.body = { data: result };
129
+ try {
130
+ const result = await getService().resendChallenge(ctx.request.body ?? {}, {
131
+ clientIp: getClientIp(ctx)
132
+ });
133
+ ctx.body = { data: result };
134
+ } catch (error2) {
135
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
136
+ sendApplicationError(ctx, error2);
137
+ return;
138
+ }
139
+ throw error2;
140
+ }
102
141
  },
103
142
  async verify(ctx) {
104
- const result = await getService().verifyChallenge(ctx.request.body ?? {}, {
105
- secureRequest: ctx.request.secure,
106
- clientIp: getClientIp(ctx)
107
- });
108
- setRefreshCookie(ctx, result.refreshToken, result.cookieOptions);
109
- ctx.body = {
110
- data: {
111
- token: result.accessToken,
112
- accessToken: result.accessToken,
113
- user: result.user
143
+ try {
144
+ const result = await getService().verifyChallenge(ctx.request.body ?? {}, {
145
+ secureRequest: ctx.request.secure,
146
+ clientIp: getClientIp(ctx)
147
+ });
148
+ setRefreshCookie(ctx, result.refreshToken, result.cookieOptions);
149
+ ctx.body = {
150
+ data: {
151
+ token: result.accessToken,
152
+ accessToken: result.accessToken,
153
+ user: result.user
154
+ }
155
+ };
156
+ } catch (error2) {
157
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
158
+ sendApplicationError(ctx, error2);
159
+ return;
114
160
  }
115
- };
161
+ throw error2;
162
+ }
116
163
  }
117
164
  };
118
165
  const auth$2 = auth$3;
@@ -19149,21 +19196,21 @@ class ForbiddenError extends ApplicationError$1 {
19149
19196
  this.message = message;
19150
19197
  }
19151
19198
  }
19152
- class UnauthorizedError extends ApplicationError$1 {
19199
+ let UnauthorizedError$1 = class UnauthorizedError extends ApplicationError$1 {
19153
19200
  constructor(message = "Unauthorized", details) {
19154
19201
  super(message, details);
19155
19202
  this.name = "UnauthorizedError";
19156
19203
  this.message = message;
19157
19204
  }
19158
- }
19159
- class RateLimitError extends ApplicationError$1 {
19205
+ };
19206
+ let RateLimitError$1 = class RateLimitError extends ApplicationError$1 {
19160
19207
  constructor(message = "Too many requests, please try again later.", details) {
19161
19208
  super(message, details);
19162
19209
  this.name = "RateLimitError";
19163
19210
  this.message = message;
19164
19211
  this.details = details || {};
19165
19212
  }
19166
- }
19213
+ };
19167
19214
  class PayloadTooLargeError extends ApplicationError$1 {
19168
19215
  constructor(message = "Entity too large", details) {
19169
19216
  super(message, details);
@@ -19196,8 +19243,8 @@ const errors$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
19196
19243
  PaginationError,
19197
19244
  PayloadTooLargeError,
19198
19245
  PolicyError,
19199
- RateLimitError,
19200
- UnauthorizedError,
19246
+ RateLimitError: RateLimitError$1,
19247
+ UnauthorizedError: UnauthorizedError$1,
19201
19248
  ValidationError: ValidationError$1,
19202
19249
  YupValidationError
19203
19250
  }, Symbol.toStringTag, { value: "Module" }));
@@ -43795,7 +43842,7 @@ const require$$1 = /* @__PURE__ */ getAugmentedNamespace(dist);
43795
43842
  const crypto = require$$1__default$1.default;
43796
43843
  const { errors } = require$$1;
43797
43844
  const sessionAuth = strapiSessionAuth;
43798
- const { ApplicationError: ApplicationError2, ValidationError: ValidationError3 } = errors;
43845
+ const { ApplicationError: ApplicationError2, RateLimitError: RateLimitError2, UnauthorizedError: UnauthorizedError2, ValidationError: ValidationError3 } = errors;
43799
43846
  const STORE_NAME = "admin-otp-login";
43800
43847
  const STORE_KEY_PREFIX = "challenge:";
43801
43848
  const RATE_LIMIT_KEY_PREFIX = "rate-limit:";
@@ -43914,11 +43961,11 @@ const deleteChallenge = async (store, challengeId) => {
43914
43961
  const getChallenge = async (store, challengeId) => {
43915
43962
  const challenge = await store.get({ key: getStoreKey(challengeId) });
43916
43963
  if (!challenge) {
43917
- throw new ApplicationError2("OTP session not found. Please log in again.");
43964
+ throw new UnauthorizedError2("OTP session not found. Please log in again.");
43918
43965
  }
43919
43966
  if (new Date(challenge.expiresAt).getTime() <= Date.now()) {
43920
43967
  await deleteChallenge(store, challengeId);
43921
- throw new ApplicationError2("OTP expired. Please log in again.");
43968
+ throw new UnauthorizedError2("OTP expired. Please log in again.");
43922
43969
  }
43923
43970
  return challenge;
43924
43971
  };
@@ -43941,7 +43988,7 @@ const registerRateLimitHit = async (store, config2, action, scope, identifier) =
43941
43988
  return;
43942
43989
  }
43943
43990
  if (existing.count >= limit) {
43944
- throw new ApplicationError2("Too many authentication attempts. Please wait a few minutes and try again.");
43991
+ throw new RateLimitError2("Too many authentication attempts. Please wait a few minutes and try again.");
43945
43992
  }
43946
43993
  await store.set({
43947
43994
  key,
@@ -44020,7 +44067,7 @@ var auth$1 = () => ({
44020
44067
  });
44021
44068
  logDuration(config2, "checkCredentials", credentialsStartedAt);
44022
44069
  if (!user) {
44023
- throw new ApplicationError2(info?.message ?? "Invalid credentials");
44070
+ throw new UnauthorizedError2(info?.message ?? "Invalid credentials");
44024
44071
  }
44025
44072
  const challengeId = crypto.randomUUID();
44026
44073
  const code = createOtpCode(config2.otpDigits);
@@ -44069,7 +44116,7 @@ var auth$1 = () => ({
44069
44116
  await registerRateLimitHit(store, config2, "resend", "email", current.email);
44070
44117
  if (current.resendCount >= config2.maxResends) {
44071
44118
  await deleteChallenge(store, challengeId);
44072
- throw new ApplicationError2("Maximum OTP resend attempts exceeded. Please log in again.");
44119
+ throw new RateLimitError2("Maximum OTP resend attempts exceeded. Please log in again.");
44073
44120
  }
44074
44121
  const code = createOtpCode(config2.otpDigits);
44075
44122
  const salt = crypto.randomBytes(16).toString("hex");
@@ -44112,7 +44159,7 @@ var auth$1 = () => ({
44112
44159
  await registerRateLimitHit(store, config2, "verify", "email", challenge.email);
44113
44160
  if (challenge.attempts >= config2.maxAttempts) {
44114
44161
  await deleteChallenge(store, challengeId);
44115
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44162
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44116
44163
  }
44117
44164
  const hashStartedAt = now();
44118
44165
  const computedHash = await createOtpHash(challengeId, code, challenge.salt);
@@ -44125,7 +44172,7 @@ var auth$1 = () => ({
44125
44172
  const nextAttempts = challenge.attempts + 1;
44126
44173
  if (nextAttempts >= config2.maxAttempts) {
44127
44174
  await deleteChallenge(store, challengeId);
44128
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44175
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44129
44176
  }
44130
44177
  const storeStartedAt = now();
44131
44178
  await store.set({
@@ -44139,7 +44186,7 @@ var auth$1 = () => ({
44139
44186
  challengeId,
44140
44187
  attempts: nextAttempts
44141
44188
  });
44142
- throw new ApplicationError2("Invalid OTP code");
44189
+ throw new UnauthorizedError2("Invalid OTP code");
44143
44190
  }
44144
44191
  const deleteStartedAt = now();
44145
44192
  await deleteChallenge(store, challengeId);
@@ -60,6 +60,16 @@ const runtimeRequire = createRequire(__filename);
60
60
  var strapiSessionAuth = runtimeRequire(resolveSessionAuthPath());
61
61
  const sessionAuth$1 = strapiSessionAuth;
62
62
  const getService = () => strapi.plugin("admin-2fa").service("auth");
63
+ const APPLICATION_ERROR_STATUS = {
64
+ ApplicationError: 400,
65
+ ValidationError: 400,
66
+ UnauthorizedError: 401,
67
+ ForbiddenError: 403,
68
+ NotFoundError: 404,
69
+ PayloadTooLargeError: 413,
70
+ RateLimitError: 429,
71
+ NotImplementedError: 501
72
+ };
63
73
  const setRefreshCookie = (ctx, refreshToken, cookieOptions) => {
64
74
  ctx.cookies.set(sessionAuth$1.REFRESH_COOKIE_NAME, refreshToken, cookieOptions);
65
75
  };
@@ -73,32 +83,69 @@ const getClientIp = (ctx) => {
73
83
  }
74
84
  return String(ctx.request.ip ?? ctx.ip ?? "").trim();
75
85
  };
86
+ const sendApplicationError = (ctx, error2) => {
87
+ const derivedStatus = typeof error2?.status === "number" && error2.status >= 400 && error2.status < 500 ? error2.status : APPLICATION_ERROR_STATUS[error2?.name] ?? 400;
88
+ ctx.status = derivedStatus;
89
+ ctx.body = {
90
+ data: null,
91
+ error: {
92
+ status: derivedStatus,
93
+ name: error2?.name ?? "ApplicationError",
94
+ message: error2?.message ?? "Request failed",
95
+ details: error2?.details ?? {}
96
+ }
97
+ };
98
+ };
76
99
  var auth$3 = {
77
100
  async login(ctx) {
78
- const result = await getService().createChallenge(ctx.request.body ?? {}, {
79
- clientIp: getClientIp(ctx)
80
- });
81
- ctx.body = { data: result };
101
+ try {
102
+ const result = await getService().createChallenge(ctx.request.body ?? {}, {
103
+ clientIp: getClientIp(ctx)
104
+ });
105
+ ctx.body = { data: result };
106
+ } catch (error2) {
107
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
108
+ sendApplicationError(ctx, error2);
109
+ return;
110
+ }
111
+ throw error2;
112
+ }
82
113
  },
83
114
  async resend(ctx) {
84
- const result = await getService().resendChallenge(ctx.request.body ?? {}, {
85
- clientIp: getClientIp(ctx)
86
- });
87
- ctx.body = { data: result };
115
+ try {
116
+ const result = await getService().resendChallenge(ctx.request.body ?? {}, {
117
+ clientIp: getClientIp(ctx)
118
+ });
119
+ ctx.body = { data: result };
120
+ } catch (error2) {
121
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
122
+ sendApplicationError(ctx, error2);
123
+ return;
124
+ }
125
+ throw error2;
126
+ }
88
127
  },
89
128
  async verify(ctx) {
90
- const result = await getService().verifyChallenge(ctx.request.body ?? {}, {
91
- secureRequest: ctx.request.secure,
92
- clientIp: getClientIp(ctx)
93
- });
94
- setRefreshCookie(ctx, result.refreshToken, result.cookieOptions);
95
- ctx.body = {
96
- data: {
97
- token: result.accessToken,
98
- accessToken: result.accessToken,
99
- user: result.user
129
+ try {
130
+ const result = await getService().verifyChallenge(ctx.request.body ?? {}, {
131
+ secureRequest: ctx.request.secure,
132
+ clientIp: getClientIp(ctx)
133
+ });
134
+ setRefreshCookie(ctx, result.refreshToken, result.cookieOptions);
135
+ ctx.body = {
136
+ data: {
137
+ token: result.accessToken,
138
+ accessToken: result.accessToken,
139
+ user: result.user
140
+ }
141
+ };
142
+ } catch (error2) {
143
+ if (error2?.name && APPLICATION_ERROR_STATUS[error2.name]) {
144
+ sendApplicationError(ctx, error2);
145
+ return;
100
146
  }
101
- };
147
+ throw error2;
148
+ }
102
149
  }
103
150
  };
104
151
  const auth$2 = auth$3;
@@ -19135,21 +19182,21 @@ class ForbiddenError extends ApplicationError$1 {
19135
19182
  this.message = message;
19136
19183
  }
19137
19184
  }
19138
- class UnauthorizedError extends ApplicationError$1 {
19185
+ let UnauthorizedError$1 = class UnauthorizedError extends ApplicationError$1 {
19139
19186
  constructor(message = "Unauthorized", details) {
19140
19187
  super(message, details);
19141
19188
  this.name = "UnauthorizedError";
19142
19189
  this.message = message;
19143
19190
  }
19144
- }
19145
- class RateLimitError extends ApplicationError$1 {
19191
+ };
19192
+ let RateLimitError$1 = class RateLimitError extends ApplicationError$1 {
19146
19193
  constructor(message = "Too many requests, please try again later.", details) {
19147
19194
  super(message, details);
19148
19195
  this.name = "RateLimitError";
19149
19196
  this.message = message;
19150
19197
  this.details = details || {};
19151
19198
  }
19152
- }
19199
+ };
19153
19200
  class PayloadTooLargeError extends ApplicationError$1 {
19154
19201
  constructor(message = "Entity too large", details) {
19155
19202
  super(message, details);
@@ -19182,8 +19229,8 @@ const errors$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProp
19182
19229
  PaginationError,
19183
19230
  PayloadTooLargeError,
19184
19231
  PolicyError,
19185
- RateLimitError,
19186
- UnauthorizedError,
19232
+ RateLimitError: RateLimitError$1,
19233
+ UnauthorizedError: UnauthorizedError$1,
19187
19234
  ValidationError: ValidationError$1,
19188
19235
  YupValidationError
19189
19236
  }, Symbol.toStringTag, { value: "Module" }));
@@ -43781,7 +43828,7 @@ const require$$1 = /* @__PURE__ */ getAugmentedNamespace(dist);
43781
43828
  const crypto = require$$1$2;
43782
43829
  const { errors } = require$$1;
43783
43830
  const sessionAuth = strapiSessionAuth;
43784
- const { ApplicationError: ApplicationError2, ValidationError: ValidationError3 } = errors;
43831
+ const { ApplicationError: ApplicationError2, RateLimitError: RateLimitError2, UnauthorizedError: UnauthorizedError2, ValidationError: ValidationError3 } = errors;
43785
43832
  const STORE_NAME = "admin-otp-login";
43786
43833
  const STORE_KEY_PREFIX = "challenge:";
43787
43834
  const RATE_LIMIT_KEY_PREFIX = "rate-limit:";
@@ -43900,11 +43947,11 @@ const deleteChallenge = async (store, challengeId) => {
43900
43947
  const getChallenge = async (store, challengeId) => {
43901
43948
  const challenge = await store.get({ key: getStoreKey(challengeId) });
43902
43949
  if (!challenge) {
43903
- throw new ApplicationError2("OTP session not found. Please log in again.");
43950
+ throw new UnauthorizedError2("OTP session not found. Please log in again.");
43904
43951
  }
43905
43952
  if (new Date(challenge.expiresAt).getTime() <= Date.now()) {
43906
43953
  await deleteChallenge(store, challengeId);
43907
- throw new ApplicationError2("OTP expired. Please log in again.");
43954
+ throw new UnauthorizedError2("OTP expired. Please log in again.");
43908
43955
  }
43909
43956
  return challenge;
43910
43957
  };
@@ -43927,7 +43974,7 @@ const registerRateLimitHit = async (store, config2, action, scope, identifier) =
43927
43974
  return;
43928
43975
  }
43929
43976
  if (existing.count >= limit) {
43930
- throw new ApplicationError2("Too many authentication attempts. Please wait a few minutes and try again.");
43977
+ throw new RateLimitError2("Too many authentication attempts. Please wait a few minutes and try again.");
43931
43978
  }
43932
43979
  await store.set({
43933
43980
  key,
@@ -44006,7 +44053,7 @@ var auth$1 = () => ({
44006
44053
  });
44007
44054
  logDuration(config2, "checkCredentials", credentialsStartedAt);
44008
44055
  if (!user) {
44009
- throw new ApplicationError2(info?.message ?? "Invalid credentials");
44056
+ throw new UnauthorizedError2(info?.message ?? "Invalid credentials");
44010
44057
  }
44011
44058
  const challengeId = crypto.randomUUID();
44012
44059
  const code = createOtpCode(config2.otpDigits);
@@ -44055,7 +44102,7 @@ var auth$1 = () => ({
44055
44102
  await registerRateLimitHit(store, config2, "resend", "email", current.email);
44056
44103
  if (current.resendCount >= config2.maxResends) {
44057
44104
  await deleteChallenge(store, challengeId);
44058
- throw new ApplicationError2("Maximum OTP resend attempts exceeded. Please log in again.");
44105
+ throw new RateLimitError2("Maximum OTP resend attempts exceeded. Please log in again.");
44059
44106
  }
44060
44107
  const code = createOtpCode(config2.otpDigits);
44061
44108
  const salt = crypto.randomBytes(16).toString("hex");
@@ -44098,7 +44145,7 @@ var auth$1 = () => ({
44098
44145
  await registerRateLimitHit(store, config2, "verify", "email", challenge.email);
44099
44146
  if (challenge.attempts >= config2.maxAttempts) {
44100
44147
  await deleteChallenge(store, challengeId);
44101
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44148
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44102
44149
  }
44103
44150
  const hashStartedAt = now();
44104
44151
  const computedHash = await createOtpHash(challengeId, code, challenge.salt);
@@ -44111,7 +44158,7 @@ var auth$1 = () => ({
44111
44158
  const nextAttempts = challenge.attempts + 1;
44112
44159
  if (nextAttempts >= config2.maxAttempts) {
44113
44160
  await deleteChallenge(store, challengeId);
44114
- throw new ApplicationError2("Maximum OTP attempts exceeded. Please log in again.");
44161
+ throw new RateLimitError2("Maximum OTP attempts exceeded. Please log in again.");
44115
44162
  }
44116
44163
  const storeStartedAt = now();
44117
44164
  await store.set({
@@ -44125,7 +44172,7 @@ var auth$1 = () => ({
44125
44172
  challengeId,
44126
44173
  attempts: nextAttempts
44127
44174
  });
44128
- throw new ApplicationError2("Invalid OTP code");
44175
+ throw new UnauthorizedError2("Invalid OTP code");
44129
44176
  }
44130
44177
  const deleteStartedAt = now();
44131
44178
  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.10",
3
+ "version": "0.1.13",
4
4
  "description": "Reusable Strapi admin 2FA plugin",
5
5
  "type": "commonjs",
6
6
  "keywords": [