@djangocfg/api 2.1.547 → 2.1.549
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 +25 -0
- package/dist/auth.cjs +17 -4
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +17 -2
- package/dist/auth.d.ts +17 -2
- package/dist/auth.mjs +17 -4
- package/dist/auth.mjs.map +1 -1
- package/package.json +2 -2
- package/src/auth/__tests__/guard.test.ts +65 -0
- package/src/auth/context/AccountsContext.tsx +17 -4
- package/src/auth/utils/guard.ts +27 -1
- package/src/auth/utils/index.ts +1 -0
package/dist/auth.d.cts
CHANGED
|
@@ -1232,7 +1232,9 @@ declare const formatAuthError: (error: unknown) => string;
|
|
|
1232
1232
|
*
|
|
1233
1233
|
* Invariant that fixes the bug: once auth is resolved (`authLoading === false`)
|
|
1234
1234
|
* and the user is unauthenticated, `isLoading` must become false so the guard
|
|
1235
|
-
* redirects to the form instead of spinning forever.
|
|
1235
|
+
* redirects to the form instead of spinning forever. The same invariant covers
|
|
1236
|
+
* the forbidden case: a signed-in user without the required privilege must stop
|
|
1237
|
+
* loading too, or the preloader spins on a state no redirect will ever clear.
|
|
1236
1238
|
*/
|
|
1237
1239
|
interface GuardInput {
|
|
1238
1240
|
/** First client render matches SSR (preloader) until mounted, to avoid hydration mismatch. */
|
|
@@ -1245,7 +1247,20 @@ interface GuardInput {
|
|
|
1245
1247
|
isRedirecting: boolean;
|
|
1246
1248
|
/** User currently has a (valid-looking) session. */
|
|
1247
1249
|
isAuthenticated: boolean;
|
|
1250
|
+
/** Route requires staff/admin privileges, not just a session. */
|
|
1251
|
+
requireStaff?: boolean;
|
|
1252
|
+
/** `is_staff || is_superuser`, from `useAuth().isAdminUser`. */
|
|
1253
|
+
isAdminUser?: boolean;
|
|
1248
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* True when the session is valid but lacks the required privilege.
|
|
1257
|
+
*
|
|
1258
|
+
* Distinct from unauthenticated: the fix is a different account, not signing
|
|
1259
|
+
* in, so this must never redirect to the auth form. Redirecting would also
|
|
1260
|
+
* loop — the guard saves the current URL first, so a successful re-login on
|
|
1261
|
+
* the same account returns straight to the wall.
|
|
1262
|
+
*/
|
|
1263
|
+
declare function isForbidden(s: GuardInput): boolean;
|
|
1249
1264
|
/**
|
|
1250
1265
|
* True while the guard should render a preloader rather than the protected
|
|
1251
1266
|
* content. Mirrors useAuthGuard's formula exactly.
|
|
@@ -1355,4 +1370,4 @@ declare const Analytics: {
|
|
|
1355
1370
|
setUser(userId: string | null): void;
|
|
1356
1371
|
};
|
|
1357
1372
|
|
|
1358
|
-
export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
|
|
1373
|
+
export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
|
package/dist/auth.d.ts
CHANGED
|
@@ -1232,7 +1232,9 @@ declare const formatAuthError: (error: unknown) => string;
|
|
|
1232
1232
|
*
|
|
1233
1233
|
* Invariant that fixes the bug: once auth is resolved (`authLoading === false`)
|
|
1234
1234
|
* and the user is unauthenticated, `isLoading` must become false so the guard
|
|
1235
|
-
* redirects to the form instead of spinning forever.
|
|
1235
|
+
* redirects to the form instead of spinning forever. The same invariant covers
|
|
1236
|
+
* the forbidden case: a signed-in user without the required privilege must stop
|
|
1237
|
+
* loading too, or the preloader spins on a state no redirect will ever clear.
|
|
1236
1238
|
*/
|
|
1237
1239
|
interface GuardInput {
|
|
1238
1240
|
/** First client render matches SSR (preloader) until mounted, to avoid hydration mismatch. */
|
|
@@ -1245,7 +1247,20 @@ interface GuardInput {
|
|
|
1245
1247
|
isRedirecting: boolean;
|
|
1246
1248
|
/** User currently has a (valid-looking) session. */
|
|
1247
1249
|
isAuthenticated: boolean;
|
|
1250
|
+
/** Route requires staff/admin privileges, not just a session. */
|
|
1251
|
+
requireStaff?: boolean;
|
|
1252
|
+
/** `is_staff || is_superuser`, from `useAuth().isAdminUser`. */
|
|
1253
|
+
isAdminUser?: boolean;
|
|
1248
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* True when the session is valid but lacks the required privilege.
|
|
1257
|
+
*
|
|
1258
|
+
* Distinct from unauthenticated: the fix is a different account, not signing
|
|
1259
|
+
* in, so this must never redirect to the auth form. Redirecting would also
|
|
1260
|
+
* loop — the guard saves the current URL first, so a successful re-login on
|
|
1261
|
+
* the same account returns straight to the wall.
|
|
1262
|
+
*/
|
|
1263
|
+
declare function isForbidden(s: GuardInput): boolean;
|
|
1249
1264
|
/**
|
|
1250
1265
|
* True while the guard should render a preloader rather than the protected
|
|
1251
1266
|
* content. Mirrors useAuthGuard's formula exactly.
|
|
@@ -1355,4 +1370,4 @@ declare const Analytics: {
|
|
|
1355
1370
|
setUser(userId: string | null): void;
|
|
1356
1371
|
};
|
|
1357
1372
|
|
|
1358
|
-
export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
|
|
1373
|
+
export { AUTH_CONSTANTS, type AccountsContextValue, AccountsProvider, Analytics, AnalyticsCategory, type AnalyticsCategoryType, AnalyticsEvent, type AnalyticsEventType, type AnalyticsSink, type AuthConfig, AuthContext, type AuthContextType, type AuthFormAutoSubmit, type AuthFormReturn, type AuthFormState, type AuthFormStateHandlers, type AuthFormSubmitHandlers, type AuthFormValidation, AuthProvider, type AuthProviderProps, type AuthStep, type DeleteAccountResult, type GuardInput, type OTPRequestConsent, type OTPRequestResult, type PasswordLoginOptions, type PasswordLoginResult, type PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema, type ProfileCacheOptions, type SessionSnapshot, type SessionStatus, type TwoFactorDevice, type TwoFactorSetupData, type UseAuthFormOptions, type UseAuthFormStateReturn, type UseAutoAuthOptions, type UseDeleteAccountReturn, type UseGithubAuthOptions, type UseGithubAuthReturn, type UseTwoFactorOptions, type UseTwoFactorReturn, type UseTwoFactorSetupOptions, type UseTwoFactorSetupReturn, type UseTwoFactorStatusReturn, type UseTwoFactorVerifyOptions, type UseTwoFactorVerifyReturn, type UserProfile, type WebmailLink, authLogger, clearProfileCache, consumeSavedRedirect, decodeBase64, encodeBase64, formatAuthError, getCacheMetadata, getCachedProfile, hasValidCache, isAllowedAuthPath, isForbidden, logger, normalizePath, peekSavedRedirect, resolveGuardIsAuthenticated, resolveGuardIsLoading, setAnalyticsSink, setCachedProfile, shouldRedirectToAuth, useAccountsContext, useAuth, useAuthForm, useAuthFormState, useAuthRedirectManager, useAuthValidation, useAutoAuth, useBase64, useCfgRouter, useDeleteAccount, useGithubAuth, useLocalStorage, useQueryParams, useSession, useSessionStorage, useTwoFactor, useTwoFactorSetup, useTwoFactorStatus, useTwoFactorVerify, validateEmail, validateIdentifier };
|
package/dist/auth.mjs
CHANGED
|
@@ -4540,6 +4540,7 @@ function AccountsProvider({ children }) {
|
|
|
4540
4540
|
const [profileError, setProfileError] = useState10(null);
|
|
4541
4541
|
const profileRef = useRef5(profile);
|
|
4542
4542
|
const isLoadingRef = useRef5(false);
|
|
4543
|
+
const fetchSeqRef = useRef5(0);
|
|
4543
4544
|
useEffect5(() => {
|
|
4544
4545
|
profileRef.current = profile;
|
|
4545
4546
|
}, [profile]);
|
|
@@ -4558,10 +4559,11 @@ function AccountsProvider({ children }) {
|
|
|
4558
4559
|
authLogger.debug(`No API origin configured, skipping profile fetch (caller: ${callerId})`);
|
|
4559
4560
|
return void 0;
|
|
4560
4561
|
}
|
|
4561
|
-
if (isLoadingRef.current) {
|
|
4562
|
+
if (isLoadingRef.current && !force) {
|
|
4562
4563
|
authLogger.debug(`Profile loading in progress, skipping (caller: ${callerId})`);
|
|
4563
4564
|
return profileRef.current;
|
|
4564
4565
|
}
|
|
4566
|
+
const seq = ++fetchSeqRef.current;
|
|
4565
4567
|
setIsLoadingProfile(true);
|
|
4566
4568
|
isLoadingRef.current = true;
|
|
4567
4569
|
setProfileError(null);
|
|
@@ -4569,17 +4571,20 @@ function AccountsProvider({ children }) {
|
|
|
4569
4571
|
authLogger.debug(`Fetching profile from API (caller: ${callerId}, force: ${force})`);
|
|
4570
4572
|
const res = await CfgAccountsProfile.cfgAccountsProfileRetrieve({ throwOnError: true });
|
|
4571
4573
|
const result = res.data;
|
|
4574
|
+
if (seq !== fetchSeqRef.current) return result;
|
|
4572
4575
|
setProfile(result);
|
|
4573
4576
|
profileRef.current = result;
|
|
4574
4577
|
setCachedProfile(result);
|
|
4575
4578
|
return result;
|
|
4576
4579
|
} catch (error) {
|
|
4577
4580
|
const err = error instanceof Error ? error : new Error("Failed to fetch profile");
|
|
4578
|
-
setProfileError(err);
|
|
4581
|
+
if (seq === fetchSeqRef.current) setProfileError(err);
|
|
4579
4582
|
throw err;
|
|
4580
4583
|
} finally {
|
|
4581
|
-
|
|
4582
|
-
|
|
4584
|
+
if (seq === fetchSeqRef.current) {
|
|
4585
|
+
setIsLoadingProfile(false);
|
|
4586
|
+
isLoadingRef.current = false;
|
|
4587
|
+
}
|
|
4583
4588
|
}
|
|
4584
4589
|
}, []);
|
|
4585
4590
|
const updateProfile = useCallback10(async (data) => {
|
|
@@ -5147,9 +5152,16 @@ var formatAuthError = /* @__PURE__ */ __name((error) => {
|
|
|
5147
5152
|
}, "formatAuthError");
|
|
5148
5153
|
|
|
5149
5154
|
// src/auth/utils/guard.ts
|
|
5155
|
+
function isForbidden(s) {
|
|
5156
|
+
return Boolean(
|
|
5157
|
+
s.mounted && s.requireAuth && s.requireStaff && !s.authLoading && s.isAuthenticated && !s.isAdminUser
|
|
5158
|
+
);
|
|
5159
|
+
}
|
|
5160
|
+
__name(isForbidden, "isForbidden");
|
|
5150
5161
|
function resolveGuardIsLoading(s) {
|
|
5151
5162
|
if (!s.mounted) return true;
|
|
5152
5163
|
if (!s.requireAuth) return false;
|
|
5164
|
+
if (isForbidden(s)) return false;
|
|
5153
5165
|
return s.authLoading || s.isRedirecting || !s.isAuthenticated;
|
|
5154
5166
|
}
|
|
5155
5167
|
__name(resolveGuardIsLoading, "resolveGuardIsLoading");
|
|
@@ -5180,6 +5192,7 @@ export {
|
|
|
5180
5192
|
getCachedProfile,
|
|
5181
5193
|
hasValidCache,
|
|
5182
5194
|
isAllowedAuthPath,
|
|
5195
|
+
isForbidden,
|
|
5183
5196
|
logger,
|
|
5184
5197
|
normalizePath,
|
|
5185
5198
|
peekSavedRedirect,
|