feat(auth): custom logout page with Authelia sign-out and re-login button
This commit is contained in:
parent
594cf3efe2
commit
312aa388d4
3 changed files with 109 additions and 12 deletions
40
src/core/ui/logout.templ
Normal file
40
src/core/ui/logout.templ
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
templ LogoutPage(authLogoutURL, workspaceURL string) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Logged Out — Next Workspace</title>
|
||||||
|
<style type="text/css">
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#e6edf3;display:flex;align-items:center;justify-content:center;min-height:100vh}
|
||||||
|
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:40px;text-align:center;max-width:380px;width:90%}
|
||||||
|
h1{font-size:1.4rem;margin-bottom:8px}
|
||||||
|
p{color:#8b949e;font-size:0.9rem;margin-bottom:24px;line-height:1.5}
|
||||||
|
.btn{display:block;width:100%;padding:12px;border-radius:8px;border:none;font-size:0.9rem;font-weight:600;cursor:pointer;text-decoration:none;margin-bottom:10px;text-align:center}
|
||||||
|
.btn-primary{background:#58a6ff;color:#fff}
|
||||||
|
.btn-primary:hover{opacity:0.9}
|
||||||
|
.btn-secondary{background:transparent;border:1px solid #30363d;color:#8b949e}
|
||||||
|
.btn-secondary:hover{background:#21262d}
|
||||||
|
.status{color:#3fb950;font-size:0.85rem;margin-bottom:16px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Logged Out</h1>
|
||||||
|
<div class="status">✓ Session cleared</div>
|
||||||
|
<p>You have been logged out of Next Workspace. Your identity provider session will also be cleared.</p>
|
||||||
|
<a href={ workspaceURL } class="btn btn-primary">Re-login</a>
|
||||||
|
<a href={ authLogoutURL } class="btn btn-secondary">Sign out from Authelia</a>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Redirect to Authelia logout after a short delay
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.href = document.querySelector('.btn-secondary').href;
|
||||||
|
}, 1500);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
66
src/core/ui/logout_templ.go
Normal file
66
src/core/ui/logout_templ.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.1020
|
||||||
|
package ui
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func LogoutPage(authLogoutURL, workspaceURL string) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Logged Out — Next Workspace</title><style type=\"text/css\">\n\t\t\t\t*{margin:0;padding:0;box-sizing:border-box}\n\t\t\t\tbody{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#e6edf3;display:flex;align-items:center;justify-content:center;min-height:100vh}\n\t\t\t\t.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:40px;text-align:center;max-width:380px;width:90%}\n\t\t\t\th1{font-size:1.4rem;margin-bottom:8px}\n\t\t\t\tp{color:#8b949e;font-size:0.9rem;margin-bottom:24px;line-height:1.5}\n\t\t\t\t.btn{display:block;width:100%;padding:12px;border-radius:8px;border:none;font-size:0.9rem;font-weight:600;cursor:pointer;text-decoration:none;margin-bottom:10px;text-align:center}\n\t\t\t\t.btn-primary{background:#58a6ff;color:#fff}\n\t\t\t\t.btn-primary:hover{opacity:0.9}\n\t\t\t\t.btn-secondary{background:transparent;border:1px solid #30363d;color:#8b949e}\n\t\t\t\t.btn-secondary:hover{background:#21262d}\n\t\t\t\t.status{color:#3fb950;font-size:0.85rem;margin-bottom:16px}\n\t\t\t</style></head><body><div class=\"card\"><h1>Logged Out</h1><div class=\"status\">✓ Session cleared</div><p>You have been logged out of Next Workspace. Your identity provider session will also be cleared.</p><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(workspaceURL)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/logout.templ`, Line: 29, Col: 26}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"btn btn-primary\">Re-login</a> <a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(authLogoutURL)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `core/ui/logout.templ`, Line: 30, Col: 27}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"btn btn-secondary\">Sign out from Authelia</a></div><script type=\"text/javascript\">\n\t\t\t\t// Redirect to Authelia logout after a short delay\n\t\t\t\tsetTimeout(function() {\n\t\t\t\t\twindow.location.href = document.querySelector('.btn-secondary').href;\n\t\t\t\t}, 1500);\n\t\t\t</script></body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
15
src/main.go
15
src/main.go
|
|
@ -123,23 +123,14 @@ func main() {
|
||||||
mux.HandleFunc("GET /auth/callback", oidcHandler.Callback)
|
mux.HandleFunc("GET /auth/callback", oidcHandler.Callback)
|
||||||
mux.HandleFunc("POST /auth/callback", oidcHandler.Callback)
|
mux.HandleFunc("POST /auth/callback", oidcHandler.Callback)
|
||||||
mux.HandleFunc("GET /auth/logout", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("GET /auth/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Clear session cookie (with domain to match)
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "nextwks_session",
|
|
||||||
Value: "",
|
|
||||||
Path: "/",
|
|
||||||
Domain: cfg.OIDC.Domain,
|
|
||||||
MaxAge: -1,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
})
|
|
||||||
// Clear NextWks session cookie
|
// Clear NextWks session cookie
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: "nextwks_session", Value: "", Path: "/", Domain: cfg.OIDC.Domain,
|
Name: "nextwks_session", Value: "", Path: "/", Domain: cfg.OIDC.Domain,
|
||||||
MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode,
|
MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode,
|
||||||
})
|
})
|
||||||
// Redirect to workspace — no session means redirect to Authelia login
|
authLogoutURL := fmt.Sprintf("%s/logout", cfg.OIDC.IssuerURL)
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
component := ui.LogoutPage(authLogoutURL, cfg.OIDC.RedirectURL)
|
||||||
|
component.Render(r.Context(), w)
|
||||||
})
|
})
|
||||||
|
|
||||||
// --- Workspace launcher (public, but OIDC-protected) ---
|
// --- Workspace launcher (public, but OIDC-protected) ---
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue