lavender/server/server.go

171 lines
4.3 KiB
Go
Raw Normal View History

2023-10-01 21:44:49 +01:00
package server
import (
2024-09-13 15:31:40 +01:00
"errors"
2023-10-03 23:20:28 +01:00
"github.com/1f349/cache"
2024-02-07 01:18:17 +00:00
clientStore "github.com/1f349/lavender/client-store"
2024-08-19 22:37:30 +01:00
"github.com/1f349/lavender/conf"
2024-02-07 01:18:17 +00:00
"github.com/1f349/lavender/database"
2023-10-01 21:44:49 +01:00
"github.com/1f349/lavender/issuer"
"github.com/1f349/lavender/pages"
2024-02-07 01:18:17 +00:00
scope2 "github.com/1f349/lavender/scope"
2023-11-03 07:39:58 +00:00
"github.com/1f349/mjwt"
2024-09-13 15:31:40 +01:00
"github.com/go-oauth2/oauth2/v4/generates"
2024-02-07 01:18:17 +00:00
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/server"
"github.com/go-oauth2/oauth2/v4/store"
2023-10-01 21:44:49 +01:00
"github.com/julienschmidt/httprouter"
"net/http"
2024-02-07 01:18:17 +00:00
"net/url"
"path"
2023-12-14 23:50:09 +00:00
"strings"
2023-10-01 21:44:49 +01:00
"time"
)
2024-02-07 01:18:17 +00:00
var errInvalidScope = errors.New("missing required scope")
2024-09-13 15:31:40 +01:00
type httpServer struct {
2024-02-07 01:18:17 +00:00
r *httprouter.Router
oauthSrv *server.Server
oauthMgr *manage.Manager
db *database.Queries
2024-08-19 22:37:30 +01:00
conf conf.Conf
signingKey *mjwt.Issuer
2024-02-07 01:18:17 +00:00
manager *issuer.Manager
2024-09-13 15:31:40 +01:00
// flowState contains the
flowState *cache.Cache[string, flowStateData]
// mailLinkCache contains a mapping of verify uuids to user uuids
mailLinkCache *cache.Cache[mailLinkKey, string]
2023-10-03 23:20:28 +01:00
}
type flowStateData struct {
2024-05-31 13:51:44 +01:00
loginName string
sso *issuer.WellKnownOIDC
redirect string
2023-10-01 21:44:49 +01:00
}
2024-09-13 15:31:40 +01:00
type mailLink byte
2023-10-01 21:44:49 +01:00
2024-09-13 15:31:40 +01:00
const (
mailLinkDelete mailLink = iota
mailLinkResetPassword
mailLinkVerifyEmail
)
2024-09-13 15:31:40 +01:00
type mailLinkKey struct {
action mailLink
data string
}
2024-02-07 01:18:17 +00:00
2024-09-13 15:31:40 +01:00
func SetupRouter(r *httprouter.Router, config conf.Conf, db *database.Queries, signingKey *mjwt.Issuer) {
// remove last slash from baseUrl
config.BaseUrl = strings.TrimRight(config.BaseUrl, "/")
contentCache := time.Now()
2024-09-13 15:31:40 +01:00
hs := &httpServer{
r: r,
2024-02-07 01:18:17 +00:00
db: db,
2024-08-19 22:37:30 +01:00
conf: config,
2024-02-07 01:18:17 +00:00
signingKey: signingKey,
2024-09-13 15:31:40 +01:00
flowState: cache.New[string, flowStateData](),
mailLinkCache: cache.New[mailLinkKey, string](),
2023-10-01 21:44:49 +01:00
}
2024-09-13 15:31:40 +01:00
oauthManager := manage.NewManager()
oauthManager.MapAuthorizeGenerate(generates.NewAuthorizeGenerate())
2024-02-07 01:18:17 +00:00
oauthManager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
oauthManager.MustTokenStorage(store.NewMemoryTokenStore())
2024-09-13 15:31:40 +01:00
oauthManager.MapAccessGenerate(NewMJWTAccessGenerate(signingKey, db))
2024-02-07 01:18:17 +00:00
oauthManager.MapClientStorage(clientStore.New(db))
2024-09-13 15:31:40 +01:00
oauthSrv := server.NewDefaultServer(oauthManager)
2024-02-07 01:18:17 +00:00
oauthSrv.SetClientInfoHandler(func(req *http.Request) (clientID, clientSecret string, err error) {
cId, cSecret, err := server.ClientBasicHandler(req)
if cId == "" && cSecret == "" {
cId, cSecret, err = server.ClientFormHandler(req)
}
if err != nil {
return "", "", err
}
return cId, cSecret, nil
})
oauthSrv.SetUserAuthorizationHandler(hs.oauthUserAuthorization)
oauthSrv.SetAuthorizeScopeHandler(func(rw http.ResponseWriter, req *http.Request) (scope string, err error) {
var form url.Values
if req.Method == http.MethodPost {
form = req.PostForm
} else {
form = req.URL.Query()
}
a := form.Get("scope")
if !scope2.ScopesExist(a) {
return "", errInvalidScope
}
return a, nil
})
2024-02-15 12:41:39 +00:00
addIdTokenSupport(oauthSrv, db, signingKey)
2024-02-07 01:18:17 +00:00
2024-09-13 15:31:40 +01:00
ssoManager := issuer.NewManager(config.SsoServices)
2024-02-07 01:18:17 +00:00
2024-09-13 15:31:40 +01:00
SetupOpenId(r, config.BaseUrl, signingKey)
r.POST("/logout", hs.RequireAuthentication(fu))
2024-02-07 01:18:17 +00:00
// theme styles
r.GET("/assets/*filepath", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
name := params.ByName("filepath")
if strings.Contains(name, "..") {
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
out := pages.RenderCss(path.Join("assets", name))
http.ServeContent(rw, req, path.Base(name), contentCache, out)
2023-12-17 22:46:58 +00:00
})
2024-09-13 15:31:40 +01:00
SetupManageApps(r)
SetupManageUsers(r)
2023-11-15 09:21:09 +00:00
}
2023-10-01 21:44:49 +01:00
2024-09-13 15:31:40 +01:00
func (h *httpServer) SafeRedirect(rw http.ResponseWriter, req *http.Request) {
2024-02-07 01:18:17 +00:00
redirectUrl := req.FormValue("redirect")
if redirectUrl == "" {
http.Redirect(rw, req, "/", http.StatusFound)
return
}
parse, err := url.Parse(redirectUrl)
2023-11-15 09:21:09 +00:00
if err != nil {
2024-02-07 01:18:17 +00:00
http.Error(rw, "Failed to parse redirect url: "+redirectUrl, http.StatusBadRequest)
return
}
if parse.Scheme != "" && parse.Opaque != "" && parse.User != nil && parse.Host != "" {
http.Error(rw, "Invalid redirect url: "+redirectUrl, http.StatusBadRequest)
return
2023-10-01 21:44:49 +01:00
}
2024-02-07 01:18:17 +00:00
http.Redirect(rw, req, parse.String(), http.StatusFound)
}
func ParseClaims(claims string) map[string]bool {
m := make(map[string]bool)
for {
n := strings.IndexByte(claims, ' ')
if n == -1 {
if claims != "" {
m[claims] = true
}
break
}
2023-11-15 09:21:09 +00:00
2024-02-07 01:18:17 +00:00
a := claims[:n]
claims = claims[n+1:]
if a != "" {
m[a] = true
}
2023-11-15 09:21:09 +00:00
}
2024-02-07 01:18:17 +00:00
return m
2023-10-01 21:44:49 +01:00
}