2023-10-01 21:44:49 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2023-12-17 22:46:58 +00:00
|
|
|
"bytes"
|
2023-10-01 21:44:49 +01:00
|
|
|
"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-12-17 22:46:58 +00:00
|
|
|
"github.com/1f349/lavender/theme"
|
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-12-14 23:50:09 +00:00
|
|
|
"strings"
|
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()
|
|
|
|
|
2023-10-03 01:14:25 +01:00
|
|
|
// 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-03 01:14:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-17 22:46:58 +00:00
|
|
|
r.GET("/theme/style.css", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
|
|
|
http.ServeContent(rw, req, "style.css", time.Now(), bytes.NewReader(theme.DefaultThemeCss))
|
|
|
|
})
|
|
|
|
|
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()
|
2023-12-14 23:50:09 +00:00
|
|
|
_, ok := (*load)[strings.TrimSuffix(origin, "/")]
|
2023-12-05 18:10:47 +00:00
|
|
|
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
|
|
|
}
|