@djangocfg/api 2.1.455 → 2.1.457

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 (89) hide show
  1. package/dist/auth.cjs +1611 -2148
  2. package/dist/auth.cjs.map +1 -1
  3. package/dist/auth.d.cts +37 -82
  4. package/dist/auth.d.ts +37 -82
  5. package/dist/auth.mjs +1581 -2118
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/chunk-2G67QRNU.mjs +2096 -0
  8. package/dist/chunk-2G67QRNU.mjs.map +1 -0
  9. package/dist/chunk-32SRQGAC.mjs +2109 -0
  10. package/dist/chunk-32SRQGAC.mjs.map +1 -0
  11. package/dist/chunk-4BPRCONN.mjs +2098 -0
  12. package/dist/chunk-4BPRCONN.mjs.map +1 -0
  13. package/dist/chunk-5UZ2Z323.mjs +2021 -0
  14. package/dist/chunk-5UZ2Z323.mjs.map +1 -0
  15. package/dist/chunk-7KFTJXNM.mjs +2097 -0
  16. package/dist/chunk-7KFTJXNM.mjs.map +1 -0
  17. package/dist/chunk-BK4K5CVT.mjs +2019 -0
  18. package/dist/chunk-BK4K5CVT.mjs.map +1 -0
  19. package/dist/chunk-JLPJZ6WB.mjs +2020 -0
  20. package/dist/chunk-JLPJZ6WB.mjs.map +1 -0
  21. package/dist/chunk-TVU6PYJH.mjs +2032 -0
  22. package/dist/chunk-TVU6PYJH.mjs.map +1 -0
  23. package/dist/clients.cjs +292 -47
  24. package/dist/clients.cjs.map +1 -1
  25. package/dist/clients.d.cts +23 -0
  26. package/dist/clients.d.ts +23 -0
  27. package/dist/clients.mjs +292 -47
  28. package/dist/clients.mjs.map +1 -1
  29. package/dist/hooks.cjs +200 -32
  30. package/dist/hooks.cjs.map +1 -1
  31. package/dist/hooks.mjs +200 -32
  32. package/dist/hooks.mjs.map +1 -1
  33. package/dist/index.cjs +292 -47
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +81 -4
  36. package/dist/index.d.ts +81 -4
  37. package/dist/index.mjs +292 -47
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/sdk.gen-2SQOPTWJ.mjs +26 -0
  40. package/dist/sdk.gen-2SQOPTWJ.mjs.map +1 -0
  41. package/dist/sdk.gen-HSGK4C5S.mjs +26 -0
  42. package/dist/sdk.gen-HSGK4C5S.mjs.map +1 -0
  43. package/dist/sdk.gen-NUK2VGHO.mjs +25 -0
  44. package/dist/sdk.gen-NUK2VGHO.mjs.map +1 -0
  45. package/dist/sdk.gen-O4KAQUZB.mjs +26 -0
  46. package/dist/sdk.gen-O4KAQUZB.mjs.map +1 -0
  47. package/dist/sdk.gen-PPAVSBNT.mjs +26 -0
  48. package/dist/sdk.gen-PPAVSBNT.mjs.map +1 -0
  49. package/dist/sdk.gen-XLHLOCJ2.mjs +25 -0
  50. package/dist/sdk.gen-XLHLOCJ2.mjs.map +1 -0
  51. package/dist/sdk.gen-ZOE6NQAL.mjs +26 -0
  52. package/dist/sdk.gen-ZOE6NQAL.mjs.map +1 -0
  53. package/dist/sdk.gen-ZT7LGJVO.mjs +26 -0
  54. package/dist/sdk.gen-ZT7LGJVO.mjs.map +1 -0
  55. package/package.json +2 -2
  56. package/src/_api/generated/_cfg_accounts/hooks/index.ts +1 -0
  57. package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsTokenBlacklistCreate.ts +28 -0
  58. package/src/_api/generated/_cfg_accounts/openapi.json +99 -0
  59. package/src/_api/generated/_cfg_accounts/schemas/TokenBlacklistRequest.ts +11 -0
  60. package/src/_api/generated/_cfg_accounts/schemas/index.ts +1 -0
  61. package/src/_api/generated/_cfg_centrifugo/openapi.json +9 -0
  62. package/src/_api/generated/_cfg_totp/openapi.json +33 -0
  63. package/src/_api/generated/helpers/auth.ts +288 -31
  64. package/src/_api/generated/openapi.json +129 -0
  65. package/src/_api/generated/sdk.gen.ts +94 -16
  66. package/src/_api/generated/types.gen.ts +18 -0
  67. package/src/auth/__tests__/guard.test.ts +0 -31
  68. package/src/auth/constants.ts +6 -0
  69. package/src/auth/context/AccountsContext.tsx +14 -24
  70. package/src/auth/context/AuthContext.tsx +263 -607
  71. package/src/auth/context/types.ts +13 -0
  72. package/src/auth/hooks/index.ts +8 -5
  73. package/src/auth/hooks/useAuthRedirect.ts +17 -0
  74. package/src/auth/hooks/useGithubAuth.ts +3 -3
  75. package/src/auth/hooks/useSession.ts +22 -0
  76. package/src/auth/hooks/useTwoFactor.ts +8 -6
  77. package/src/auth/middlewares/index.ts +5 -6
  78. package/src/auth/utils/guard.ts +0 -22
  79. package/src/auth/utils/index.ts +0 -2
  80. package/src/_api/generated/_cfg_accounts/events.ts +0 -198
  81. package/src/_api/generated/_cfg_centrifugo/events.ts +0 -198
  82. package/src/_api/generated/_cfg_totp/events.ts +0 -198
  83. package/src/auth/__tests__/jwt.test.ts +0 -119
  84. package/src/auth/__tests__/sessionBootstrap.test.ts +0 -111
  85. package/src/auth/__tests__/useTokenRefresh.dom.test.tsx +0 -167
  86. package/src/auth/hooks/useAuthGuard.ts +0 -35
  87. package/src/auth/hooks/useTokenRefresh.ts +0 -131
  88. package/src/auth/refreshHandler.ts +0 -79
  89. package/src/auth/utils/jwt.ts +0 -66
package/dist/hooks.cjs CHANGED
@@ -999,6 +999,89 @@ var _onUnauthorized = null;
999
999
  var _refreshHandler = null;
1000
1000
  var _refreshInflight = null;
1001
1001
  var RETRY_MARKER = "X-Auth-Retry";
1002
+ function jwtExpMs(token) {
1003
+ try {
1004
+ const payload = token.split(".")[1];
1005
+ if (!payload) return null;
1006
+ const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
1007
+ return typeof json.exp === "number" ? json.exp * 1e3 : null;
1008
+ } catch {
1009
+ return null;
1010
+ }
1011
+ }
1012
+ __name(jwtExpMs, "jwtExpMs");
1013
+ function computeSnapshot() {
1014
+ const access = _storage.get(ACCESS_KEY);
1015
+ const refresh = _storage.get(REFRESH_KEY);
1016
+ const now = Date.now();
1017
+ const accessExp = access ? jwtExpMs(access) : null;
1018
+ const accessAlive = access !== null && (accessExp === null || accessExp > now);
1019
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
1020
+ const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
1021
+ return {
1022
+ status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
1023
+ accessExpiresAt: accessExp
1024
+ };
1025
+ }
1026
+ __name(computeSnapshot, "computeSnapshot");
1027
+ var SERVER_SNAPSHOT = { status: "anonymous", accessExpiresAt: null };
1028
+ var SESSION_SYNC_EVENT = `cfg-auth:changed:${ACCESS_KEY}`;
1029
+ var _snapshot = computeSnapshot();
1030
+ var _sessionListeners = /* @__PURE__ */ new Set();
1031
+ var _expiryTimer = null;
1032
+ var _storageListenerInstalled = false;
1033
+ function scheduleExpiryFlip() {
1034
+ if (!isBrowser) return;
1035
+ if (_expiryTimer !== null) {
1036
+ clearTimeout(_expiryTimer);
1037
+ _expiryTimer = null;
1038
+ }
1039
+ if (_sessionListeners.size === 0) return;
1040
+ const now = Date.now();
1041
+ const exps = [];
1042
+ const access = _storage.get(ACCESS_KEY);
1043
+ const refresh = _storage.get(REFRESH_KEY);
1044
+ const accessExp = access ? jwtExpMs(access) : null;
1045
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
1046
+ if (accessExp !== null && accessExp > now) exps.push(accessExp);
1047
+ if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
1048
+ if (!exps.length) return;
1049
+ const delay = Math.min(Math.min(...exps) - now + 250, 2147483647);
1050
+ _expiryTimer = setTimeout(notifySessionChanged, delay);
1051
+ }
1052
+ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
1053
+ function notifySessionChanged() {
1054
+ const next = computeSnapshot();
1055
+ const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
1056
+ if (changed) _snapshot = next;
1057
+ scheduleExpiryFlip();
1058
+ if (!changed) return;
1059
+ for (const listener of Array.from(_sessionListeners)) {
1060
+ try {
1061
+ listener();
1062
+ } catch {
1063
+ }
1064
+ }
1065
+ if (isBrowser) {
1066
+ try {
1067
+ window.dispatchEvent(new Event(SESSION_SYNC_EVENT));
1068
+ } catch {
1069
+ }
1070
+ }
1071
+ }
1072
+ __name(notifySessionChanged, "notifySessionChanged");
1073
+ function ensureStorageSync() {
1074
+ if (!isBrowser || _storageListenerInstalled) return;
1075
+ _storageListenerInstalled = true;
1076
+ window.addEventListener("storage", (e) => {
1077
+ if (e.key === null || e.key === ACCESS_KEY || e.key === REFRESH_KEY) {
1078
+ notifySessionChanged();
1079
+ }
1080
+ });
1081
+ window.addEventListener(SESSION_SYNC_EVENT, () => notifySessionChanged());
1082
+ }
1083
+ __name(ensureStorageSync, "ensureStorageSync");
1084
+ var _sessionExpiredHandlers = /* @__PURE__ */ new Set();
1002
1085
  var _client = null;
1003
1086
  function pushClientConfig() {
1004
1087
  if (!_client) return;
@@ -1016,6 +1099,7 @@ var auth = {
1016
1099
  setStorageMode(mode) {
1017
1100
  _storageMode = mode;
1018
1101
  _storage = mode === "cookie" ? cookieBackend : localStorageBackend;
1102
+ notifySessionChanged();
1019
1103
  },
1020
1104
  // ── Bearer token ──────────────────────────────────────────────────
1021
1105
  getToken() {
@@ -1023,19 +1107,79 @@ var auth = {
1023
1107
  },
1024
1108
  setToken(token) {
1025
1109
  _storage.set(ACCESS_KEY, token);
1110
+ notifySessionChanged();
1026
1111
  },
1027
1112
  getRefreshToken() {
1028
1113
  return _storage.get(REFRESH_KEY);
1029
1114
  },
1030
1115
  setRefreshToken(token) {
1031
1116
  _storage.set(REFRESH_KEY, token);
1117
+ notifySessionChanged();
1032
1118
  },
1033
1119
  clearTokens() {
1034
1120
  _storage.set(ACCESS_KEY, null);
1035
1121
  _storage.set(REFRESH_KEY, null);
1122
+ notifySessionChanged();
1036
1123
  },
1124
+ /** Session-aware: token PRESENT and not past its `exp` (or a live refresh
1125
+ * token exists that can mint one). Prefer `getSnapshot().status` in React. */
1037
1126
  isAuthenticated() {
1038
- return _storage.get(ACCESS_KEY) !== null;
1127
+ return computeSnapshot().status === "authenticated";
1128
+ },
1129
+ // ── Session (the ONE write path for login/logout flows) ──────────
1130
+ /**
1131
+ * Persist a token pair atomically. Every login flow (OTP verify, 2FA,
1132
+ * OAuth callback) and the refresh handler should end here — do NOT
1133
+ * scatter `setToken`/`setRefreshToken` pairs through app code.
1134
+ * `refresh: undefined` keeps the current refresh token (access-only
1135
+ * rotation); `refresh: null` explicitly drops it.
1136
+ */
1137
+ setSession(tokens) {
1138
+ _storage.set(ACCESS_KEY, tokens.access);
1139
+ if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
1140
+ notifySessionChanged();
1141
+ },
1142
+ clearSession() {
1143
+ auth.clearTokens();
1144
+ },
1145
+ // ── Reactive snapshot (for useSyncExternalStore) ──────────────────
1146
+ /**
1147
+ * @example React:
1148
+ * const session = useSyncExternalStore(
1149
+ * auth.subscribe, auth.getSnapshot, auth.getServerSnapshot,
1150
+ * );
1151
+ * const isAuthenticated = session.status === 'authenticated';
1152
+ */
1153
+ getSnapshot() {
1154
+ return _snapshot;
1155
+ },
1156
+ getServerSnapshot() {
1157
+ return SERVER_SNAPSHOT;
1158
+ },
1159
+ subscribe(listener) {
1160
+ ensureStorageSync();
1161
+ _sessionListeners.add(listener);
1162
+ notifySessionChanged();
1163
+ return () => {
1164
+ _sessionListeners.delete(listener);
1165
+ if (_sessionListeners.size === 0 && _expiryTimer !== null) {
1166
+ clearTimeout(_expiryTimer);
1167
+ _expiryTimer = null;
1168
+ }
1169
+ };
1170
+ },
1171
+ /**
1172
+ * Fired on TERMINAL 401 — after the refresh+retry path is exhausted.
1173
+ * The store has already cleared the session before calling back, so
1174
+ * handlers only need to route to login (no clear-then-redirect
1175
+ * ordering to get wrong). Returns an unsubscribe function; multiple
1176
+ * handlers compose (unlike the legacy single-slot `onUnauthorized`).
1177
+ */
1178
+ onSessionExpired(cb) {
1179
+ _sessionExpiredHandlers.add(cb);
1180
+ return () => {
1181
+ _sessionExpiredHandlers.delete(cb);
1182
+ };
1039
1183
  },
1040
1184
  // ── API key ───────────────────────────────────────────────────────
1041
1185
  getApiKey() {
@@ -1117,15 +1261,22 @@ var auth = {
1117
1261
  async function tryRefresh() {
1118
1262
  if (_refreshInflight) return _refreshInflight;
1119
1263
  if (!_refreshHandler) return null;
1120
- const refresh = auth.getRefreshToken();
1121
- if (!refresh) return null;
1264
+ const runRefresh = /* @__PURE__ */ __name(async () => {
1265
+ const refresh = auth.getRefreshToken();
1266
+ if (!refresh) return null;
1267
+ const result = await _refreshHandler(refresh);
1268
+ if (!result?.access) return null;
1269
+ auth.setToken(result.access);
1270
+ if (result.refresh) auth.setRefreshToken(result.refresh);
1271
+ return result.access;
1272
+ }, "runRefresh");
1122
1273
  _refreshInflight = (async () => {
1123
1274
  try {
1124
- const result = await _refreshHandler(refresh);
1125
- if (!result?.access) return null;
1126
- auth.setToken(result.access);
1127
- if (result.refresh) auth.setRefreshToken(result.refresh);
1128
- return result.access;
1275
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
1276
+ if (locks?.request) {
1277
+ return await locks.request(`${REFRESH_KEY}:refresh`, runRefresh);
1278
+ }
1279
+ return await runRefresh();
1129
1280
  } catch {
1130
1281
  return null;
1131
1282
  } finally {
@@ -1135,6 +1286,22 @@ async function tryRefresh() {
1135
1286
  return _refreshInflight;
1136
1287
  }
1137
1288
  __name(tryRefresh, "tryRefresh");
1289
+ function expireSession(response) {
1290
+ auth.clearTokens();
1291
+ for (const cb of Array.from(_sessionExpiredHandlers)) {
1292
+ try {
1293
+ cb(response);
1294
+ } catch {
1295
+ }
1296
+ }
1297
+ if (_onUnauthorized) {
1298
+ try {
1299
+ _onUnauthorized(response);
1300
+ } catch {
1301
+ }
1302
+ }
1303
+ }
1304
+ __name(expireSession, "expireSession");
1138
1305
  function dpopEnabled() {
1139
1306
  try {
1140
1307
  return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
@@ -1264,22 +1431,12 @@ function installAuthOnClient(client2) {
1264
1431
  client2.interceptors.response.use(async (response, request) => {
1265
1432
  if (response.status !== 401) return response;
1266
1433
  if (request.headers.get(RETRY_MARKER)) {
1267
- if (_onUnauthorized) {
1268
- try {
1269
- _onUnauthorized(response);
1270
- } catch {
1271
- }
1272
- }
1434
+ expireSession(response);
1273
1435
  return response;
1274
1436
  }
1275
1437
  const newToken = await tryRefresh();
1276
1438
  if (!newToken) {
1277
- if (_onUnauthorized) {
1278
- try {
1279
- _onUnauthorized(response);
1280
- } catch {
1281
- }
1282
- }
1439
+ expireSession(response);
1283
1440
  return response;
1284
1441
  }
1285
1442
  const retry = request.clone();
@@ -1291,25 +1448,36 @@ function installAuthOnClient(client2) {
1291
1448
  }
1292
1449
  try {
1293
1450
  const retried = await fetch(retry);
1294
- if (retried.status === 401 && _onUnauthorized) {
1295
- try {
1296
- _onUnauthorized(retried);
1297
- } catch {
1298
- }
1299
- }
1451
+ if (retried.status === 401) expireSession(retried);
1300
1452
  return retried;
1301
1453
  } catch {
1302
- if (_onUnauthorized) {
1303
- try {
1304
- _onUnauthorized(response);
1305
- } catch {
1306
- }
1307
- }
1308
1454
  return response;
1309
1455
  }
1310
1456
  });
1311
1457
  }
1312
1458
  __name(installAuthOnClient, "installAuthOnClient");
1459
+ var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
1460
+ auth.setRefreshHandler(async (refresh) => {
1461
+ try {
1462
+ const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
1463
+ const headers = { "Content-Type": "application/json" };
1464
+ if (dpopEnabled() && typeof window !== "undefined") {
1465
+ const proof = await _makeDpopProof("POST", url);
1466
+ if (proof) headers["DPoP"] = proof;
1467
+ }
1468
+ const res = await fetch(url, {
1469
+ method: "POST",
1470
+ headers,
1471
+ credentials: auth.getWithCredentials() ? "include" : "same-origin",
1472
+ body: JSON.stringify({ refresh })
1473
+ });
1474
+ if (!res.ok) return null;
1475
+ const data = await res.json();
1476
+ return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
1477
+ } catch {
1478
+ return null;
1479
+ }
1480
+ });
1313
1481
 
1314
1482
  // src/_api/generated/client.gen.ts
1315
1483
  var client = createClient(createConfig({ baseUrl: "http://localhost:8000" }));