deploy configuration
This commit is contained in:
parent
9ba3c47d82
commit
dac02c61c3
45 changed files with 1405 additions and 582 deletions
|
|
@ -8,22 +8,27 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/danyel/go-cms/internal/security"
|
||||
"github.com/danyel/go-cms/internal/service"
|
||||
goloose "github.com/danyel/go-loose/client"
|
||||
|
||||
"github.com/danyel/go-tell/internal/golooseauth"
|
||||
"github.com/danyel/go-tell/internal/security"
|
||||
"github.com/danyel/go-tell/internal/service"
|
||||
)
|
||||
|
||||
// Handler serves the CMS API and the embedded React app from one binary.
|
||||
// Reading content is anonymous; every mutation requires the identity asserted by
|
||||
// the upstream SSO proxy.
|
||||
// Reading content is anonymous; every mutation requires an authenticated
|
||||
// editor: a Go Loose browser session, the identity asserted by the upstream
|
||||
// SSO proxy, or a demo user.
|
||||
type Handler struct {
|
||||
content service.IContentService
|
||||
assets fs.FS
|
||||
sso security.ITokenVerifier
|
||||
browser *golooseauth.Auth
|
||||
demo *service.DemoAuth
|
||||
}
|
||||
|
||||
func NewHandler(content service.IContentService, assets fs.FS, sso security.ITokenVerifier, demo ...*service.DemoAuth) *Handler {
|
||||
h := &Handler{content: content, assets: assets, sso: sso}
|
||||
func NewHandler(content service.IContentService, assets fs.FS, sso security.ITokenVerifier, browser *golooseauth.Auth, demo ...*service.DemoAuth) *Handler {
|
||||
h := &Handler{content: content, assets: assets, sso: sso, browser: browser}
|
||||
if len(demo) > 0 {
|
||||
h.demo = demo[0]
|
||||
}
|
||||
|
|
@ -35,6 +40,9 @@ func (h *Handler) Routes(origins string) http.Handler {
|
|||
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
jsonWrite(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
mux.HandleFunc(golooseauth.LoginPath, h.login)
|
||||
mux.HandleFunc(golooseauth.CallbackPath, h.callback)
|
||||
mux.HandleFunc(golooseauth.LogoutPath, h.logout)
|
||||
mux.HandleFunc("/api/auth/session", h.session)
|
||||
mux.HandleFunc("/api/auth/sudo", h.sudo)
|
||||
mux.HandleFunc("/api/auth/users", h.users)
|
||||
|
|
@ -48,9 +56,51 @@ func (h *Handler) Routes(origins string) http.Handler {
|
|||
return cors(mux, origins, h.sso.HeaderName())
|
||||
}
|
||||
|
||||
// session reports who the caller is. When Go Loose is configured the response
|
||||
// also carries the login and logout endpoints so the UI can offer the browser
|
||||
// login instead of a proxy redirect.
|
||||
func (h *Handler) session(w http.ResponseWriter, r *http.Request) {
|
||||
username, authenticated := h.identity(r)
|
||||
jsonWrite(w, http.StatusOK, map[string]any{"authenticated": authenticated, "canEdit": authenticated, "username": username})
|
||||
response := map[string]any{"authenticated": authenticated, "canEdit": authenticated, "username": username}
|
||||
if user, ok := h.browserUser(r); ok {
|
||||
response["email"] = user.Email
|
||||
response["displayName"] = user.DisplayName
|
||||
}
|
||||
if h.browser != nil {
|
||||
response["loginUrl"] = golooseauth.LoginPath
|
||||
response["logoutUrl"] = golooseauth.LogoutPath
|
||||
}
|
||||
jsonWrite(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
|
||||
if h.browser == nil {
|
||||
writeAuthUnavailable(w)
|
||||
return
|
||||
}
|
||||
h.browser.LoginHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) callback(w http.ResponseWriter, r *http.Request) {
|
||||
if h.browser == nil {
|
||||
writeAuthUnavailable(w)
|
||||
return
|
||||
}
|
||||
h.browser.CallbackHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) logout(w http.ResponseWriter, r *http.Request) {
|
||||
if h.browser == nil {
|
||||
writeAuthUnavailable(w)
|
||||
return
|
||||
}
|
||||
h.browser.LogoutHandler(w, r)
|
||||
}
|
||||
|
||||
func writeAuthUnavailable(w http.ResponseWriter) {
|
||||
jsonWrite(w, http.StatusNotFound, map[string]string{
|
||||
"error": "Go Loose login is not configured: set GO_LOOSE_CLIENT_ID and GO_LOOSE_CLIENT_SECRET, then restart the server",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) protected(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -182,6 +232,9 @@ func (h *Handler) contentDetail(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// identity resolves the editor behind a request. The demo profile wins, then
|
||||
// the SSO proxy, then the Go Loose browser session: the session is checked last
|
||||
// because it costs a call to the identity provider.
|
||||
func (h *Handler) identity(r *http.Request) (string, bool) {
|
||||
if h.demo != nil {
|
||||
token := strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "))
|
||||
|
|
@ -192,9 +245,31 @@ func (h *Handler) identity(r *http.Request) (string, bool) {
|
|||
if h.sso.Authenticated(r) {
|
||||
return "sso-owner", true
|
||||
}
|
||||
if user, ok := h.browserUser(r); ok {
|
||||
return editorName(user), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// browserUser returns the Go Loose user carried by the request session, if any.
|
||||
func (h *Handler) browserUser(r *http.Request) (goloose.User, bool) {
|
||||
if h.browser == nil {
|
||||
return goloose.User{}, false
|
||||
}
|
||||
user, ok := h.browser.SessionUser(r)
|
||||
if !ok {
|
||||
return goloose.User{}, false
|
||||
}
|
||||
return user, true
|
||||
}
|
||||
|
||||
func editorName(user goloose.User) string {
|
||||
if name := strings.TrimSpace(user.DisplayName); name != "" {
|
||||
return name
|
||||
}
|
||||
return user.Email
|
||||
}
|
||||
|
||||
// singlePageApp serves the embedded build and falls back to index.html so the
|
||||
// client-side router owns every non-API path.
|
||||
func (h *Handler) singlePageApp(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue