feat: integrate tenant SSO and remote toggle
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
660d5291ec
commit
ca5765b988
27 changed files with 529 additions and 159 deletions
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/danyel/go-guess/backend/internal/database/repository"
|
||||
"github.com/danyel/go-guess/backend/internal/eventbus"
|
||||
"github.com/danyel/go-guess/backend/internal/golooseauth"
|
||||
"github.com/danyel/go-guess/backend/internal/security"
|
||||
"github.com/danyel/go-guess/backend/internal/service"
|
||||
servicemodel "github.com/danyel/go-guess/backend/internal/service/model"
|
||||
|
|
@ -37,6 +38,7 @@ type Handler struct {
|
|||
interviews service.IScheduledInterviewService
|
||||
maxUpload int64
|
||||
frontendURL string
|
||||
browser *golooseauth.Registry
|
||||
}
|
||||
|
||||
func New(
|
||||
|
|
@ -651,7 +653,8 @@ func (h *Handler) frontendURLForRequest(r *http.Request) string {
|
|||
if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") {
|
||||
return h.frontendURL
|
||||
}
|
||||
if strings.EqualFold(parsed.Hostname(), tenant+".guess.local") {
|
||||
if strings.EqualFold(parsed.Hostname(), tenant+".guess.local") ||
|
||||
strings.EqualFold(parsed.Hostname(), tenant+".guess.dev") {
|
||||
return origin
|
||||
}
|
||||
return h.frontendURL
|
||||
|
|
|
|||
|
|
@ -28,3 +28,14 @@ func TestFrontendURLRejectsDifferentTenantOrigin(t *testing.T) {
|
|||
t.Fatalf("expected configured frontend URL, got %q", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFrontendURLAcceptsDevTenantOrigin(t *testing.T) {
|
||||
handler := &Handler{frontendURL: "http://localhost:5173"}
|
||||
request := httptest.NewRequest("GET", "/api/jobs/2/invitations", nil)
|
||||
request.Header.Set("Origin", "https://ypto.guess.dev")
|
||||
request.Header.Set(client.XTenantId, "ypto")
|
||||
|
||||
if value := handler.frontendURLForRequest(request); value != "https://ypto.guess.dev" {
|
||||
t.Fatalf("expected tenant dev origin, got %q", value)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
92
backend/internal/web/handler/sso.go
Normal file
92
backend/internal/web/handler/sso.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
goloose "github.com/danyel/go-loose/client"
|
||||
|
||||
"github.com/danyel/go-guess/backend/internal/golooseauth"
|
||||
webmapper "github.com/danyel/go-guess/backend/internal/web/mapper"
|
||||
webmodel "github.com/danyel/go-guess/backend/internal/web/model"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingSession = errors.New("missing Go Loose session")
|
||||
errTenantMismatch = errors.New("Go Loose session does not match this tenant")
|
||||
)
|
||||
|
||||
func (h *Handler) SetBrowserAuth(auth *golooseauth.Registry) {
|
||||
h.browser = auth
|
||||
}
|
||||
|
||||
func (h *Handler) AppDomain() string {
|
||||
if h.browser == nil {
|
||||
return "guess.dev"
|
||||
}
|
||||
return h.browser.AppDomain()
|
||||
}
|
||||
|
||||
func (h *Handler) BeginExternalLogin(w http.ResponseWriter, r *http.Request) {
|
||||
auth, ok := h.externalAuth(r)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
auth.LoginHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) CompleteExternalLogin(w http.ResponseWriter, r *http.Request) {
|
||||
auth, ok := h.externalAuth(r)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
auth.CallbackHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) ExternalLogout(w http.ResponseWriter, r *http.Request) {
|
||||
auth, ok := h.externalAuth(r)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
auth.LogoutHandler(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) ExternalSession(w http.ResponseWriter, r *http.Request) {
|
||||
auth, slug, ok := h.externalAuthWithSlug(r)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if _, err := r.Cookie(golooseauth.SessionCookie); err != nil {
|
||||
writeError(w, http.StatusUnauthorized, errMissingSession)
|
||||
return
|
||||
}
|
||||
auth.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := goloose.UserFromContext(r.Context())
|
||||
if !ok || user.TenantSlug != slug {
|
||||
writeError(w, http.StatusForbidden, errTenantMismatch)
|
||||
return
|
||||
}
|
||||
token, local, err := h.auth.EstablishExternalSession(r.Context(), user.Email, user.DisplayName)
|
||||
if err != nil {
|
||||
internalError(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, webmodel.LoginResponse{Token: token, User: webmapper.UserToWeb(local)})
|
||||
})).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) externalAuth(r *http.Request) (*goloose.BrowserAuth, bool) {
|
||||
auth, _, ok := h.externalAuthWithSlug(r)
|
||||
return auth, ok
|
||||
}
|
||||
|
||||
func (h *Handler) externalAuthWithSlug(r *http.Request) (*goloose.BrowserAuth, string, bool) {
|
||||
if h.browser == nil {
|
||||
return nil, "", false
|
||||
}
|
||||
return h.browser.ForHost(r.Host)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue