bugfix: organization not selected problem fixed. being logged out too often fixed.
This commit is contained in:
@@ -11,6 +11,11 @@ import {
|
||||
} from '@/lib/auth/rememberMe';
|
||||
import { User, Organization } from '@/types/organization';
|
||||
import { isAppLocale, getLocaleFromPathname } from '@/i18n/routing';
|
||||
import {
|
||||
clearAccessTokenExpiresAt,
|
||||
rememberAccessTokenExpiresAt,
|
||||
startProactiveSessionRefresh,
|
||||
} from '@/lib/auth/proactiveRefresh';
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
@@ -91,6 +96,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
if (response.success) {
|
||||
const { user: userData, organizations: orgs } = normalizeProfilePayload(response.data);
|
||||
rememberAccessTokenExpiresAt(
|
||||
(response.data as { accessTokenExpiresAt?: string }).accessTokenExpiresAt,
|
||||
);
|
||||
|
||||
setUser(userData);
|
||||
setOrganizations(orgs);
|
||||
@@ -99,17 +107,29 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
if (storedOrgId && orgs.length > 0) {
|
||||
const org = orgs.find(o => o.id === storedOrgId);
|
||||
if (org) {
|
||||
setCurrentOrganization(org);
|
||||
// Ensure cookie token carries organizationId for org-scoped APIs.
|
||||
await authApi.selectOrganization(org.id);
|
||||
const selected = await authApi.selectOrganization(org.id);
|
||||
setCurrentOrganization({
|
||||
id: selected.data.organization.id,
|
||||
name: selected.data.organization.name,
|
||||
type: selected.data.organization.type as Organization['type'],
|
||||
isOwner: Boolean(selected.data.organization.isOwner),
|
||||
permissions: selected.data.organization.permissions,
|
||||
plan: selected.data.organization.plan,
|
||||
});
|
||||
} else {
|
||||
setCurrentOrganization(null);
|
||||
}
|
||||
} else if (orgs.length === 1 && userData) {
|
||||
setCurrentOrganization(orgs[0]);
|
||||
const selected = await authApi.selectOrganization(orgs[0].id);
|
||||
localStorage.setItem('currentOrganizationId', orgs[0].id);
|
||||
// Keep JWT in sync with selected org even for single-org users.
|
||||
await authApi.selectOrganization(orgs[0].id);
|
||||
setCurrentOrganization({
|
||||
id: selected.data.organization.id,
|
||||
name: selected.data.organization.name,
|
||||
type: selected.data.organization.type as Organization['type'],
|
||||
isOwner: Boolean(selected.data.organization.isOwner),
|
||||
permissions: selected.data.organization.permissions,
|
||||
plan: selected.data.organization.plan,
|
||||
});
|
||||
} else {
|
||||
setCurrentOrganization(null);
|
||||
}
|
||||
@@ -119,6 +139,61 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
(err as { statusCode?: number })?.statusCode ??
|
||||
(err as { response?: { status?: number } })?.response?.status;
|
||||
|
||||
// Access token may have expired while refresh cookie is still valid (e.g. JWT_EXPIRES_IN=15m).
|
||||
if (status === 401) {
|
||||
try {
|
||||
const refreshResult = await authApi.refreshSessionFromCookies();
|
||||
rememberAccessTokenExpiresAt(refreshResult.data?.accessTokenExpiresAt);
|
||||
const orgId = localStorage.getItem('currentOrganizationId');
|
||||
if (orgId) {
|
||||
await authApi.selectOrganization(orgId);
|
||||
}
|
||||
const retry = await authApi.getProfile();
|
||||
if (retry.success) {
|
||||
const { user: userData, organizations: orgs } = normalizeProfilePayload(retry.data);
|
||||
rememberAccessTokenExpiresAt(
|
||||
(retry.data as { accessTokenExpiresAt?: string }).accessTokenExpiresAt,
|
||||
);
|
||||
setUser(userData);
|
||||
setOrganizations(orgs);
|
||||
|
||||
const storedOrgId = localStorage.getItem('currentOrganizationId');
|
||||
if (storedOrgId && orgs.length > 0) {
|
||||
const org = orgs.find((o) => o.id === storedOrgId);
|
||||
if (org) {
|
||||
const selected = await authApi.selectOrganization(org.id);
|
||||
setCurrentOrganization({
|
||||
id: selected.data.organization.id,
|
||||
name: selected.data.organization.name,
|
||||
type: selected.data.organization.type as Organization['type'],
|
||||
isOwner: Boolean(selected.data.organization.isOwner),
|
||||
permissions: selected.data.organization.permissions,
|
||||
plan: selected.data.organization.plan,
|
||||
});
|
||||
} else {
|
||||
setCurrentOrganization(null);
|
||||
}
|
||||
} else if (orgs.length === 1 && userData) {
|
||||
const selected = await authApi.selectOrganization(orgs[0].id);
|
||||
localStorage.setItem('currentOrganizationId', orgs[0].id);
|
||||
setCurrentOrganization({
|
||||
id: selected.data.organization.id,
|
||||
name: selected.data.organization.name,
|
||||
type: selected.data.organization.type as Organization['type'],
|
||||
isOwner: Boolean(selected.data.organization.isOwner),
|
||||
permissions: selected.data.organization.permissions,
|
||||
plan: selected.data.organization.plan,
|
||||
});
|
||||
} else {
|
||||
setCurrentOrganization(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
/* fall through to logged-out state */
|
||||
}
|
||||
}
|
||||
|
||||
// 401 on profile is expected when there is no session — not an application error.
|
||||
if (status !== 401) {
|
||||
console.error('Auth check failed:', err);
|
||||
@@ -128,6 +203,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
setUser(null);
|
||||
setOrganizations([]);
|
||||
setCurrentOrganization(null);
|
||||
clearAccessTokenExpiresAt();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsAuthReady(true);
|
||||
@@ -138,6 +214,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
void checkAuth();
|
||||
}, [checkAuth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !isAuthReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
return startProactiveSessionRefresh();
|
||||
}, [user, isAuthReady]);
|
||||
|
||||
const applyUrlLocaleToUser = useCallback(async (user: User): Promise<User> => {
|
||||
if (typeof window === 'undefined') return user;
|
||||
|
||||
@@ -180,6 +264,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
});
|
||||
|
||||
const userData = await applyUrlLocaleToUser(response.data.user);
|
||||
rememberAccessTokenExpiresAt(response.data.accessTokenExpiresAt);
|
||||
setUser(userData);
|
||||
setOrganizations(response.data.organizations);
|
||||
|
||||
@@ -223,6 +308,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
const userData = await applyUrlLocaleToUser(response.data.user);
|
||||
rememberAccessTokenExpiresAt(response.data.accessTokenExpiresAt);
|
||||
setUser(userData);
|
||||
setOrganizations(response.data.organizations);
|
||||
|
||||
@@ -263,6 +349,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
setOrganizations([]);
|
||||
setCurrentOrganization(null);
|
||||
setError(null);
|
||||
clearAccessTokenExpiresAt();
|
||||
setIsAuthReady(true);
|
||||
router.replace('/');
|
||||
router.refresh();
|
||||
|
||||
Reference in New Issue
Block a user