fix: OIDC logout redirect unreliable (#9508)

This commit is contained in:
Tom Moor
2025-06-29 07:57:49 -04:00
committed by GitHub
parent c97e5fd181
commit ba20eb4040
7 changed files with 50 additions and 23 deletions

View File

@@ -20,7 +20,6 @@ import SearchQuery from "~/models/SearchQuery";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { createAction } from "~/actions";
import { NavigationSection, RecentSearchesSection } from "~/actions/sections";
import env from "~/env";
import Desktop from "~/utils/Desktop";
import history from "~/utils/history";
import isCloudHosted from "~/utils/isCloudHosted";
@@ -231,12 +230,7 @@ export const logout = createAction({
section: NavigationSection,
icon: <LogoutIcon />,
perform: async () => {
await stores.auth.logout();
if (env.OIDC_LOGOUT_URI) {
setTimeout(() => {
window.location.replace(env.OIDC_LOGOUT_URI);
}, 200);
}
await stores.auth.logout({ userInitiated: true });
},
});

View File

@@ -31,7 +31,12 @@ const Authenticated = ({ children }: Props) => {
return <LoadingIndicator />;
}
void auth.logout(true);
void auth.logout({ savePath: true });
if (auth.logoutRedirectUri) {
window.location.href = auth.logoutRedirectUri;
return null;
}
return <Redirect to="/" />;
};

View File

@@ -6,13 +6,7 @@ import { logoutPath } from "~/utils/routeHelpers";
const Logout = () => {
const { auth } = useStores();
void auth.logout().then(() => {
if (env.OIDC_LOGOUT_URI) {
setTimeout(() => {
window.location.replace(env.OIDC_LOGOUT_URI);
}, 200);
}
});
void auth.logout({ userInitiated: true });
if (env.OIDC_LOGOUT_URI) {
return null; // user will be redirected to logout URI after logout

View File

@@ -48,7 +48,11 @@ function TeamDelete({ onSubmit }: Props) {
async (data: FormData) => {
try {
await auth.deleteTeam(data);
await auth.logout();
await auth.logout({
savePath: false,
revokeToken: false,
userInitiated: true,
});
onSubmit();
} catch (error) {
toast.error(error.message);

View File

@@ -47,7 +47,11 @@ function UserDelete({ onSubmit }: Props) {
async (data: FormData) => {
try {
await auth.deleteUser(data);
await auth.logout();
await auth.logout({
savePath: false,
revokeToken: false,
userInitiated: true,
});
onSubmit();
} catch (err) {
toast.error(err.message);

View File

@@ -50,6 +50,10 @@ export default class AuthStore extends Store<Team> {
@observable
public collaborationToken?: string | null;
/* When set, the user will be redirected to this URL after logging out. */
@observable
public logoutRedirectUri?: string;
/* A list of teams that the current user has access to. */
@observable
public availableTeams?: {
@@ -109,7 +113,11 @@ export default class AuthStore extends Store<Team> {
// we are signed in and the received data contains no user then sign out
if (this.authenticated) {
if (isNil(newData.user)) {
void this.logout(false, false);
void this.logout({
savePath: false,
revokeToken: false,
userInitiated: true,
});
}
} else {
this.rehydrate(newData);
@@ -298,18 +306,26 @@ export default class AuthStore extends Store<Team> {
* Logs the user out and optionally revokes the authentication token.
*
* @param savePath Whether the current path should be saved and returned to after login.
* @param tryRevokingToken Whether the auth token should attempt to be revoked, this should be
* @param revokeToken Whether the auth token should attempt to be revoked, this should be
* disabled with requests from ApiClient to prevent infinite loops.
*/
@action
logout = async (savePath = false, tryRevokingToken = true) => {
logout = async ({
savePath = false,
revokeToken = true,
userInitiated = false,
}: {
savePath?: boolean;
revokeToken?: boolean;
userInitiated?: boolean;
}) => {
// if this logout was forced from an authenticated route then
// save the current path so we can go back there once signed in
if (savePath) {
setPostLoginPath(window.location.pathname + window.location.search);
}
if (tryRevokingToken) {
if (revokeToken) {
try {
// invalidate authentication token on server and unset auth cookie
await client.post(`/auth.delete`);
@@ -329,6 +345,10 @@ export default class AuthStore extends Store<Team> {
});
}
if (userInitiated) {
this.logoutRedirectUri = env.OIDC_LOGOUT_URI;
}
// clear all credentials from cache (and local storage via autorun)
this.currentUserId = null;
this.currentTeamId = null;

View File

@@ -153,7 +153,10 @@ class ApiClient {
// Handle 401, log out user
if (response.status === 401) {
await stores.auth.logout(true, false);
await stores.auth.logout({
savePath: true,
revokeToken: false,
});
throw new AuthorizationError();
}
@@ -201,7 +204,10 @@ class ApiClient {
if (response.status === 403) {
if (error.error === "user_suspended") {
await stores.auth.logout(false, false);
await stores.auth.logout({
savePath: false,
revokeToken: false,
});
}
throw new AuthorizationError(error.message);