diff --git a/authenticate/handlers.go b/authenticate/handlers.go
index 8f31ee278..c854f042f 100644
--- a/authenticate/handlers.go
+++ b/authenticate/handlers.go
@@ -89,6 +89,7 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
// routes that don't need a session:
sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
+ sr.Path("/signed_out").Handler(handlers.SignedOut(handlers.SignedOutData{})).Methods(http.MethodGet)
// routes that need a session:
sr = sr.NewRoute().Subrouter()
@@ -266,16 +267,25 @@ func (a *Authenticate) signOutRedirect(w http.ResponseWriter, r *http.Request) e
rawIDToken := a.revokeSession(ctx, w, r)
- signOutURL := ""
+ authenticateURL, err := options.GetAuthenticateURL()
+ if err != nil {
+ return fmt.Errorf("error getting authenticate url: %w", err)
+ }
+
signOutRedirectURL, err := options.GetSignOutRedirectURL()
if err != nil {
return err
}
- if signOutRedirectURL != nil {
- signOutURL = signOutRedirectURL.String()
- }
+
+ var signOutURL string
if uri := r.FormValue(urlutil.QueryRedirectURI); uri != "" {
signOutURL = uri
+ } else if signOutRedirectURL != nil {
+ signOutURL = signOutRedirectURL.String()
+ } else {
+ signOutURL = authenticateURL.ResolveReference(&url.URL{
+ Path: "/.pomerium/signed_out",
+ }).String()
}
if idpSignOutURL, err := authenticator.GetSignOutURL(rawIDToken, signOutURL); err == nil {
@@ -284,11 +294,8 @@ func (a *Authenticate) signOutRedirect(w http.ResponseWriter, r *http.Request) e
log.Warn(r.Context()).Err(err).Msg("authenticate: failed to get sign out url for authenticator")
}
- if signOutURL != "" {
- httputil.Redirect(w, r, signOutURL, http.StatusFound)
- return nil
- }
- return httputil.NewError(http.StatusOK, errors.New("user logged out"))
+ httputil.Redirect(w, r, signOutURL, http.StatusFound)
+ return nil
}
// reauthenticateOrFail starts the authenticate process by redirecting the
diff --git a/internal/handlers/signedout.go b/internal/handlers/signedout.go
new file mode 100644
index 000000000..099a333db
--- /dev/null
+++ b/internal/handlers/signedout.go
@@ -0,0 +1,23 @@
+package handlers
+
+import (
+ "net/http"
+
+ "github.com/pomerium/pomerium/internal/httputil"
+ "github.com/pomerium/pomerium/ui"
+)
+
+// SignedOutData is the data for the SignedOut page.
+type SignedOutData struct{}
+
+// ToJSON converts the data into a JSON map.
+func (data SignedOutData) ToJSON() map[string]interface{} {
+ return map[string]interface{}{}
+}
+
+// SignedOut returns a handler that renders the signed out page.
+func SignedOut(data SignedOutData) http.Handler {
+ return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
+ return ui.ServePage(w, r, "SignedOut", data.ToJSON())
+ })
+}
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
index 56851e5b2..48d645910 100644
--- a/ui/src/App.tsx
+++ b/ui/src/App.tsx
@@ -1,12 +1,13 @@
import Box from "@mui/material/Box";
import CssBaseline from "@mui/material/CssBaseline";
import { ThemeProvider } from "@mui/material/styles";
-import React, {FC, useLayoutEffect} from "react";
+import React, { FC, useLayoutEffect } from "react";
import ErrorPage from "./components/ErrorPage";
import Footer from "./components/Footer";
import Header from "./components/Header";
import SignOutConfirmPage from "./components/SignOutConfirmPage";
+import SignedOutPage from "./components/SignedOutPage";
import { ToolbarOffset } from "./components/ToolbarOffset";
import UserInfoPage from "./components/UserInfoPage";
import WebAuthnRegistrationPage from "./components/WebAuthnRegistrationPage";
@@ -27,6 +28,9 @@ const App: FC = () => {
case "SignOutConfirm":
body = ;
break;
+ case "SignedOut":
+ body = ;
+ break;
case "DeviceEnrolled":
case "UserInfo":
body = ;
@@ -38,18 +42,18 @@ const App: FC = () => {
useLayoutEffect(() => {
const favicon = document.getElementById(
- 'favicon'
+ "favicon"
) as HTMLAnchorElement | null;
if (favicon) {
- favicon.href = data?.faviconUrl || '/.pomerium/favicon.ico';
+ favicon.href = data?.faviconUrl || "/.pomerium/favicon.ico";
}
const extraFaviconLinks = document.getElementsByClassName(
- 'pomerium_favicon'
+ "pomerium_favicon"
) as HTMLCollectionOf | null;
for (const link of extraFaviconLinks) {
- link.style.display = data?.faviconUrl ? 'none' : '';
+ link.style.display = data?.faviconUrl ? "none" : "";
}
- }, [])
+ }, []);
return (
diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx
index dfa11e3d9..e562fb85a 100644
--- a/ui/src/components/Header.tsx
+++ b/ui/src/components/Header.tsx
@@ -57,6 +57,8 @@ const Header: FC = ({ includeSidebar, data }) => {
get(data, "user.claims.picture") ||
get(data, "profile.claims.picture") ||
null;
+ const showAvatar =
+ data?.page !== "SignOutConfirm" && data?.page !== "SignedOut";
const handleDrawerOpen = () => {
setDrawerOpen(true);
@@ -122,9 +124,11 @@ const Header: FC = ({ includeSidebar, data }) => {
)}
-
-
-
+ {showAvatar && (
+
+
+
+ )}