package handler
import (
"errors"
"html/template"
"log/slog"
"net/http"
"net/url"
"time"
"git.leonardobishop.net/instancer/pkg/auth"
"git.leonardobishop.net/instancer/pkg/session"
)
func GetAuth(tmpl *template.Template, authProvider *auth.OIDCAuthProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
errMessage := r.URL.Query().Get("error")
if errMessage != "" {
w.Header().Add("HX-Redirect", "/auth?error="+url.QueryEscape(errMessage))
} else {
w.Header().Add("HX-Redirect", "/auth")
}
tmpl.ExecuteTemplate(w, "auth.html", struct {
Error string
OidcIdPName string
}{
Error: errMessage,
OidcIdPName: authProvider.Name,
})
}
}
func PostAuth(tmpl *template.Template, session *session.MemoryStore, authProvider *auth.OIDCAuthProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
url, err := authProvider.StartJourney(r.RemoteAddr, r.UserAgent())
if err != nil {
slog.Error("oidc start journey failed", "cause", err)
tmpl.ExecuteTemplate(w, "auth.html", struct {
Error string
OidcIdPName string
}{
Error: "Failed to start OIDC journey",
OidcIdPName: authProvider.Name,
})
return
}
http.Redirect(w, r, url, http.StatusFound)
}
}
func GetAuthCallback(tmpl *template.Template, session *session.MemoryStore, authProvider *auth.OIDCAuthProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
type pageParams struct {
Error string
OidcIdPName string
}
params := pageParams{OidcIdPName: authProvider.Name}
query := r.URL.Query()
s, err := session.Create()
if err != nil {
params.Error = "Failed to create session"
slog.Error("session creation failed", "cause", err)
w.Header().Add("HX-Push-Url", "/auth")
tmpl.ExecuteTemplate(w, "auth.html", params)
return
}
err = authProvider.CompleteJourney(r.Context(), query.Get("code"), query.Get("state"), r.RemoteAddr, r.UserAgent(), s)
if err != nil {
if errors.Is(err, auth.ErrNotAuthorised) {
params.Error = "You are not authorised to use this service"
goto done
} else if errors.Is(err, auth.ErrInvalidState) {
params.Error = "Invalid state"
goto done
} else if errors.Is(err, auth.ErrStateVerificationFailed) {
params.Error = "State verification failed"
goto done
} else if errors.Is(err, auth.ErrInvalidIDToken) {
params.Error = "Invalid ID token"
goto done
}
params.Error = "Failed to complete OIDC journey"
slog.Error("oidc complete journey failed", "cause", err)
done:
w.Header().Add("HX-Push-Url", "/auth")
tmpl.ExecuteTemplate(w, "auth.html", params)
session.Destroy(s.Token)
return
}
http.SetCookie(w, &http.Cookie{
Name: "instancer-session",
Value: s.Token,
Path: "/",
Secure: true,
SameSite: http.SameSiteStrictMode,
HttpOnly: true,
})
http.Redirect(w, r, "/", http.StatusFound)
}
}
//func PostAuth(tmpl *template.Template, session *session.MemoryStore) http.HandlerFunc {
// return func(w http.ResponseWriter, r *http.Request) {
// if err := r.ParseForm(); err != nil {
// tmpl.ExecuteTemplate(w, "f_auth_error.html", struct {
// Message string
// }{
// Message: "Invalid form data",
// })
// return
// }
//
// team := r.FormValue("team")
// if team == "" {
// tmpl.ExecuteTemplate(w, "f_auth_error.html", struct {
// Message string
// }{
// Message: "No team entered",
// })
// return
// }
//
// if _, err := strconv.Atoi(team); err != nil {
// tmpl.ExecuteTemplate(w, "f_auth_error.html", struct {
// Message string
// }{
// Message: "Team ID must be number",
// })
// return
// }
//
// session, err := session.Create(team)
// if err != nil {
// slog.Error("could not create session", "cause", err)
// tmpl.ExecuteTemplate(w, "f_auth_error.html", struct {
// Message string
// }{
// Message: "Could not create session",
// })
// return
// }
//
// http.SetCookie(w, &http.Cookie{
// Name: "session",
// Value: session.Token,
//
// Path: "/",
// Secure: true,
// SameSite: http.SameSiteStrictMode,
// HttpOnly: true,
// })
// w.Header().Add("HX-Redirect", "/")
// }
//}
func GetLogout(session *session.MemoryStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//TODO expire session here
http.SetCookie(w, &http.Cookie{
Name: "instancer-session",
Value: "",
Expires: time.Unix(0, 0),
Path: "/",
Secure: true,
SameSite: http.SameSiteStrictMode,
HttpOnly: true,
})
http.Redirect(w, r, "/auth", http.StatusFound)
}
}