package web import ( "errors" "log/slog" "net/http" "tug/internal/transport/web/text" ) const AppLogoutPath = "/.sso/logout" type appPageData struct { Username Ident Account string } func (k *Kit) AppHost(g Guard, routes ...Route) http.Handler { if g == nil { panic("web: an app host needs a guard") } for _, rt := range routes { if rt.Access != Public { panic("web: an app host serves only public routes: " + rt.Method + " " + rt.Path) } } return k.newHost(routes, func(_ Route, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p, err := g.Resolve(r) switch { case err == nil: next.ServeHTTP(w, withPrincipal(r, p)) case errors.Is(err, ErrNoSession): next.ServeHTTP(w, r) default: slog.Error("session check failed", "err", err) k.renderError(w, r, http.StatusInternalServerError, text.ErrorInternalTitle, text.ErrorInternal) } }) }) } func (k *Kit) AppLogoutRoutes(e SessionEnder) []Route { if e == nil { panic("web: the app logout pages need a session ender") } show := func(w http.ResponseWriter, r *http.Request) { d := appPageData{Account: k.account + LogoutPath} p, ok := PrincipalFrom(r) if !ok { k.render(w, r, http.StatusOK, "app-signed-out", k.newPage(w, r, d)) return } d.Username = p.Username k.render(w, r, http.StatusOK, "app-logout", k.formPage(w, r, d)) } submit := func(w http.ResponseWriter, r *http.Request) { if p, ok := PrincipalFrom(r); ok { if err := e.EndSession(w, r, p); err != nil && !errors.Is(err, ErrNoSession) { slog.Error("app logout failed", "err", err) k.renderError(w, r, http.StatusInternalServerError, text.ErrorInternalTitle, text.ErrorInternal) return } k.RotateCSRF(w) } k.SeeOther(w, r, AppLogoutPath) } ended := func(w http.ResponseWriter, r *http.Request) { if _, ok := PrincipalFrom(r); ok { k.renderError(w, r, http.StatusForbidden, text.ErrorForbiddenTitle, text.ErrorFormExpired) return } k.SeeOther(w, r, AppLogoutPath) } return []Route{ {Method: http.MethodGet, Path: AppLogoutPath, Access: Public, Handler: http.HandlerFunc(show)}, {Method: http.MethodPost, Path: AppLogoutPath, Access: Public, Handler: http.HandlerFunc(submit), csrfFallback: http.HandlerFunc(ended)}, } } func (k *Kit) RenderAppForbidden(w http.ResponseWriter, r *http.Request, username Ident) { k.render(w, r, http.StatusForbidden, "app-forbidden", k.newPage(w, r, appPageData{Username: username, Account: k.account + "/"})) }