@djangocfg/api 2.1.546 → 2.1.548

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.
Files changed (44) hide show
  1. package/README.md +25 -0
  2. package/dist/auth.cjs +339 -73
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.d.cts +106 -2
  5. package/dist/auth.d.ts +106 -2
  6. package/dist/auth.mjs +339 -73
  7. package/dist/auth.mjs.map +1 -1
  8. package/dist/clients.cjs +18 -0
  9. package/dist/clients.cjs.map +1 -1
  10. package/dist/clients.d.cts +96 -0
  11. package/dist/clients.d.ts +96 -0
  12. package/dist/clients.mjs +18 -0
  13. package/dist/clients.mjs.map +1 -1
  14. package/dist/hooks.cjs.map +1 -1
  15. package/dist/hooks.mjs.map +1 -1
  16. package/dist/index.cjs +18 -0
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +171 -3
  19. package/dist/index.d.ts +171 -3
  20. package/dist/index.mjs +18 -0
  21. package/dist/index.mjs.map +1 -1
  22. package/package.json +2 -2
  23. package/src/_api/generated/_cfg_accounts/hooks/index.ts +1 -0
  24. package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenCreate.ts +64 -0
  25. package/src/_api/generated/_cfg_accounts/openapi.json +157 -0
  26. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginError.ts +13 -0
  27. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginRequest.ts +13 -0
  28. package/src/_api/generated/_cfg_accounts/schemas/PasswordLoginResponse.ts +18 -0
  29. package/src/_api/generated/_cfg_accounts/schemas/index.ts +3 -0
  30. package/src/_api/generated/openapi.json +157 -0
  31. package/src/_api/generated/sdk.gen.ts +20 -1
  32. package/src/_api/generated/types.gen.ts +149 -0
  33. package/src/auth/__tests__/guard.test.ts +65 -0
  34. package/src/auth/__tests__/passwordLogin.test.tsx +239 -0
  35. package/src/auth/context/AccountsContext.tsx +32 -7
  36. package/src/auth/context/AuthContext.tsx +104 -1
  37. package/src/auth/context/types.ts +10 -1
  38. package/src/auth/hooks/useAuthForm.ts +84 -0
  39. package/src/auth/hooks/useAuthFormState.ts +3 -0
  40. package/src/auth/types/form.ts +41 -1
  41. package/src/auth/types/index.ts +2 -0
  42. package/src/auth/utils/analytics.ts +1 -0
  43. package/src/auth/utils/guard.ts +27 -1
  44. package/src/auth/utils/index.ts +1 -0
package/README.md CHANGED
@@ -77,6 +77,31 @@ import {
77
77
  } from '@djangocfg/api/auth';
78
78
  ```
79
79
 
80
+ ### Guard resolvers
81
+
82
+ The "spinner vs redirect vs render" decision is pure and separately testable.
83
+ `useAuthGuard` (and `@djangocfg/layouts`' `PrivateLayout`) just wire React state
84
+ into these:
85
+
86
+ ```typescript
87
+ import {
88
+ resolveGuardIsLoading, // show the preloader?
89
+ shouldRedirectToAuth, // no session → send to the auth form
90
+ isForbidden, // signed in, but lacks the required privilege
91
+ resolveGuardIsAuthenticated,
92
+ type GuardInput,
93
+ } from '@djangocfg/api/auth';
94
+ ```
95
+
96
+ `GuardInput` takes two optional fields for the role gate: `requireStaff` (the
97
+ route needs staff/admin) and `isAdminUser` (from `useAuth()` — the
98
+ `is_staff || is_superuser` fold, never one flag).
99
+
100
+ `isForbidden` is deliberately **not** a redirect condition: the session is
101
+ valid, so the auth form cannot fix it, and redirecting would loop on the saved
102
+ URL. Render a distinct "not authorised" state instead. `resolveGuardIsLoading`
103
+ returns `false` once forbidden, so the preloader never spins on it.
104
+
80
105
  ## Token storage & DPoP
81
106
 
82
107
  The token lives in the shared `auth` store (localStorage by default, switchable
package/dist/auth.cjs CHANGED
@@ -50,6 +50,7 @@ __export(auth_exports, {
50
50
  getCachedProfile: () => getCachedProfile,
51
51
  hasValidCache: () => hasValidCache,
52
52
  isAllowedAuthPath: () => isAllowedAuthPath,
53
+ isForbidden: () => isForbidden,
53
54
  logger: () => logger,
54
55
  normalizePath: () => normalizePath,
55
56
  peekSavedRedirect: () => peekSavedRedirect,
@@ -1044,6 +1045,7 @@ var formatCountdown = /* @__PURE__ */ __name((s) => {
1044
1045
  var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1045
1046
  const [identifier, setIdentifier] = (0, import_react4.useState)(initialIdentifier);
1046
1047
  const [otp, setOtp] = (0, import_react4.useState)("");
1048
+ const [password, setPassword] = (0, import_react4.useState)("");
1047
1049
  const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
1048
1050
  const [acceptedTerms, setAcceptedTerms] = (0, import_react4.useState)(true);
1049
1051
  const [marketingConsent, setMarketingConsent] = (0, import_react4.useState)(null);
@@ -1079,6 +1081,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1079
1081
  // State
1080
1082
  identifier,
1081
1083
  otp,
1084
+ password,
1082
1085
  isLoading,
1083
1086
  acceptedTerms,
1084
1087
  marketingConsent,
@@ -1096,6 +1099,7 @@ var useAuthFormState = /* @__PURE__ */ __name((initialIdentifier = "") => {
1096
1099
  // Handlers
1097
1100
  setIdentifier,
1098
1101
  setOtp,
1102
+ setPassword,
1099
1103
  setAcceptedTerms,
1100
1104
  setMarketingConsent,
1101
1105
  setRememberMe,
@@ -2310,6 +2314,24 @@ var CfgAccountsAuth = class {
2310
2314
  static {
2311
2315
  __name(this, "CfgAccountsAuth");
2312
2316
  }
2317
+ /**
2318
+ * Obtain a JWT pair with email + password.
2319
+ *
2320
+ * Returns the same envelope as OTP verify: either ``requires_2fa=True`` with
2321
+ * a session id, or the token pair plus the user object. Failures are uniform
2322
+ * (401 ``invalid_credentials``) whether the email is unknown, the password is
2323
+ * wrong, or the account is inactive/soft-deleted — no enumeration.
2324
+ */
2325
+ static cfgAccountsTokenCreate(options) {
2326
+ return (options.client ?? client).post({
2327
+ url: "/cfg/accounts/token/",
2328
+ ...options,
2329
+ headers: {
2330
+ "Content-Type": "application/json",
2331
+ ...options.headers
2332
+ }
2333
+ });
2334
+ }
2313
2335
  /**
2314
2336
  * Revoke a refresh token (logout).
2315
2337
  *
@@ -2498,6 +2520,7 @@ var AnalyticsEvent = /* @__PURE__ */ ((AnalyticsEvent2) => {
2498
2520
  AnalyticsEvent2["AUTH_OTP_REQUEST"] = "auth_otp_request";
2499
2521
  AnalyticsEvent2["AUTH_LOGIN_SUCCESS"] = "auth_login_success";
2500
2522
  AnalyticsEvent2["AUTH_OTP_VERIFY_FAIL"] = "auth_otp_verify_fail";
2523
+ AnalyticsEvent2["AUTH_PASSWORD_LOGIN_FAIL"] = "auth_password_login_fail";
2501
2524
  AnalyticsEvent2["AUTH_SESSION_EXPIRED"] = "auth_session_expired";
2502
2525
  AnalyticsEvent2["AUTH_TOKEN_REFRESH"] = "auth_token_refresh";
2503
2526
  AnalyticsEvent2["AUTH_TOKEN_REFRESH_FAIL"] = "auth_token_refresh_fail";
@@ -2937,12 +2960,14 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
2937
2960
  const {
2938
2961
  requestOTP,
2939
2962
  verifyOTP,
2963
+ login,
2940
2964
  getSavedEmail,
2941
2965
  saveEmail
2942
2966
  } = useAuth();
2943
2967
  const {
2944
2968
  identifier,
2945
2969
  otp,
2970
+ password,
2946
2971
  acceptedTerms,
2947
2972
  marketingConsent,
2948
2973
  rememberMe,
@@ -3094,6 +3119,78 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
3094
3119
  e.preventDefault();
3095
3120
  await submitOTP(identifier, otp);
3096
3121
  }, [identifier, otp, submitOTP]);
3122
+ const submitPassword = (0, import_react9.useCallback)(async (submitIdentifier, submitPassword_) => {
3123
+ if (!submitIdentifier || !validateIdentifier2(submitIdentifier)) {
3124
+ const msg = "Please enter a valid email address";
3125
+ setError(msg);
3126
+ onError?.(msg);
3127
+ return false;
3128
+ }
3129
+ if (!submitPassword_) {
3130
+ const msg = "Please enter your password";
3131
+ setError(msg);
3132
+ onError?.(msg);
3133
+ return false;
3134
+ }
3135
+ setIsLoading(true);
3136
+ clearError();
3137
+ try {
3138
+ const result = await login(submitIdentifier, submitPassword_, {
3139
+ redirectUrl,
3140
+ skipRedirect: true,
3141
+ rememberMe
3142
+ });
3143
+ if (result.requires_2fa && result.session_id) {
3144
+ authLogger.info("2FA required after password login");
3145
+ setTwoFactorSessionId(result.session_id);
3146
+ setShouldPrompt2FA(result.should_prompt_2fa || false);
3147
+ setStep("2fa");
3148
+ saveIdentifierToStorage(submitIdentifier);
3149
+ return true;
3150
+ }
3151
+ if (result.success) {
3152
+ saveIdentifierToStorage(submitIdentifier);
3153
+ authLogger.info("Password login successful, showing success screen");
3154
+ setStep("success");
3155
+ onOTPSuccess?.();
3156
+ return true;
3157
+ }
3158
+ if (result.retryAfter) {
3159
+ startRateLimitCountdown(result.retryAfter);
3160
+ clearError();
3161
+ } else {
3162
+ setError(result.message);
3163
+ onError?.(result.message);
3164
+ }
3165
+ return false;
3166
+ } catch {
3167
+ const msg = "An unexpected error occurred";
3168
+ setError(msg);
3169
+ onError?.(msg);
3170
+ return false;
3171
+ } finally {
3172
+ setIsLoading(false);
3173
+ }
3174
+ }, [
3175
+ login,
3176
+ validateIdentifier2,
3177
+ saveIdentifierToStorage,
3178
+ setError,
3179
+ setIsLoading,
3180
+ clearError,
3181
+ startRateLimitCountdown,
3182
+ onOTPSuccess,
3183
+ onError,
3184
+ redirectUrl,
3185
+ rememberMe,
3186
+ setTwoFactorSessionId,
3187
+ setShouldPrompt2FA,
3188
+ setStep
3189
+ ]);
3190
+ const handlePasswordSubmit = (0, import_react9.useCallback)(async (e) => {
3191
+ e.preventDefault();
3192
+ await submitPassword(identifier, password);
3193
+ }, [identifier, password, submitPassword]);
3097
3194
  const handleResendOTP = (0, import_react9.useCallback)(async () => {
3098
3195
  setIsLoading(true);
3099
3196
  clearError();
@@ -3187,6 +3284,7 @@ var useAuthForm = /* @__PURE__ */ __name((options) => {
3187
3284
  // Submit handlers
3188
3285
  handleIdentifierSubmit,
3189
3286
  handleOTPSubmit,
3287
+ handlePasswordSubmit,
3190
3288
  handleResendOTP,
3191
3289
  handleBackToIdentifier,
3192
3290
  forceOTPStep,
@@ -4290,119 +4388,197 @@ __name(useCfgAccountsProfileUpdateUpdate, "useCfgAccountsProfileUpdateUpdate");
4290
4388
  // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts
4291
4389
  var import_mutation15 = __toESM(require("swr/mutation"), 1);
4292
4390
 
4293
- // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4391
+ // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenCreate.ts
4294
4392
  var import_mutation16 = __toESM(require("swr/mutation"), 1);
4295
4393
 
4296
- // src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
4394
+ // src/_api/generated/_cfg_accounts/schemas/PasswordLoginResponse.ts
4297
4395
  var import_zod20 = require("zod");
4298
- var TokenRefreshSchema = import_zod20.z.object({
4299
- access: import_zod20.z.string(),
4300
- refresh: import_zod20.z.string()
4396
+ var PasswordLoginResponseSchema = import_zod20.z.object({
4397
+ requires_2fa: import_zod20.z.boolean().default(false).optional(),
4398
+ session_id: import_zod20.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i).nullable().optional(),
4399
+ refresh: import_zod20.z.string().nullable().optional(),
4400
+ access: import_zod20.z.string().nullable().optional(),
4401
+ user: UserSchema.nullable().optional(),
4402
+ should_prompt_2fa: import_zod20.z.boolean().optional(),
4403
+ persistent_session: import_zod20.z.boolean().optional()
4301
4404
  });
4302
4405
 
4303
- // src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
4406
+ // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenCreate.ts
4407
+ function useCfgAccountsTokenCreate(config) {
4408
+ return (0, import_mutation16.default)(
4409
+ ["cfg_accounts_token_create"],
4410
+ async (_key, { arg }) => {
4411
+ const res = await CfgAccountsAuth.cfgAccountsTokenCreate({ ...arg, throwOnError: true });
4412
+ const data = res.data;
4413
+ const parsed = PasswordLoginResponseSchema.safeParse(data);
4414
+ if (!parsed.success) {
4415
+ console.warn(
4416
+ "[zod] response did not match schema",
4417
+ {
4418
+ operation: "cfg_accounts_token_create",
4419
+ method: "POST",
4420
+ path: "/cfg/accounts/token/",
4421
+ issues: parsed.error.issues,
4422
+ data
4423
+ }
4424
+ );
4425
+ if (typeof window !== "undefined") {
4426
+ try {
4427
+ window.dispatchEvent(
4428
+ new CustomEvent("zod-validation-error", {
4429
+ detail: {
4430
+ operation: "cfg_accounts_token_create",
4431
+ method: "POST",
4432
+ path: "/cfg/accounts/token/",
4433
+ issues: parsed.error.issues,
4434
+ data,
4435
+ timestamp: /* @__PURE__ */ new Date()
4436
+ },
4437
+ bubbles: true,
4438
+ cancelable: false
4439
+ })
4440
+ );
4441
+ } catch {
4442
+ }
4443
+ }
4444
+ return data;
4445
+ }
4446
+ return parsed.data;
4447
+ },
4448
+ config
4449
+ );
4450
+ }
4451
+ __name(useCfgAccountsTokenCreate, "useCfgAccountsTokenCreate");
4452
+
4453
+ // src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenRefreshCreate.ts
4454
+ var import_mutation17 = __toESM(require("swr/mutation"), 1);
4455
+
4456
+ // src/_api/generated/_cfg_accounts/schemas/TokenRefresh.ts
4304
4457
  var import_zod21 = require("zod");
4305
- var APIKeyRequestSchema = import_zod21.z.object({
4306
- key: import_zod21.z.string().min(1),
4307
- reissued_at: import_zod21.z.string().datetime({ offset: true }).nullable(),
4308
- created_at: import_zod21.z.string().datetime({ offset: true })
4458
+ var TokenRefreshSchema = import_zod21.z.object({
4459
+ access: import_zod21.z.string(),
4460
+ refresh: import_zod21.z.string()
4309
4461
  });
4310
4462
 
4311
- // src/_api/generated/_cfg_accounts/schemas/APIKeyTestRequest.ts
4463
+ // src/_api/generated/_cfg_accounts/schemas/APIKeyRequest.ts
4312
4464
  var import_zod22 = require("zod");
4313
- var APIKeyTestRequestSchema = import_zod22.z.object({
4314
- key: import_zod22.z.string().min(1)
4465
+ var APIKeyRequestSchema = import_zod22.z.object({
4466
+ key: import_zod22.z.string().min(1),
4467
+ reissued_at: import_zod22.z.string().datetime({ offset: true }).nullable(),
4468
+ created_at: import_zod22.z.string().datetime({ offset: true })
4315
4469
  });
4316
4470
 
4317
- // src/_api/generated/_cfg_accounts/schemas/CfgUserUpdateRequest.ts
4471
+ // src/_api/generated/_cfg_accounts/schemas/APIKeyTestRequest.ts
4318
4472
  var import_zod23 = require("zod");
4319
- var CfgUserUpdateRequestSchema = import_zod23.z.object({
4320
- first_name: import_zod23.z.string().max(50).optional(),
4321
- last_name: import_zod23.z.string().max(50).optional(),
4322
- company: import_zod23.z.string().max(100).optional(),
4323
- phone: import_zod23.z.string().max(20).optional(),
4324
- position: import_zod23.z.string().max(100).optional(),
4325
- language: import_zod23.z.string().max(10).optional(),
4326
- timezone: import_zod23.z.string().max(64).optional()
4473
+ var APIKeyTestRequestSchema = import_zod23.z.object({
4474
+ key: import_zod23.z.string().min(1)
4327
4475
  });
4328
4476
 
4329
- // src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts
4477
+ // src/_api/generated/_cfg_accounts/schemas/CfgUserUpdateRequest.ts
4330
4478
  var import_zod24 = require("zod");
4331
- var OAuthAuthorizeRequestRequestSchema = import_zod24.z.object({
4332
- redirect_uri: import_zod24.z.string().optional(),
4333
- source_url: import_zod24.z.string().optional()
4479
+ var CfgUserUpdateRequestSchema = import_zod24.z.object({
4480
+ first_name: import_zod24.z.string().max(50).optional(),
4481
+ last_name: import_zod24.z.string().max(50).optional(),
4482
+ company: import_zod24.z.string().max(100).optional(),
4483
+ phone: import_zod24.z.string().max(20).optional(),
4484
+ position: import_zod24.z.string().max(100).optional(),
4485
+ language: import_zod24.z.string().max(10).optional(),
4486
+ timezone: import_zod24.z.string().max(64).optional()
4334
4487
  });
4335
4488
 
4336
- // src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts
4489
+ // src/_api/generated/_cfg_accounts/schemas/OAuthAuthorizeRequestRequest.ts
4337
4490
  var import_zod25 = require("zod");
4338
- var OAuthCallbackRequestRequestSchema = import_zod25.z.object({
4339
- code: import_zod25.z.string().min(10).max(500),
4340
- state: import_zod25.z.string().min(20).max(100),
4491
+ var OAuthAuthorizeRequestRequestSchema = import_zod25.z.object({
4341
4492
  redirect_uri: import_zod25.z.string().optional(),
4342
- remember_me: import_zod25.z.boolean().default(false).optional()
4493
+ source_url: import_zod25.z.string().optional()
4343
4494
  });
4344
4495
 
4345
- // src/_api/generated/_cfg_accounts/schemas/OAuthDisconnectRequestRequest.ts
4496
+ // src/_api/generated/_cfg_accounts/schemas/OAuthCallbackRequestRequest.ts
4346
4497
  var import_zod26 = require("zod");
4347
- var OAuthDisconnectRequestRequestSchema = import_zod26.z.object({
4348
- provider: OAuthProviderEnumSchema
4498
+ var OAuthCallbackRequestRequestSchema = import_zod26.z.object({
4499
+ code: import_zod26.z.string().min(10).max(500),
4500
+ state: import_zod26.z.string().min(20).max(100),
4501
+ redirect_uri: import_zod26.z.string().optional(),
4502
+ remember_me: import_zod26.z.boolean().default(false).optional()
4349
4503
  });
4350
4504
 
4351
- // src/_api/generated/_cfg_accounts/schemas/OAuthError.ts
4505
+ // src/_api/generated/_cfg_accounts/schemas/OAuthDisconnectRequestRequest.ts
4352
4506
  var import_zod27 = require("zod");
4353
- var OAuthErrorSchema = import_zod27.z.object({
4354
- error: import_zod27.z.string(),
4355
- error_description: import_zod27.z.string().optional()
4507
+ var OAuthDisconnectRequestRequestSchema = import_zod27.z.object({
4508
+ provider: OAuthProviderEnumSchema
4356
4509
  });
4357
4510
 
4358
- // src/_api/generated/_cfg_accounts/schemas/OTPErrorResponse.ts
4511
+ // src/_api/generated/_cfg_accounts/schemas/OAuthError.ts
4359
4512
  var import_zod28 = require("zod");
4360
- var OTPErrorResponseSchema = import_zod28.z.object({
4513
+ var OAuthErrorSchema = import_zod28.z.object({
4361
4514
  error: import_zod28.z.string(),
4362
- error_code: import_zod28.z.string().nullable().optional(),
4363
- retry_after: import_zod28.z.number().int().nullable().optional()
4515
+ error_description: import_zod28.z.string().optional()
4364
4516
  });
4365
4517
 
4366
- // src/_api/generated/_cfg_accounts/schemas/OTPRequestRequest.ts
4518
+ // src/_api/generated/_cfg_accounts/schemas/OTPErrorResponse.ts
4367
4519
  var import_zod29 = require("zod");
4368
- var OTPRequestRequestSchema = import_zod29.z.object({
4369
- identifier: import_zod29.z.string().min(1),
4370
- source_url: import_zod29.z.string().optional(),
4371
- marketing_consent: import_zod29.z.boolean().nullable().optional(),
4372
- consent_disclosure_version: import_zod29.z.string().max(64).optional()
4520
+ var OTPErrorResponseSchema = import_zod29.z.object({
4521
+ error: import_zod29.z.string(),
4522
+ error_code: import_zod29.z.string().nullable().optional(),
4523
+ retry_after: import_zod29.z.number().int().nullable().optional()
4373
4524
  });
4374
4525
 
4375
- // src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts
4526
+ // src/_api/generated/_cfg_accounts/schemas/OTPRequestRequest.ts
4376
4527
  var import_zod30 = require("zod");
4377
- var OTPVerifyRequestSchema = import_zod30.z.object({
4528
+ var OTPRequestRequestSchema = import_zod30.z.object({
4378
4529
  identifier: import_zod30.z.string().min(1),
4379
- otp: import_zod30.z.string().min(4).max(4),
4380
4530
  source_url: import_zod30.z.string().optional(),
4381
- remember_me: import_zod30.z.boolean().default(false).optional()
4531
+ marketing_consent: import_zod30.z.boolean().nullable().optional(),
4532
+ consent_disclosure_version: import_zod30.z.string().max(64).optional()
4382
4533
  });
4383
4534
 
4384
- // src/_api/generated/_cfg_accounts/schemas/PatchedCfgUserUpdateRequest.ts
4535
+ // src/_api/generated/_cfg_accounts/schemas/OTPVerifyRequest.ts
4385
4536
  var import_zod31 = require("zod");
4386
- var PatchedCfgUserUpdateRequestSchema = import_zod31.z.object({
4387
- first_name: import_zod31.z.string().max(50).optional(),
4388
- last_name: import_zod31.z.string().max(50).optional(),
4389
- company: import_zod31.z.string().max(100).optional(),
4390
- phone: import_zod31.z.string().max(20).optional(),
4391
- position: import_zod31.z.string().max(100).optional(),
4392
- language: import_zod31.z.string().max(10).optional(),
4393
- timezone: import_zod31.z.string().max(64).optional()
4537
+ var OTPVerifyRequestSchema = import_zod31.z.object({
4538
+ identifier: import_zod31.z.string().min(1),
4539
+ otp: import_zod31.z.string().min(4).max(4),
4540
+ source_url: import_zod31.z.string().optional(),
4541
+ remember_me: import_zod31.z.boolean().default(false).optional()
4394
4542
  });
4395
4543
 
4396
- // src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts
4544
+ // src/_api/generated/_cfg_accounts/schemas/PasswordLoginError.ts
4397
4545
  var import_zod32 = require("zod");
4398
- var TokenBlacklistRequestSchema = import_zod32.z.object({
4399
- refresh: import_zod32.z.string().min(1)
4546
+ var PasswordLoginErrorSchema = import_zod32.z.object({
4547
+ error: import_zod32.z.string(),
4548
+ error_code: import_zod32.z.string(),
4549
+ retry_after: import_zod32.z.number().int().nullable().optional()
4400
4550
  });
4401
4551
 
4402
- // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4552
+ // src/_api/generated/_cfg_accounts/schemas/PasswordLoginRequest.ts
4403
4553
  var import_zod33 = require("zod");
4404
- var TokenRefreshRequestSchema = import_zod33.z.object({
4405
- refresh: import_zod33.z.string().min(1)
4554
+ var PasswordLoginRequestSchema = import_zod33.z.object({
4555
+ remember_me: import_zod33.z.boolean().default(false).optional(),
4556
+ email: import_zod33.z.email(),
4557
+ password: import_zod33.z.string()
4558
+ });
4559
+
4560
+ // src/_api/generated/_cfg_accounts/schemas/PatchedCfgUserUpdateRequest.ts
4561
+ var import_zod34 = require("zod");
4562
+ var PatchedCfgUserUpdateRequestSchema = import_zod34.z.object({
4563
+ first_name: import_zod34.z.string().max(50).optional(),
4564
+ last_name: import_zod34.z.string().max(50).optional(),
4565
+ company: import_zod34.z.string().max(100).optional(),
4566
+ phone: import_zod34.z.string().max(20).optional(),
4567
+ position: import_zod34.z.string().max(100).optional(),
4568
+ language: import_zod34.z.string().max(10).optional(),
4569
+ timezone: import_zod34.z.string().max(64).optional()
4570
+ });
4571
+
4572
+ // src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts
4573
+ var import_zod35 = require("zod");
4574
+ var TokenBlacklistRequestSchema = import_zod35.z.object({
4575
+ refresh: import_zod35.z.string().min(1)
4576
+ });
4577
+
4578
+ // src/_api/generated/_cfg_accounts/schemas/TokenRefreshRequest.ts
4579
+ var import_zod36 = require("zod");
4580
+ var TokenRefreshRequestSchema = import_zod36.z.object({
4581
+ refresh: import_zod36.z.string().min(1)
4406
4582
  });
4407
4583
 
4408
4584
  // src/auth/context/AccountsContext.tsx
@@ -4439,6 +4615,7 @@ function AccountsProvider({ children }) {
4439
4615
  const { trigger: triggerAvatar } = useCfgAccountsProfileAvatarCreate();
4440
4616
  const { trigger: triggerOtpRequest } = useCfgAccountsOtpRequestCreate();
4441
4617
  const { trigger: triggerOtpVerify } = useCfgAccountsOtpVerifyCreate();
4618
+ const { trigger: triggerPasswordLogin } = useCfgAccountsTokenCreate();
4442
4619
  const refreshProfile = (0, import_react15.useCallback)(async (options) => {
4443
4620
  const { callerId, force } = options || {};
4444
4621
  if (!hasApiOrigin()) {
@@ -4488,22 +4665,30 @@ function AccountsProvider({ children }) {
4488
4665
  const result = await triggerOtpRequest({ body: data });
4489
4666
  return result;
4490
4667
  }, [triggerOtpRequest]);
4491
- const verifyOTP = (0, import_react15.useCallback)(async (data) => {
4492
- const result = await triggerOtpVerify({ body: data });
4668
+ const settleLoginEnvelope = (0, import_react15.useCallback)(async (result, callerId) => {
4493
4669
  if (result.requires_2fa && result.session_id) {
4494
4670
  authLogger.info("2FA required, session:", result.session_id);
4495
- return result;
4671
+ return;
4496
4672
  }
4497
4673
  if (result.access && result.refresh) {
4498
4674
  auth.setSession({ access: result.access, refresh: result.refresh });
4499
4675
  try {
4500
- await refreshProfile({ callerId: "verifyOTP", force: true });
4676
+ await refreshProfile({ callerId, force: true });
4501
4677
  } catch (profileError2) {
4502
- authLogger.warn("Profile refresh failed after OTP verify (tokens are saved):", profileError2);
4678
+ authLogger.warn(`Profile refresh failed after ${callerId} (tokens are saved):`, profileError2);
4503
4679
  }
4504
4680
  }
4681
+ }, [refreshProfile]);
4682
+ const verifyOTP = (0, import_react15.useCallback)(async (data) => {
4683
+ const result = await triggerOtpVerify({ body: data });
4684
+ await settleLoginEnvelope(result, "verifyOTP");
4505
4685
  return result;
4506
- }, [triggerOtpVerify, refreshProfile]);
4686
+ }, [triggerOtpVerify, settleLoginEnvelope]);
4687
+ const loginWithPassword = (0, import_react15.useCallback)(async (data) => {
4688
+ const result = await triggerPasswordLogin({ body: data });
4689
+ await settleLoginEnvelope(result, "loginWithPassword");
4690
+ return result;
4691
+ }, [triggerPasswordLogin, settleLoginEnvelope]);
4507
4692
  const logout = (0, import_react15.useCallback)(() => {
4508
4693
  const refresh = auth.getRefreshToken();
4509
4694
  if (refresh) {
@@ -4526,6 +4711,7 @@ function AccountsProvider({ children }) {
4526
4711
  refreshProfile,
4527
4712
  requestOTP,
4528
4713
  verifyOTP,
4714
+ loginWithPassword,
4529
4715
  logout
4530
4716
  }), [
4531
4717
  profile,
@@ -4537,6 +4723,7 @@ function AccountsProvider({ children }) {
4537
4723
  refreshProfile,
4538
4724
  requestOTP,
4539
4725
  verifyOTP,
4726
+ loginWithPassword,
4540
4727
  logout
4541
4728
  ]);
4542
4729
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccountsContext.Provider, { value, children });
@@ -4773,6 +4960,72 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4773
4960
  },
4774
4961
  [setStoredEmail, redirectManager, router]
4775
4962
  );
4963
+ const login = (0, import_react16.useCallback)(
4964
+ async (email, password, options) => {
4965
+ const { redirectUrl, skipRedirect, rememberMe = false } = options || {};
4966
+ try {
4967
+ pendingManualNavRef.current = Boolean(skipRedirect);
4968
+ const result = await accountsRef.current.loginWithPassword({
4969
+ email,
4970
+ password,
4971
+ remember_me: rememberMe
4972
+ });
4973
+ if (result.requires_2fa && result.session_id) {
4974
+ authLogger.info("2FA required, session:", result.session_id);
4975
+ return {
4976
+ success: true,
4977
+ message: "Password verified, 2FA required",
4978
+ requires_2fa: true,
4979
+ session_id: result.session_id,
4980
+ should_prompt_2fa: result.should_prompt_2fa
4981
+ };
4982
+ }
4983
+ if (!result.access || !result.refresh) {
4984
+ authLogger.error("Password login returned invalid response:", result);
4985
+ return { success: false, message: "Invalid login response" };
4986
+ }
4987
+ setStoredEmail(email);
4988
+ Analytics.event("auth_login_success" /* AUTH_LOGIN_SUCCESS */, {
4989
+ category: "auth" /* AUTH */,
4990
+ label: "password"
4991
+ });
4992
+ if (result.user?.id) {
4993
+ Analytics.setUser(String(result.user.id));
4994
+ }
4995
+ if (!skipRedirect) {
4996
+ const saved = redirectManager.hasRedirect() ? redirectManager.getRedirect() : null;
4997
+ if (saved) redirectManager.clearRedirect();
4998
+ const finalRedirectUrl = saved || redirectUrl || routesRef.current.defaultCallback;
4999
+ authLogger.info("Redirecting after auth to:", finalRedirectUrl);
5000
+ router.hardPush(finalRedirectUrl);
5001
+ }
5002
+ return {
5003
+ success: true,
5004
+ message: "Login successful",
5005
+ user: result.user,
5006
+ should_prompt_2fa: result.should_prompt_2fa
5007
+ };
5008
+ } catch (error) {
5009
+ authLogger.error("Password login error:", error);
5010
+ Analytics.event("auth_password_login_fail" /* AUTH_PASSWORD_LOGIN_FAIL */, {
5011
+ category: "auth" /* AUTH */,
5012
+ label: "password"
5013
+ });
5014
+ if (error instanceof APIError) {
5015
+ const body = error.response;
5016
+ return {
5017
+ success: false,
5018
+ statusCode: error.statusCode,
5019
+ message: apiErrorMessage(error),
5020
+ errorCode: body?.error_code,
5021
+ retryAfter: typeof body?.retry_after === "number" ? body.retry_after : void 0
5022
+ };
5023
+ }
5024
+ return { success: false, message: "Failed to sign in" };
5025
+ }
5026
+ },
5027
+ [setStoredEmail, redirectManager, router]
5028
+ );
4776
5029
  const refreshToken = (0, import_react16.useCallback)(async () => {
4777
5030
  const access = await auth.refreshNow();
4778
5031
  if (access) {
@@ -4827,6 +5080,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4827
5080
  clearSavedEmail: clearStoredEmail,
4828
5081
  requestOTP,
4829
5082
  verifyOTP,
5083
+ login,
4830
5084
  refreshToken,
4831
5085
  logout,
4832
5086
  saveRedirectUrl: redirectManager.setRedirect,
@@ -4849,6 +5103,7 @@ var AuthProviderInternal = /* @__PURE__ */ __name(({ children, config }) => {
4849
5103
  clearStoredEmail,
4850
5104
  requestOTP,
4851
5105
  verifyOTP,
5106
+ login,
4852
5107
  refreshToken,
4853
5108
  logout,
4854
5109
  redirectManager,
@@ -4892,6 +5147,10 @@ var defaultAuthState = {
4892
5147
  authLogger.warn("useAuth: verifyOTP called outside AuthProvider");
4893
5148
  return { success: false, message: "AuthProvider not available" };
4894
5149
  }, "verifyOTP"),
5150
+ login: /* @__PURE__ */ __name(async () => {
5151
+ authLogger.warn("useAuth: login called outside AuthProvider");
5152
+ return { success: false, message: "AuthProvider not available" };
5153
+ }, "login"),
4895
5154
  refreshToken: /* @__PURE__ */ __name(async () => {
4896
5155
  authLogger.warn("useAuth: refreshToken called outside AuthProvider");
4897
5156
  return { success: false, message: "AuthProvider not available" };
@@ -4952,9 +5211,16 @@ var formatAuthError = /* @__PURE__ */ __name((error) => {
4952
5211
  }, "formatAuthError");
4953
5212
 
4954
5213
  // src/auth/utils/guard.ts
5214
+ function isForbidden(s) {
5215
+ return Boolean(
5216
+ s.mounted && s.requireAuth && s.requireStaff && !s.authLoading && s.isAuthenticated && !s.isAdminUser
5217
+ );
5218
+ }
5219
+ __name(isForbidden, "isForbidden");
4955
5220
  function resolveGuardIsLoading(s) {
4956
5221
  if (!s.mounted) return true;
4957
5222
  if (!s.requireAuth) return false;
5223
+ if (isForbidden(s)) return false;
4958
5224
  return s.authLoading || s.isRedirecting || !s.isAuthenticated;
4959
5225
  }
4960
5226
  __name(resolveGuardIsLoading, "resolveGuardIsLoading");