lavender/server/server.go

115 lines
3.0 KiB
Go
Raw Normal View History

2023-10-01 21:44:49 +01:00
package server
import (
"fmt"
2023-10-03 23:20:28 +01:00
"github.com/1f349/cache"
2023-10-01 21:44:49 +01:00
"github.com/1f349/lavender/issuer"
2023-11-03 07:39:58 +00:00
"github.com/1f349/mjwt"
2023-10-01 21:44:49 +01:00
"github.com/julienschmidt/httprouter"
2023-12-05 18:10:47 +00:00
"github.com/rs/cors"
2023-10-08 15:24:59 +01:00
"log"
2023-10-01 21:44:49 +01:00
"net/http"
2023-11-15 09:21:09 +00:00
"sync/atomic"
2023-10-01 21:44:49 +01:00
"time"
)
type HttpServer struct {
2023-11-15 09:21:09 +00:00
Server *http.Server
2023-10-08 15:24:59 +01:00
r *httprouter.Router
2023-11-15 09:21:09 +00:00
conf atomic.Pointer[Conf]
manager atomic.Pointer[issuer.Manager]
2023-10-08 15:24:59 +01:00
signer mjwt.Signer
flowState *cache.Cache[string, flowStateData]
2023-11-15 09:21:09 +00:00
services atomic.Pointer[map[string]AllowedClient]
2023-10-03 23:20:28 +01:00
}
type flowStateData struct {
2023-10-26 11:30:04 +01:00
sso *issuer.WellKnownOIDC
target AllowedClient
2023-10-01 21:44:49 +01:00
}
2023-11-15 09:21:09 +00:00
func NewHttpServer(conf Conf, signer mjwt.Signer) *HttpServer {
2023-10-01 21:44:49 +01:00
r := httprouter.New()
// remove last slash from baseUrl
{
2023-10-08 15:24:59 +01:00
l := len(conf.BaseUrl)
if conf.BaseUrl[l-1] == '/' {
conf.BaseUrl = conf.BaseUrl[:l-1]
}
}
2023-10-01 21:44:49 +01:00
hs := &HttpServer{
2023-11-15 09:21:09 +00:00
Server: &http.Server{
Addr: conf.Listen,
Handler: r,
ReadTimeout: time.Minute,
ReadHeaderTimeout: time.Minute,
WriteTimeout: time.Minute,
IdleTimeout: time.Minute,
MaxHeaderBytes: 2500,
},
2023-10-08 15:24:59 +01:00
r: r,
signer: signer,
flowState: cache.New[string, flowStateData](),
2023-11-15 09:21:09 +00:00
}
err := hs.UpdateConfig(conf)
if err != nil {
log.Fatalln("Failed to load initial config:", err)
return nil
2023-10-01 21:44:49 +01:00
}
r.GET("/", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
rw.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintln(rw, "What is this?")
})
r.GET("/popup", hs.flowPopup)
r.POST("/popup", hs.flowPopupPost)
r.GET("/callback", hs.flowCallback)
2023-12-05 18:10:47 +00:00
2023-12-14 23:41:39 +00:00
// setup CORS options for `/verify` and `/refresh` endpoints
2023-12-05 18:10:47 +00:00
var corsAccessControl = cors.New(cors.Options{
AllowOriginFunc: func(origin string) bool {
load := hs.services.Load()
_, ok := (*load)[origin]
return ok
},
AllowedMethods: []string{http.MethodPost, http.MethodOptions},
AllowedHeaders: []string{"Content-Type"},
AllowCredentials: true,
})
2023-12-14 23:41:39 +00:00
// `/verify` and `/refresh` need CORS headers to be usable on other domains
r.POST("/verify", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
corsAccessControl.ServeHTTP(rw, req, func(writer http.ResponseWriter, request *http.Request) {
hs.verifyHandler(rw, req, params)
})
})
2023-12-05 18:10:47 +00:00
r.POST("/refresh", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
corsAccessControl.ServeHTTP(rw, req, func(writer http.ResponseWriter, request *http.Request) {
hs.refreshHandler(rw, req, params)
})
})
r.OPTIONS("/refresh", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
corsAccessControl.ServeHTTP(rw, req, func(_ http.ResponseWriter, _ *http.Request) {})
})
2023-11-15 09:21:09 +00:00
return hs
}
2023-10-01 21:44:49 +01:00
2023-11-15 09:21:09 +00:00
func (h *HttpServer) UpdateConfig(conf Conf) error {
m, err := issuer.NewManager(conf.SsoServices)
if err != nil {
return fmt.Errorf("failed to reload SSO service manager: %w", err)
2023-10-01 21:44:49 +01:00
}
2023-11-15 09:21:09 +00:00
clientLookup := make(map[string]AllowedClient)
for _, i := range conf.AllowedClients {
clientLookup[i.Url.String()] = i
}
h.conf.Store(&conf)
h.manager.Store(m)
h.services.Store(&clientLookup)
return nil
2023-10-01 21:44:49 +01:00
}