@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.mjs CHANGED
@@ -966,6 +966,89 @@ var _onUnauthorized = null;
966
966
  var _refreshHandler = null;
967
967
  var _refreshInflight = null;
968
968
  var RETRY_MARKER = "X-Auth-Retry";
969
+ function jwtExpMs(token) {
970
+ try {
971
+ const payload = token.split(".")[1];
972
+ if (!payload) return null;
973
+ const json = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
974
+ return typeof json.exp === "number" ? json.exp * 1e3 : null;
975
+ } catch {
976
+ return null;
977
+ }
978
+ }
979
+ __name(jwtExpMs, "jwtExpMs");
980
+ function computeSnapshot() {
981
+ const access = _storage.get(ACCESS_KEY);
982
+ const refresh = _storage.get(REFRESH_KEY);
983
+ const now = Date.now();
984
+ const accessExp = access ? jwtExpMs(access) : null;
985
+ const accessAlive = access !== null && (accessExp === null || accessExp > now);
986
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
987
+ const refreshAlive = refresh !== null && (refreshExp === null || refreshExp > now);
988
+ return {
989
+ status: accessAlive || refreshAlive ? "authenticated" : "anonymous",
990
+ accessExpiresAt: accessExp
991
+ };
992
+ }
993
+ __name(computeSnapshot, "computeSnapshot");
994
+ var SERVER_SNAPSHOT = { status: "anonymous", accessExpiresAt: null };
995
+ var SESSION_SYNC_EVENT = `cfg-auth:changed:${ACCESS_KEY}`;
996
+ var _snapshot = computeSnapshot();
997
+ var _sessionListeners = /* @__PURE__ */ new Set();
998
+ var _expiryTimer = null;
999
+ var _storageListenerInstalled = false;
1000
+ function scheduleExpiryFlip() {
1001
+ if (!isBrowser) return;
1002
+ if (_expiryTimer !== null) {
1003
+ clearTimeout(_expiryTimer);
1004
+ _expiryTimer = null;
1005
+ }
1006
+ if (_sessionListeners.size === 0) return;
1007
+ const now = Date.now();
1008
+ const exps = [];
1009
+ const access = _storage.get(ACCESS_KEY);
1010
+ const refresh = _storage.get(REFRESH_KEY);
1011
+ const accessExp = access ? jwtExpMs(access) : null;
1012
+ const refreshExp = refresh ? jwtExpMs(refresh) : null;
1013
+ if (accessExp !== null && accessExp > now) exps.push(accessExp);
1014
+ if (refreshExp !== null && refreshExp > now) exps.push(refreshExp);
1015
+ if (!exps.length) return;
1016
+ const delay = Math.min(Math.min(...exps) - now + 250, 2147483647);
1017
+ _expiryTimer = setTimeout(notifySessionChanged, delay);
1018
+ }
1019
+ __name(scheduleExpiryFlip, "scheduleExpiryFlip");
1020
+ function notifySessionChanged() {
1021
+ const next = computeSnapshot();
1022
+ const changed = next.status !== _snapshot.status || next.accessExpiresAt !== _snapshot.accessExpiresAt;
1023
+ if (changed) _snapshot = next;
1024
+ scheduleExpiryFlip();
1025
+ if (!changed) return;
1026
+ for (const listener of Array.from(_sessionListeners)) {
1027
+ try {
1028
+ listener();
1029
+ } catch {
1030
+ }
1031
+ }
1032
+ if (isBrowser) {
1033
+ try {
1034
+ window.dispatchEvent(new Event(SESSION_SYNC_EVENT));
1035
+ } catch {
1036
+ }
1037
+ }
1038
+ }
1039
+ __name(notifySessionChanged, "notifySessionChanged");
1040
+ function ensureStorageSync() {
1041
+ if (!isBrowser || _storageListenerInstalled) return;
1042
+ _storageListenerInstalled = true;
1043
+ window.addEventListener("storage", (e) => {
1044
+ if (e.key === null || e.key === ACCESS_KEY || e.key === REFRESH_KEY) {
1045
+ notifySessionChanged();
1046
+ }
1047
+ });
1048
+ window.addEventListener(SESSION_SYNC_EVENT, () => notifySessionChanged());
1049
+ }
1050
+ __name(ensureStorageSync, "ensureStorageSync");
1051
+ var _sessionExpiredHandlers = /* @__PURE__ */ new Set();
969
1052
  var _client = null;
970
1053
  function pushClientConfig() {
971
1054
  if (!_client) return;
@@ -983,6 +1066,7 @@ var auth = {
983
1066
  setStorageMode(mode) {
984
1067
  _storageMode = mode;
985
1068
  _storage = mode === "cookie" ? cookieBackend : localStorageBackend;
1069
+ notifySessionChanged();
986
1070
  },
987
1071
  // ── Bearer token ──────────────────────────────────────────────────
988
1072
  getToken() {
@@ -990,19 +1074,79 @@ var auth = {
990
1074
  },
991
1075
  setToken(token) {
992
1076
  _storage.set(ACCESS_KEY, token);
1077
+ notifySessionChanged();
993
1078
  },
994
1079
  getRefreshToken() {
995
1080
  return _storage.get(REFRESH_KEY);
996
1081
  },
997
1082
  setRefreshToken(token) {
998
1083
  _storage.set(REFRESH_KEY, token);
1084
+ notifySessionChanged();
999
1085
  },
1000
1086
  clearTokens() {
1001
1087
  _storage.set(ACCESS_KEY, null);
1002
1088
  _storage.set(REFRESH_KEY, null);
1089
+ notifySessionChanged();
1003
1090
  },
1091
+ /** Session-aware: token PRESENT and not past its `exp` (or a live refresh
1092
+ * token exists that can mint one). Prefer `getSnapshot().status` in React. */
1004
1093
  isAuthenticated() {
1005
- return _storage.get(ACCESS_KEY) !== null;
1094
+ return computeSnapshot().status === "authenticated";
1095
+ },
1096
+ // ── Session (the ONE write path for login/logout flows) ──────────
1097
+ /**
1098
+ * Persist a token pair atomically. Every login flow (OTP verify, 2FA,
1099
+ * OAuth callback) and the refresh handler should end here — do NOT
1100
+ * scatter `setToken`/`setRefreshToken` pairs through app code.
1101
+ * `refresh: undefined` keeps the current refresh token (access-only
1102
+ * rotation); `refresh: null` explicitly drops it.
1103
+ */
1104
+ setSession(tokens) {
1105
+ _storage.set(ACCESS_KEY, tokens.access);
1106
+ if (tokens.refresh !== void 0) _storage.set(REFRESH_KEY, tokens.refresh);
1107
+ notifySessionChanged();
1108
+ },
1109
+ clearSession() {
1110
+ auth.clearTokens();
1111
+ },
1112
+ // ── Reactive snapshot (for useSyncExternalStore) ──────────────────
1113
+ /**
1114
+ * @example React:
1115
+ * const session = useSyncExternalStore(
1116
+ * auth.subscribe, auth.getSnapshot, auth.getServerSnapshot,
1117
+ * );
1118
+ * const isAuthenticated = session.status === 'authenticated';
1119
+ */
1120
+ getSnapshot() {
1121
+ return _snapshot;
1122
+ },
1123
+ getServerSnapshot() {
1124
+ return SERVER_SNAPSHOT;
1125
+ },
1126
+ subscribe(listener) {
1127
+ ensureStorageSync();
1128
+ _sessionListeners.add(listener);
1129
+ notifySessionChanged();
1130
+ return () => {
1131
+ _sessionListeners.delete(listener);
1132
+ if (_sessionListeners.size === 0 && _expiryTimer !== null) {
1133
+ clearTimeout(_expiryTimer);
1134
+ _expiryTimer = null;
1135
+ }
1136
+ };
1137
+ },
1138
+ /**
1139
+ * Fired on TERMINAL 401 — after the refresh+retry path is exhausted.
1140
+ * The store has already cleared the session before calling back, so
1141
+ * handlers only need to route to login (no clear-then-redirect
1142
+ * ordering to get wrong). Returns an unsubscribe function; multiple
1143
+ * handlers compose (unlike the legacy single-slot `onUnauthorized`).
1144
+ */
1145
+ onSessionExpired(cb) {
1146
+ _sessionExpiredHandlers.add(cb);
1147
+ return () => {
1148
+ _sessionExpiredHandlers.delete(cb);
1149
+ };
1006
1150
  },
1007
1151
  // ── API key ───────────────────────────────────────────────────────
1008
1152
  getApiKey() {
@@ -1084,15 +1228,22 @@ var auth = {
1084
1228
  async function tryRefresh() {
1085
1229
  if (_refreshInflight) return _refreshInflight;
1086
1230
  if (!_refreshHandler) return null;
1087
- const refresh = auth.getRefreshToken();
1088
- if (!refresh) return null;
1231
+ const runRefresh = /* @__PURE__ */ __name(async () => {
1232
+ const refresh = auth.getRefreshToken();
1233
+ if (!refresh) return null;
1234
+ const result = await _refreshHandler(refresh);
1235
+ if (!result?.access) return null;
1236
+ auth.setToken(result.access);
1237
+ if (result.refresh) auth.setRefreshToken(result.refresh);
1238
+ return result.access;
1239
+ }, "runRefresh");
1089
1240
  _refreshInflight = (async () => {
1090
1241
  try {
1091
- const result = await _refreshHandler(refresh);
1092
- if (!result?.access) return null;
1093
- auth.setToken(result.access);
1094
- if (result.refresh) auth.setRefreshToken(result.refresh);
1095
- return result.access;
1242
+ const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
1243
+ if (locks?.request) {
1244
+ return await locks.request(`${REFRESH_KEY}:refresh`, runRefresh);
1245
+ }
1246
+ return await runRefresh();
1096
1247
  } catch {
1097
1248
  return null;
1098
1249
  } finally {
@@ -1102,6 +1253,22 @@ async function tryRefresh() {
1102
1253
  return _refreshInflight;
1103
1254
  }
1104
1255
  __name(tryRefresh, "tryRefresh");
1256
+ function expireSession(response) {
1257
+ auth.clearTokens();
1258
+ for (const cb of Array.from(_sessionExpiredHandlers)) {
1259
+ try {
1260
+ cb(response);
1261
+ } catch {
1262
+ }
1263
+ }
1264
+ if (_onUnauthorized) {
1265
+ try {
1266
+ _onUnauthorized(response);
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ }
1271
+ __name(expireSession, "expireSession");
1105
1272
  function dpopEnabled() {
1106
1273
  try {
1107
1274
  return typeof process !== "undefined" && process.env?.NEXT_PUBLIC_DPOP_ENABLED === "true";
@@ -1231,22 +1398,12 @@ function installAuthOnClient(client2) {
1231
1398
  client2.interceptors.response.use(async (response, request) => {
1232
1399
  if (response.status !== 401) return response;
1233
1400
  if (request.headers.get(RETRY_MARKER)) {
1234
- if (_onUnauthorized) {
1235
- try {
1236
- _onUnauthorized(response);
1237
- } catch {
1238
- }
1239
- }
1401
+ expireSession(response);
1240
1402
  return response;
1241
1403
  }
1242
1404
  const newToken = await tryRefresh();
1243
1405
  if (!newToken) {
1244
- if (_onUnauthorized) {
1245
- try {
1246
- _onUnauthorized(response);
1247
- } catch {
1248
- }
1249
- }
1406
+ expireSession(response);
1250
1407
  return response;
1251
1408
  }
1252
1409
  const retry = request.clone();
@@ -1258,25 +1415,36 @@ function installAuthOnClient(client2) {
1258
1415
  }
1259
1416
  try {
1260
1417
  const retried = await fetch(retry);
1261
- if (retried.status === 401 && _onUnauthorized) {
1262
- try {
1263
- _onUnauthorized(retried);
1264
- } catch {
1265
- }
1266
- }
1418
+ if (retried.status === 401) expireSession(retried);
1267
1419
  return retried;
1268
1420
  } catch {
1269
- if (_onUnauthorized) {
1270
- try {
1271
- _onUnauthorized(response);
1272
- } catch {
1273
- }
1274
- }
1275
1421
  return response;
1276
1422
  }
1277
1423
  });
1278
1424
  }
1279
1425
  __name(installAuthOnClient, "installAuthOnClient");
1426
+ var REFRESH_ENDPOINT = "/cfg/accounts/token/refresh/";
1427
+ auth.setRefreshHandler(async (refresh) => {
1428
+ try {
1429
+ const url = `${auth.getBaseUrl()}${REFRESH_ENDPOINT}`;
1430
+ const headers = { "Content-Type": "application/json" };
1431
+ if (dpopEnabled() && typeof window !== "undefined") {
1432
+ const proof = await _makeDpopProof("POST", url);
1433
+ if (proof) headers["DPoP"] = proof;
1434
+ }
1435
+ const res = await fetch(url, {
1436
+ method: "POST",
1437
+ headers,
1438
+ credentials: auth.getWithCredentials() ? "include" : "same-origin",
1439
+ body: JSON.stringify({ refresh })
1440
+ });
1441
+ if (!res.ok) return null;
1442
+ const data = await res.json();
1443
+ return data?.access ? { access: data.access, refresh: data.refresh ?? refresh } : null;
1444
+ } catch {
1445
+ return null;
1446
+ }
1447
+ });
1280
1448
 
1281
1449
  // src/_api/generated/client.gen.ts
1282
1450
  var client = createClient(createConfig({ baseUrl: "http://localhost:8000" }));