AuthProxy: Render unauthorized page
Change-Id: I979762c63b0d1b3c3674fd0b9ab79ccd8849887a
diff --git a/core/auth/proxy/main.go b/core/auth/proxy/main.go
index d1e1b49..2114127 100644
--- a/core/auth/proxy/main.go
+++ b/core/auth/proxy/main.go
@@ -4,26 +4,43 @@
"bytes"
"context"
"crypto/tls"
+ "embed"
"encoding/json"
"flag"
"fmt"
+ "html/template"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
+ "slices"
"strings"
-
- "golang.org/x/exp/slices"
)
var port = flag.Int("port", 3000, "Port to listen on")
var whoAmIAddr = flag.String("whoami-addr", "", "Kratos whoami endpoint address")
var loginAddr = flag.String("login-addr", "", "Login page address")
var membershipAddr = flag.String("membership-addr", "", "Group membership API endpoint")
+var membershipPublicAddr = flag.String("membership-public-addr", "", "Public address of membership service")
var groups = flag.String("groups", "", "Comma separated list of groups. User must be part of at least one of them. If empty group membership will not be checked.")
var upstream = flag.String("upstream", "", "Upstream service address")
+//go:embed unauthorized.html
+var unauthorizedHTML embed.FS
+
+//go:embed static/*
+var f embed.FS
+
+type cachingHandler struct {
+ h http.Handler
+}
+
+func (h cachingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Cache-Control", "max-age=604800")
+ h.h.ServeHTTP(w, r)
+}
+
type user struct {
Identity struct {
Traits struct {
@@ -46,6 +63,34 @@
r.URL.RequestURI()))
}
+var funcMap = template.FuncMap{
+ "IsLast": func(index int, slice []string) bool {
+ return index == len(slice)-1
+ },
+}
+
+type UnauthorizedPageData struct {
+ MembershipPublicAddr string
+ Groups []string
+}
+
+func renderUnauthorizedPage(w http.ResponseWriter, groups []string) {
+ tmpl, err := template.New("unauthorized.html").Funcs(funcMap).ParseFS(unauthorizedHTML, "unauthorized.html")
+ if err != nil {
+ http.Error(w, "Failed to load template", http.StatusInternalServerError)
+ return
+ }
+ data := UnauthorizedPageData{
+ MembershipPublicAddr: *membershipPublicAddr,
+ Groups: groups,
+ }
+ w.Header().Set("Content-Type", "text/html")
+ w.WriteHeader(http.StatusUnauthorized)
+ if err := tmpl.Execute(w, data); err != nil {
+ http.Error(w, "Failed render template", http.StatusInternalServerError)
+ }
+}
+
func handle(w http.ResponseWriter, r *http.Request) {
user, err := queryWhoAmI(r.Cookies())
if err != nil {
@@ -80,10 +125,10 @@
}
}
if !hasPermission {
- http.Error(w, "not authorized", http.StatusUnauthorized)
+ groupList := strings.Split(*groups, ",")
+ renderUnauthorizedPage(w, groupList)
return
}
-
}
rc := r.Clone(context.Background())
rc.Header.Add("X-User", user.Identity.Traits.Username)
@@ -189,9 +234,10 @@
func main() {
flag.Parse()
- if *groups != "" && *membershipAddr == "" {
- log.Fatal("membership-addr flag is required when groups are provided")
+ if *groups != "" && (*membershipAddr == "" || *membershipPublicAddr == "") {
+ log.Fatal("membership-addr and membership-public-addr flags are required when groups are provided")
}
+ http.Handle("/static/", cachingHandler{http.FileServer(http.FS(f))})
http.HandleFunc("/", handle)
fmt.Printf("Starting HTTP server on port: %d\n", *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))