From 0df2cf6681e4e5effbb62e47b04852fd48aff799 Mon Sep 17 00:00:00 2001 From: MrMelon54 Date: Sun, 19 Jan 2025 12:04:25 +0000 Subject: [PATCH] Oh dear --- auth/auth-buttons.go | 15 ++ auth/auth.go | 6 +- auth/authContext/context.go | 48 +++++ auth/providers/{login.go => basic.go} | 27 ++- auth/providers/oauth.go | 46 +++-- auth/providers/otp.go | 37 ++-- auth/providers/passkey.go | 25 ++- conf/conf.go | 3 +- mail/mail.go | 2 +- openid/config.go | 20 +-- openid/config_test.go | 3 +- server/login.go | 167 ++++++++++++------ server/oauth.go | 3 + server/openid.go | 3 +- server/otp.go | 5 +- server/server.go | 39 ++-- tmp/main | Bin 0 -> 185588 bytes url/url.go | 40 +++++ web/astro.config.mjs | 9 +- .../auth-buttons/PasskeyButton.svelte | 7 + web/src/pages/auth-buttons/oauth.astro | 5 + web/src/pages/auth-buttons/passkey.astro | 7 + web/src/pages/auth/basic.astro | 16 ++ web/src/pages/auth/password.astro | 26 --- web/src/pages/auth/sso.astro | 12 ++ web/src/pages/login.astro | 16 +- web/web.go | 42 ++++- web/web_test.go | 46 ++++- 28 files changed, 502 insertions(+), 173 deletions(-) create mode 100644 auth/auth-buttons.go create mode 100644 auth/authContext/context.go rename auth/providers/{login.go => basic.go} (59%) create mode 100644 tmp/main create mode 100644 url/url.go create mode 100644 web/src/components/auth-buttons/PasskeyButton.svelte create mode 100644 web/src/pages/auth-buttons/oauth.astro create mode 100644 web/src/pages/auth-buttons/passkey.astro create mode 100644 web/src/pages/auth/basic.astro delete mode 100644 web/src/pages/auth/password.astro create mode 100644 web/src/pages/auth/sso.astro diff --git a/auth/auth-buttons.go b/auth/auth-buttons.go new file mode 100644 index 0000000..b1f7b34 --- /dev/null +++ b/auth/auth-buttons.go @@ -0,0 +1,15 @@ +package auth + +import ( + "context" + "html/template" + "net/http" +) + +type Button interface { + // ButtonName defines the text to show on the button. + ButtonName() string + + // RenderButtonTemplate returns a template for the button widget. + RenderButtonTemplate(ctx context.Context, req *http.Request) template.HTML +} diff --git a/auth/auth.go b/auth/auth.go index 4d30deb..d9e0624 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -4,8 +4,8 @@ import ( "context" "errors" "fmt" + "github.com/1f349/lavender/auth/authContext" "github.com/1f349/lavender/database" - "html/template" "net/http" ) @@ -38,10 +38,10 @@ type Provider interface { Name() string // RenderTemplate returns HTML to embed in the page template. - RenderTemplate(ctx context.Context, req *http.Request, user *database.User) (template.HTML, error) + RenderTemplate(ctx authContext.TemplateContext) error // AttemptLogin processes the login request. - AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error + AttemptLogin(ctx authContext.TemplateContext) error } type UserSafeError struct { diff --git a/auth/authContext/context.go b/auth/authContext/context.go new file mode 100644 index 0000000..09ea375 --- /dev/null +++ b/auth/authContext/context.go @@ -0,0 +1,48 @@ +package authContext + +import ( + "context" + "github.com/1f349/lavender/database" + "net/http" +) + +func NewTemplateContext(req *http.Request, user *database.User) TemplateContext { + return &BaseTemplateContext{ + req: req, + user: user, + } +} + +type TemplateContext interface { + Context() context.Context + Request() *http.Request + User() *database.User + Render(data any) + Data() any +} + +type BaseTemplateContext struct { + req *http.Request + user *database.User + data any +} + +func (t *BaseTemplateContext) Context() context.Context { + return t.req.Context() +} + +func (t *BaseTemplateContext) Request() *http.Request { + return t.req +} + +func (t *BaseTemplateContext) User() *database.User { + return t.user +} + +func (t *BaseTemplateContext) Render(data any) { + t.data = data +} + +func (t *BaseTemplateContext) Data() any { + return t.data +} diff --git a/auth/providers/login.go b/auth/providers/basic.go similarity index 59% rename from auth/providers/login.go rename to auth/providers/basic.go index e21c222..65c09aa 100644 --- a/auth/providers/login.go +++ b/auth/providers/basic.go @@ -4,10 +4,9 @@ import ( "context" "database/sql" "errors" - "fmt" "github.com/1f349/lavender/auth" + "github.com/1f349/lavender/auth/authContext" "github.com/1f349/lavender/database" - "html/template" "net/http" ) @@ -26,22 +25,36 @@ func (b *BasicLogin) AccessState() auth.State { return auth.StateUnauthorized } func (b *BasicLogin) Name() string { return "basic" } -func (b *BasicLogin) RenderTemplate(ctx context.Context, req *http.Request, user *database.User) (template.HTML, error) { +func (b *BasicLogin) RenderTemplate(ctx authContext.TemplateContext) error { // TODO(melon): rewrite this - return template.HTML(fmt.Sprintf("
%s
", req.FormValue("username"))), nil + req := ctx.Request() + un := req.FormValue("login") + redirect := req.FormValue("redirect") + if redirect == "" { + redirect = "/" + } + ctx.Render(struct { + UserEmail string + Redirect string + }{ + UserEmail: un, + Redirect: redirect, + }) + return nil } -func (b *BasicLogin) AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error { +func (b *BasicLogin) AttemptLogin(ctx authContext.TemplateContext) error { + req := ctx.Request() un := req.FormValue("username") pw := req.FormValue("password") if len(pw) < 8 { return auth.BasicUserSafeError(http.StatusBadRequest, "Password too short") } - login, err := b.DB.CheckLogin(ctx, un, pw) + login, err := b.DB.CheckLogin(ctx.Context(), un, pw) switch { case err == nil: - return auth.LookupUser(ctx, b.DB, login.Subject, user) + return auth.LookupUser(ctx.Context(), b.DB, login.Subject, ctx.User()) case errors.Is(err, sql.ErrNoRows): return auth.BasicUserSafeError(http.StatusForbidden, "Username or password is invalid") default: diff --git a/auth/providers/oauth.go b/auth/providers/oauth.go index 855ffd5..6375246 100644 --- a/auth/providers/oauth.go +++ b/auth/providers/oauth.go @@ -5,8 +5,10 @@ import ( "fmt" "github.com/1f349/cache" "github.com/1f349/lavender/auth" + "github.com/1f349/lavender/auth/authContext" "github.com/1f349/lavender/database" "github.com/1f349/lavender/issuer" + "github.com/1f349/lavender/url" "github.com/google/uuid" "golang.org/x/oauth2" "html/template" @@ -20,12 +22,15 @@ type flowStateData struct { redirect string } -var _ auth.Provider = (*OAuthLogin)(nil) +var ( + _ auth.Provider = (*OAuthLogin)(nil) + _ auth.Button = (*OAuthLogin)(nil) +) type OAuthLogin struct { DB *database.Queries - BaseUrl string + BaseUrl *url.URL flow *cache.Cache[string, flowStateData] } @@ -34,29 +39,41 @@ func (o OAuthLogin) Init() { o.flow = cache.New[string, flowStateData]() } +func (o OAuthLogin) authUrlBase(ref string) *url.URL { + return o.BaseUrl.Resolve("oauth", o.Name(), ref) +} + func (o OAuthLogin) AccessState() auth.State { return auth.StateUnauthorized } func (o OAuthLogin) Name() string { return "oauth" } -func (o OAuthLogin) RenderTemplate(ctx context.Context, req *http.Request, user *database.User) (template.HTML, error) { - return "
OAuth Login Template
", nil +func (o OAuthLogin) RenderTemplate(ctx authContext.TemplateContext) error { + // TODO: does this need to exist? + ctx.Render(map[string]any{"Error": "no"}) + return nil } -func (o OAuthLogin) AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error { - login, ok := ctx.Value(oauthServiceLogin(0)).(*issuer.WellKnownOIDC) +func (o OAuthLogin) AttemptLogin(ctx authContext.TemplateContext) error { + rCtx := ctx.Context() + + login, ok := rCtx.Value(oauthServiceLogin(0)).(*issuer.WellKnownOIDC) if !ok { return fmt.Errorf("missing issuer wellknown") } - loginName := ctx.Value("login_full").(string) - loginUn := ctx.Value("login_username").(string) + loginName := rCtx.Value("login_full").(string) + loginUn := rCtx.Value("login_username").(string) // save state for use later state := login.Config.Namespace + ":" + uuid.NewString() - o.flow.Set(state, flowStateData{loginName, login, req.PostFormValue("redirect")}, time.Now().Add(15*time.Minute)) + o.flow.Set(state, flowStateData{ + loginName: loginName, + sso: login, + redirect: ctx.Request().PostFormValue("redirect"), + }, time.Now().Add(15*time.Minute)) // generate oauth2 config and redirect to authorize URL oa2conf := login.OAuth2Config - oa2conf.RedirectURL = o.BaseUrl + "/callback" + oa2conf.RedirectURL = o.authUrlBase("callback").String() nextUrl := oa2conf.AuthCodeURL(state, oauth2.SetAuthURLParam("login_name", loginUn)) return auth.RedirectError{Target: nextUrl, Code: http.StatusFound} @@ -68,7 +85,7 @@ func (o OAuthLogin) OAuthCallback(rw http.ResponseWriter, req *http.Request, inf http.Error(rw, "Invalid flow state", http.StatusBadRequest) return } - token, err := flowState.sso.OAuth2Config.Exchange(context.Background(), req.FormValue("code"), oauth2.SetAuthURLParam("redirect_uri", o.BaseUrl+"/callback")) + token, err := flowState.sso.OAuth2Config.Exchange(context.Background(), req.FormValue("code"), oauth2.SetAuthURLParam("redirect_uri", o.authUrlBase("callback").String())) if err != nil { http.Error(rw, "Failed to exchange code for token", http.StatusInternalServerError) return @@ -90,6 +107,13 @@ func (o OAuthLogin) OAuthCallback(rw http.ResponseWriter, req *http.Request, inf redirect(rw, req) } +func (o OAuthLogin) ButtonName() string { return o.Name() } + +func (o OAuthLogin) RenderButtonTemplate(ctx context.Context, req *http.Request) template.HTML { + // o.authUrlBase("button") + return "
OAuth Login Template
" +} + type oauthServiceLogin int func WithWellKnown(ctx context.Context, login *issuer.WellKnownOIDC) context.Context { diff --git a/auth/providers/otp.go b/auth/providers/otp.go index ed1279b..cfb7f63 100644 --- a/auth/providers/otp.go +++ b/auth/providers/otp.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" "github.com/1f349/lavender/auth" + "github.com/1f349/lavender/auth/authContext" "github.com/1f349/lavender/database" "github.com/xlzd/gotp" - "html/template" "net/http" "time" ) @@ -30,19 +30,8 @@ func (o *OtpLogin) AccessState() auth.State { return auth.StateBasic } func (o *OtpLogin) Name() string { return "basic" } -func (o *OtpLogin) RenderTemplate(_ context.Context, _ *http.Request, user *database.User) (template.HTML, error) { - if user == nil || user.Subject == "" { - return "", fmt.Errorf("requires previous factor") - } - if user.OtpSecret == "" || !isDigitsSupported(user.OtpDigits) { - return "", fmt.Errorf("user does not support factor") - } - - // no need to provide render data - return "
OTP login template
", nil -} - -func (o *OtpLogin) AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error { +func (o *OtpLogin) RenderTemplate(ctx authContext.TemplateContext) error { + user := ctx.User() if user == nil || user.Subject == "" { return fmt.Errorf("requires previous factor") } @@ -50,7 +39,25 @@ func (o *OtpLogin) AttemptLogin(ctx context.Context, req *http.Request, user *da return fmt.Errorf("user does not support factor") } - code := req.FormValue("code") + // TODO: is this right? + ctx.Render(map[string]any{ + "Redirect": "/", + }) + + // no need to provide render data + return nil +} + +func (o *OtpLogin) AttemptLogin(ctx authContext.TemplateContext) error { + user := ctx.User() + if user == nil || user.Subject == "" { + return fmt.Errorf("requires previous factor") + } + if user.OtpSecret == "" || !isDigitsSupported(user.OtpDigits) { + return fmt.Errorf("user does not support factor") + } + + code := ctx.Request().FormValue("code") if !validateTotp(user.OtpSecret, int(user.OtpDigits), code) { return auth.BasicUserSafeError(http.StatusBadRequest, "invalid OTP code") diff --git a/auth/providers/passkey.go b/auth/providers/passkey.go index 642f5a4..7e029a4 100644 --- a/auth/providers/passkey.go +++ b/auth/providers/passkey.go @@ -4,7 +4,7 @@ import ( "context" "fmt" "github.com/1f349/lavender/auth" - "github.com/1f349/lavender/database" + "github.com/1f349/lavender/auth/authContext" "html/template" "net/http" ) @@ -13,7 +13,10 @@ type passkeyLoginDB interface { auth.LookupUserDB } -var _ auth.Provider = (*PasskeyLogin)(nil) +var ( + _ auth.Provider = (*PasskeyLogin)(nil) + _ auth.Button = (*PasskeyLogin)(nil) +) type PasskeyLogin struct { DB passkeyLoginDB @@ -23,12 +26,13 @@ func (p *PasskeyLogin) AccessState() auth.State { return auth.StateUnauthorized func (p *PasskeyLogin) Name() string { return "passkey" } -func (p *PasskeyLogin) RenderTemplate(ctx context.Context, req *http.Request, user *database.User) (template.HTML, error) { +func (p *PasskeyLogin) RenderTemplate(ctx authContext.TemplateContext) error { + user := ctx.User() if user == nil || user.Subject == "" { - return "", fmt.Errorf("requires previous factor") + return fmt.Errorf("requires previous factor") } if user.OtpSecret == "" { - return "", fmt.Errorf("user does not support factor") + return fmt.Errorf("user does not support factor") } panic("implement me") @@ -40,7 +44,8 @@ func init() { passkeyShortcut = true } -func (p *PasskeyLogin) AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error { +func (p *PasskeyLogin) AttemptLogin(ctx authContext.TemplateContext) error { + user := ctx.User() if user.Subject == "" && !passkeyShortcut { return fmt.Errorf("requires previous factor") } @@ -48,3 +53,11 @@ func (p *PasskeyLogin) AttemptLogin(ctx context.Context, req *http.Request, user //TODO implement me panic("implement me") } + +func (p *PasskeyLogin) ButtonName() string { + return "Login with Passkey" +} + +func (p *PasskeyLogin) RenderButtonTemplate(ctx context.Context, req *http.Request) template.HTML { + return "
Passkey Button
" +} diff --git a/conf/conf.go b/conf/conf.go index 841e7ee..c7f9009 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -2,12 +2,13 @@ package conf import ( "github.com/1f349/lavender/issuer" + "github.com/1f349/lavender/url" "github.com/1f349/simplemail" ) type Conf struct { Listen string `yaml:"listen"` - BaseUrl string `yaml:"baseUrl"` + BaseUrl url.URL `yaml:"baseUrl"` ServiceName string `yaml:"serviceName"` Issuer string `yaml:"issuer"` Kid string `yaml:"kid"` diff --git a/mail/mail.go b/mail/mail.go index dca2e29..3bcde6b 100644 --- a/mail/mail.go +++ b/mail/mail.go @@ -28,7 +28,7 @@ func New(sender *simplemail.Mail, wd, name string) (*Mail, error) { err := os.Mkdir(mailDir, os.ModePerm) if err == nil || errors.Is(err, os.ErrExist) { wdFs := os.DirFS(mailDir) - o = overlapfs.OverlapFS{A: embeddedTemplates, B: wdFs} + o = overlapfs.OverlapFS{A: o, B: wdFs} } } diff --git a/openid/config.go b/openid/config.go index 99fbdf8..7c52e2b 100644 --- a/openid/config.go +++ b/openid/config.go @@ -1,8 +1,6 @@ package openid -import ( - "strings" -) +import "github.com/1f349/lavender/url" type Config struct { Issuer string `json:"issuer"` @@ -16,21 +14,17 @@ type Config struct { JwksUri string `json:"jwks_uri"` } -func GenConfig(baseUrl string, scopes, claims []string) Config { - baseUrlRaw := baseUrl - if !strings.HasSuffix(baseUrl, "/") { - baseUrl += "/" - } +func GenConfig(baseUrl *url.URL, scopes, claims []string) Config { return Config{ - Issuer: baseUrlRaw, - AuthorizationEndpoint: baseUrl + "authorize", - TokenEndpoint: baseUrl + "token", - UserInfoEndpoint: baseUrl + "userinfo", + Issuer: baseUrl.String(), + AuthorizationEndpoint: baseUrl.Resolve("authorize").String(), + TokenEndpoint: baseUrl.Resolve("token").String(), + UserInfoEndpoint: baseUrl.Resolve("userinfo").String(), ResponseTypesSupported: []string{"code"}, ScopesSupported: scopes, ClaimsSupported: claims, GrantTypesSupported: []string{"authorization_code", "refresh_token"}, - JwksUri: baseUrl + ".well-known/jwks.json", + JwksUri: baseUrl.Resolve(".well-known/jwks.json").String(), } } diff --git a/openid/config_test.go b/openid/config_test.go index d8a8158..b8afa2d 100644 --- a/openid/config_test.go +++ b/openid/config_test.go @@ -1,6 +1,7 @@ package openid import ( + "github.com/1f349/lavender/url" "github.com/stretchr/testify/assert" "testing" ) @@ -16,5 +17,5 @@ func TestGenConfig(t *testing.T) { ClaimsSupported: []string{"name", "email", "preferred_username"}, GrantTypesSupported: []string{"authorization_code", "refresh_token"}, JwksUri: "https://example.com/.well-known/jwks.json", - }, GenConfig("https://example.com", []string{"openid", "email"}, []string{"name", "email", "preferred_username"})) + }, GenConfig(url.MustParse("https://example.com"), []string{"openid", "email"}, []string{"name", "email", "preferred_username"})) } diff --git a/server/login.go b/server/login.go index 992700c..f6d0f5b 100644 --- a/server/login.go +++ b/server/login.go @@ -1,25 +1,29 @@ package server import ( + "bytes" "context" "database/sql" "encoding/json" "errors" "fmt" - auth2 "github.com/1f349/lavender/auth" + "github.com/1f349/lavender/auth" + "github.com/1f349/lavender/auth/authContext" "github.com/1f349/lavender/auth/providers" "github.com/1f349/lavender/database" "github.com/1f349/lavender/database/types" "github.com/1f349/lavender/issuer" + "github.com/1f349/lavender/logger" "github.com/1f349/lavender/web" "github.com/1f349/mjwt" - "github.com/1f349/mjwt/auth" + mjwtAuth "github.com/1f349/mjwt/auth" "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" "github.com/julienschmidt/httprouter" "github.com/mrmelon54/pronouns" "golang.org/x/oauth2" "golang.org/x/text/language" + "html/template" "net/http" "net/url" "strings" @@ -42,7 +46,7 @@ func getUserLoginName(req *http.Request) string { return originUrl.Query().Get("login_name") } -func (h *httpServer) testAuthSources(req *http.Request, user *database.User, factor auth2.State) map[string]bool { +func (h *httpServer) testAuthSources(req *http.Request, user *database.User, factor auth.State) map[string]bool { authSource := make(map[string]bool) data := make(map[string]any) for _, i := range h.authSources { @@ -50,23 +54,46 @@ func (h *httpServer) testAuthSources(req *http.Request, user *database.User, fac if i.AccessState() != factor { continue } - page, err := i.RenderTemplate(req.Context(), req, user) - _ = page + err := i.RenderTemplate(authContext.NewTemplateContext(req, user)) authSource[i.Name()] = err == nil clear(data) } return authSource } -func (h *httpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth auth2.UserAuth) { - if !auth.IsGuest() { +func (h *httpServer) getAuthWithState(state auth.State) auth.Provider { + for _, i := range h.authSources { + if i.AccessState() == state { + return i + } + } + return nil +} + +func (h *httpServer) renderAuthTemplate(req *http.Request, provider auth.Provider) (template.HTML, error) { + tmpCtx := authContext.NewTemplateContext(req, new(database.User)) + + err := provider.RenderTemplate(tmpCtx) + if err != nil { + return "", err + } + + w := new(bytes.Buffer) + if web.RenderPageTemplate(w, "auth/"+provider.Name(), tmpCtx.Data()) { + return template.HTML(w.Bytes()), nil + } + return "", fmt.Errorf("failed to render auth template") +} + +func (h *httpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, userAuth auth.UserAuth) { + if !userAuth.IsGuest() { h.SafeRedirect(rw, req) return } cookie, err := req.Cookie("lavender-login-name") if err == nil && cookie.Valid() == nil { - user, err := h.db.GetUser(req.Context(), auth.Subject) + user, err := h.db.GetUser(req.Context(), userAuth.Subject) var userPtr *database.User switch { case err == nil: @@ -78,30 +105,55 @@ func (h *httpServer) loginGet(rw http.ResponseWriter, req *http.Request, _ httpr return } - fmt.Printf("%#v\n", h.testAuthSources(req, userPtr, auth2.StateBasic)) + fmt.Printf("%#v\n", h.testAuthSources(req, userPtr, auth.StateBasic)) web.RenderPageTemplate(rw, "login-memory", map[string]any{ "ServiceName": h.conf.ServiceName, "LoginName": cookie.Value, "Redirect": req.URL.Query().Get("redirect"), "Source": "start", - "Auth": h.testAuthSources(req, userPtr, auth2.StateBasic), + "Auth": h.testAuthSources(req, userPtr, auth.StateBasic), }) return } + buttonTemplates := make([]template.HTML, len(h.authButtons)) + for i := range h.authButtons { + buttonTemplates[i] = h.authButtons[i].RenderButtonTemplate(req.Context(), req) + } + + type loginError struct { + Error string `json:"error"` + } + + var renderTemplate template.HTML + + provider := h.getAuthWithState(auth.StateUnauthorized) + + // Maybe the admin has disabled some login providers but does have a button based provider available? + if provider != nil { + renderTemplate, err = h.renderAuthTemplate(req, provider) + if err != nil { + logger.Logger.Warn("No provider for login") + web.RenderPageTemplate(rw, "login-error", loginError{Error: "No available provider for login"}) + return + } + } + // render different page sources web.RenderPageTemplate(rw, "login", map[string]any{ - "ServiceName": h.conf.ServiceName, - "LoginName": "", - "Redirect": req.URL.Query().Get("redirect"), - "Source": "start", - "Auth": h.testAuthSources(req, nil, auth2.StateBasic), + "ServiceName": h.conf.ServiceName, + "LoginName": "", + "Redirect": req.URL.Query().Get("redirect"), + "Source": "start", + "Auth": h.testAuthSources(req, nil, auth.StateUnauthorized), + "AuthTemplate": renderTemplate, + "AuthButtons": buttonTemplates, }) } -func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth auth2.UserAuth) { - if !auth.IsGuest() { +func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth2 auth.UserAuth) { + if !auth2.IsGuest() { h.SafeRedirect(rw, req) return } @@ -120,7 +172,7 @@ func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http }).String(), http.StatusFound) return } - loginName := req.PostFormValue("loginname") + loginName := req.PostFormValue("email") // append local namespace if @ is missing n := strings.IndexByte(loginName, '@') @@ -156,12 +208,16 @@ func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http SameSite: http.SameSiteLaxMode, }) - var redirectError auth2.RedirectError + var redirectError auth.RedirectError + + // TODO(melon): rewrite login system here // if the login is the local server if login == issuer.MeWellKnown { // TODO(melon): work on this - err := h.authBasic.AttemptLogin(ctx, req, nil) + // TODO: rewrite + //err := h.authBasic.AttemptLogin(ctx, req, nil) + var err error switch { case errors.As(err, &redirectError): http.Redirect(rw, req, redirectError.Target, redirectError.Code) @@ -170,7 +226,9 @@ func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http return } - err := h.authOAuth.AttemptLogin(ctx, req, nil) + // TODO: rewrite + //err := h.authOAuth.AttemptLogin(ctx, req, nil) + var err error switch { case errors.As(err, &redirectError): http.Redirect(rw, req, redirectError.Target, redirectError.Code) @@ -178,14 +236,15 @@ func (h *httpServer) loginPost(rw http.ResponseWriter, req *http.Request, _ http } } -func (h *httpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, userAuth auth2.UserAuth) { - h.authOAuth.OAuthCallback(rw, req, h.updateExternalUserInfo, h.setLoginDataCookie, h.SafeRedirect) +func (h *httpServer) loginCallback(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, _ auth.UserAuth) { + // TODO: rewrite + //h.authOAuth.OAuthCallback(rw, req, h.updateExternalUserInfo, h.setLoginDataCookie, h.SafeRedirect) } -func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellKnownOIDC, token *oauth2.Token) (auth2.UserAuth, error) { +func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellKnownOIDC, token *oauth2.Token) (auth.UserAuth, error) { sessionData, err := h.fetchUserInfo(sso, token) if err != nil || sessionData.Subject == "" { - return auth2.UserAuth{}, fmt.Errorf("failed to fetch user info") + return auth.UserAuth{}, fmt.Errorf("failed to fetch user info") } // TODO(melon): fix this to use a merging of lavender and tulip auth @@ -206,9 +265,9 @@ func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellK err = h.DbTxError(func(tx *database.Queries) error { return h.updateOAuth2UserProfile(req.Context(), tx, sessionData) }) - return auth2.UserAuth{ + return auth.UserAuth{ Subject: userSubject, - Factor: auth2.StateExtended, + Factor: auth.StateExtended, UserInfo: sessionData.UserInfo, }, err case errors.Is(err, sql.ErrNoRows): @@ -216,12 +275,12 @@ func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellK break default: // another error occurred - return auth2.UserAuth{}, err + return auth.UserAuth{}, err } // guard for disabled registration if !sso.Config.Registration { - return auth2.UserAuth{}, fmt.Errorf("registration is not enabled for this authentication source") + return auth.UserAuth{}, fmt.Errorf("registration is not enabled for this authentication source") } // TODO(melon): rework this @@ -246,7 +305,7 @@ func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellK return h.updateOAuth2UserProfile(req.Context(), tx, sessionData) }) if err != nil { - return auth2.UserAuth{}, err + return auth.UserAuth{}, err } // only continues if the above tx succeeds @@ -258,20 +317,20 @@ func (h *httpServer) updateExternalUserInfo(req *http.Request, sso *issuer.WellK Subject: sessionData.Subject, }) }); err != nil { - return auth2.UserAuth{}, err + return auth.UserAuth{}, err } // TODO(melon): this feels bad - sessionData = auth2.UserAuth{ + sessionData = auth.UserAuth{ Subject: userSubject, - Factor: auth2.StateExtended, + Factor: auth.StateExtended, UserInfo: sessionData.UserInfo, } return sessionData, nil } -func (h *httpServer) updateOAuth2UserProfile(ctx context.Context, tx *database.Queries, sessionData auth2.UserAuth) error { +func (h *httpServer) updateOAuth2UserProfile(ctx context.Context, tx *database.Queries, sessionData auth.UserAuth) error { // all of these updates must succeed return tx.UseTx(ctx, func(tx *database.Queries) error { name := sessionData.UserInfo.GetStringOrDefault("name", "Unknown User") @@ -312,9 +371,9 @@ const twelveHours = 12 * time.Hour const oneWeek = 7 * 24 * time.Hour type lavenderLoginAccess struct { - UserInfo auth2.UserInfoFields `json:"user_info"` - Factor auth2.State `json:"factor"` - auth.AccessTokenClaims + UserInfo auth.UserInfoFields `json:"user_info"` + Factor auth.State `json:"factor"` + mjwtAuth.AccessTokenClaims } func (l lavenderLoginAccess) Valid() error { return l.AccessTokenClaims.Valid() } @@ -323,28 +382,28 @@ func (l lavenderLoginAccess) Type() string { return "lavender-login-access" } type lavenderLoginRefresh struct { Login string `json:"login"` - auth.RefreshTokenClaims + mjwtAuth.RefreshTokenClaims } func (l lavenderLoginRefresh) Valid() error { return l.RefreshTokenClaims.Valid() } func (l lavenderLoginRefresh) Type() string { return "lavender-login-refresh" } -func (h *httpServer) setLoginDataCookie(rw http.ResponseWriter, authData auth2.UserAuth, loginName string) bool { - ps := auth.NewPermStorage() +func (h *httpServer) setLoginDataCookie(rw http.ResponseWriter, authData auth.UserAuth, loginName string) bool { + ps := mjwtAuth.NewPermStorage() accId := uuid.NewString() - gen, err := h.signingKey.GenerateJwt(authData.Subject, accId, jwt.ClaimStrings{h.conf.BaseUrl}, twelveHours, lavenderLoginAccess{ + gen, err := h.signingKey.GenerateJwt(authData.Subject, accId, jwt.ClaimStrings{h.conf.BaseUrl.String()}, twelveHours, lavenderLoginAccess{ UserInfo: authData.UserInfo, Factor: authData.Factor, - AccessTokenClaims: auth.AccessTokenClaims{Perms: ps}, + AccessTokenClaims: mjwtAuth.AccessTokenClaims{Perms: ps}, }) if err != nil { http.Error(rw, "Failed to generate cookie token", http.StatusInternalServerError) return true } - ref, err := h.signingKey.GenerateJwt(authData.Subject, uuid.NewString(), jwt.ClaimStrings{h.conf.BaseUrl}, oneWeek, lavenderLoginRefresh{ + ref, err := h.signingKey.GenerateJwt(authData.Subject, uuid.NewString(), jwt.ClaimStrings{h.conf.BaseUrl.String()}, oneWeek, lavenderLoginRefresh{ Login: loginName, - RefreshTokenClaims: auth.RefreshTokenClaims{AccessTokenId: accId}, + RefreshTokenClaims: mjwtAuth.RefreshTokenClaims{AccessTokenId: accId}, }) if err != nil { http.Error(rw, "Failed to generate cookie token", http.StatusInternalServerError) @@ -382,12 +441,12 @@ func readJwtCookie[T mjwt.Claims](req *http.Request, cookieName string, signingK return b, nil } -func (h *httpServer) readLoginAccessCookie(rw http.ResponseWriter, req *http.Request, u *auth2.UserAuth) error { +func (h *httpServer) readLoginAccessCookie(rw http.ResponseWriter, req *http.Request, u *auth.UserAuth) error { loginData, err := readJwtCookie[lavenderLoginAccess](req, "lavender-login-access", h.signingKey.KeyStore()) if err != nil { return h.readLoginRefreshCookie(rw, req, u) } - *u = auth2.UserAuth{ + *u = auth.UserAuth{ Subject: loginData.Subject, Factor: loginData.Claims.Factor, UserInfo: loginData.Claims.UserInfo, @@ -395,7 +454,7 @@ func (h *httpServer) readLoginAccessCookie(rw http.ResponseWriter, req *http.Req return nil } -func (h *httpServer) readLoginRefreshCookie(rw http.ResponseWriter, req *http.Request, userAuth *auth2.UserAuth) error { +func (h *httpServer) readLoginRefreshCookie(rw http.ResponseWriter, req *http.Request, userAuth *auth.UserAuth) error { refreshData, err := readJwtCookie[lavenderLoginRefresh](req, "lavender-login-refresh", h.signingKey.KeyStore()) if err != nil { return err @@ -433,28 +492,28 @@ func (h *httpServer) readLoginRefreshCookie(rw http.ResponseWriter, req *http.Re return nil } -func (h *httpServer) fetchUserInfo(sso *issuer.WellKnownOIDC, token *oauth2.Token) (auth2.UserAuth, error) { +func (h *httpServer) fetchUserInfo(sso *issuer.WellKnownOIDC, token *oauth2.Token) (auth.UserAuth, error) { res, err := sso.OAuth2Config.Client(context.Background(), token).Get(sso.UserInfoEndpoint) if err != nil || res.StatusCode != http.StatusOK { - return auth2.UserAuth{}, fmt.Errorf("request failed") + return auth.UserAuth{}, fmt.Errorf("request failed") } defer res.Body.Close() - var userInfoJson auth2.UserInfoFields + var userInfoJson auth.UserInfoFields if err := json.NewDecoder(res.Body).Decode(&userInfoJson); err != nil { - return auth2.UserAuth{}, err + return auth.UserAuth{}, err } subject, ok := userInfoJson.GetString("sub") if !ok { - return auth2.UserAuth{}, fmt.Errorf("invalid subject") + return auth.UserAuth{}, fmt.Errorf("invalid subject") } // TODO(melon): there is no need for this //subject += "@" + sso.Config.Namespace - return auth2.UserAuth{ + return auth.UserAuth{ Subject: subject, - Factor: auth2.StateExtended, + Factor: auth.StateExtended, UserInfo: userInfoJson, }, nil } diff --git a/server/oauth.go b/server/oauth.go index 1046b59..89c0bb2 100644 --- a/server/oauth.go +++ b/server/oauth.go @@ -25,6 +25,9 @@ import ( "time" ) +// TODO(melon): add ldap client, radius client and other login support +// TODO(melon): add ldap server, radius server support + func SetupOAuth2(r *httprouter.Router, hs *httpServer, key *mjwt.Issuer, db *database.Queries) { oauthManager := manage.NewDefaultManager() oauthManager.MapAuthorizeGenerate(generates.NewAuthorizeGenerate()) diff --git a/server/openid.go b/server/openid.go index 7a56b9c..fd61587 100644 --- a/server/openid.go +++ b/server/openid.go @@ -5,12 +5,13 @@ import ( "encoding/json" "github.com/1f349/lavender/logger" "github.com/1f349/lavender/openid" + "github.com/1f349/lavender/url" "github.com/1f349/mjwt" "github.com/julienschmidt/httprouter" "net/http" ) -func SetupOpenId(r *httprouter.Router, baseUrl string, signingKey *mjwt.Issuer) { +func SetupOpenId(r *httprouter.Router, baseUrl *url.URL, signingKey *mjwt.Issuer) { openIdConf := openid.GenConfig(baseUrl, []string{ "openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale", }, []string{ diff --git a/server/otp.go b/server/otp.go index 25171fb..ed4dac8 100644 --- a/server/otp.go +++ b/server/otp.go @@ -26,7 +26,10 @@ func (h *httpServer) editOtpPost(rw http.ResponseWriter, req *http.Request, _ ht } otpInput := req.Form.Get("code") - err := h.authOtp.VerifyOtpCode(req.Context(), auth.Subject, otpInput) + _ = otpInput + // TODO: rewrite + //err := h.authOtp.VerifyOtpCode(req.Context(), auth.Subject, otpInput) + var err error if err != nil { http.Error(rw, "Invalid OTP code", http.StatusBadRequest) return diff --git a/server/server.go b/server/server.go index 28bb9e4..a6baef1 100644 --- a/server/server.go +++ b/server/server.go @@ -35,11 +35,8 @@ type httpServer struct { // mailLinkCache contains a mapping of verify uuids to user uuids mailLinkCache *cache.Cache[mailLinkKey, string] - authBasic *providers.BasicLogin - authOtp *providers.OtpLogin - authOAuth *providers.OAuthLogin - authSources []auth.Provider + authButtons []auth.Button } type mailLink byte @@ -56,13 +53,26 @@ type mailLinkKey struct { } func SetupRouter(r *httprouter.Router, config conf.Conf, mailSender *mail.Mail, db *database.Queries, signingKey *mjwt.Issuer) { - // remove last slash from baseUrl - config.BaseUrl = strings.TrimRight(config.BaseUrl, "/") - + // TODO: move auth provider init to main function + // TODO: allow dynamically changing the providers based on database information authBasic := &providers.BasicLogin{DB: db} authOtp := &providers.OtpLogin{DB: db} - authOAuth := &providers.OAuthLogin{DB: db, BaseUrl: config.BaseUrl} + authOAuth := &providers.OAuthLogin{DB: db, BaseUrl: &config.BaseUrl} authOAuth.Init() + authPasskey := &providers.PasskeyLogin{DB: db} + + authSources := []auth.Provider{ + authBasic, + authOtp, + authOAuth, + authPasskey, + } + authButtons := make([]auth.Button, 0) + for _, source := range authSources { + if button, isButton := source.(auth.Button); isButton { + authButtons = append(authButtons, button) + } + } hs := &httpServer{ r: r, @@ -73,15 +83,8 @@ func SetupRouter(r *httprouter.Router, config conf.Conf, mailSender *mail.Mail, mailLinkCache: cache.New[mailLinkKey, string](), - authBasic: authBasic, - authOtp: authOtp, - authOAuth: authOAuth, - //authPasskey: &auth.PasskeyLogin{DB: db}, - - authSources: []auth.Provider{ - authBasic, - authOtp, - }, + authSources: authSources, + authButtons: authButtons, } var err error @@ -90,7 +93,7 @@ func SetupRouter(r *httprouter.Router, config conf.Conf, mailSender *mail.Mail, logger.Logger.Fatal("Failed to load SSO services", "err", err) } - SetupOpenId(r, config.BaseUrl, signingKey) + SetupOpenId(r, &config.BaseUrl, signingKey) r.GET("/", hs.OptionalAuthentication(false, hs.Home)) r.POST("/logout", hs.RequireAuthentication(hs.logoutPost)) diff --git a/tmp/main b/tmp/main new file mode 100644 index 0000000000000000000000000000000000000000..75ae25367c23efd6fa04c78e1851e9176b66b02e GIT binary patch literal 185588 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 %f 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 z ztP2K$8U=n>r$jvi#@OCqD^q}LN4JI=S0tQR4@QPIG+!`q4!~O7)-8ZVIr?&WZ)rPaQXbcHHafuvk0!`IXknAg4L8lG1{XH(|N4|?5JPk#@a*fX5vMWtkfcO2C}k=N5yZ(i;psvG3np;xY6)u-+zUx8SP$>V>45|gssk zu5`QMiU!laH;P!ZZx3AY#+yfzy9= C-D@zfrkwNBmn>{2Rj|ZhVd_7hjxgW>a;_ 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 + function loginWithPasskey() { + alert("Sign in with Passkey"); + } + + +Sign in with Passkey diff --git a/web/src/pages/auth-buttons/oauth.astro b/web/src/pages/auth-buttons/oauth.astro new file mode 100644 index 0000000..ee55cc5 --- /dev/null +++ b/web/src/pages/auth-buttons/oauth.astro @@ -0,0 +1,5 @@ +--- +export const partial = true; +--- + +[[ .ButtonName ]] diff --git a/web/src/pages/auth-buttons/passkey.astro b/web/src/pages/auth-buttons/passkey.astro new file mode 100644 index 0000000..6da81e7 --- /dev/null +++ b/web/src/pages/auth-buttons/passkey.astro @@ -0,0 +1,7 @@ +--- +export const partial = true; + +import PasskeyButton from '../../components/auth-buttons/PasskeyButton.svelte'; +--- + + diff --git a/web/src/pages/auth/basic.astro b/web/src/pages/auth/basic.astro new file mode 100644 index 0000000..d044c5a --- /dev/null +++ b/web/src/pages/auth/basic.astro @@ -0,0 +1,16 @@ +--- +export const partial = true; +--- + +
+ +
+ + +
+
+ + +
+ +
diff --git a/web/src/pages/auth/password.astro b/web/src/pages/auth/password.astro deleted file mode 100644 index eb82814..0000000 --- a/web/src/pages/auth/password.astro +++ /dev/null @@ -1,26 +0,0 @@ ---- -export const partial = true; ---- - -
- -
- - -
-
- - -
- -
- -
-

Enter your email address below to receive an email with instructions on how to reset your password.

-

Please note this only works if your email address is already verified.

-
- - -
- -
diff --git a/web/src/pages/auth/sso.astro b/web/src/pages/auth/sso.astro new file mode 100644 index 0000000..8eff4ec --- /dev/null +++ b/web/src/pages/auth/sso.astro @@ -0,0 +1,12 @@ +--- +export const partial = true; +--- + +
+ +
+ + +
+ +
diff --git a/web/src/pages/login.astro b/web/src/pages/login.astro index ca2bf80..fea5857 100644 --- a/web/src/pages/login.astro +++ b/web/src/pages/login.astro @@ -9,6 +9,17 @@ import Layout from "../layouts/Layout.astro";

Check your inbox for a verification email

[[ end ]] [[ .AuthTemplate ]] +
+
+

Enter your email address below to receive an email with instructions on how to reset your password.

+

Please note this only works if your email address is already verified.

+
+ + +
+ +
+
[[ if gt (len .AuthButtons) 0 ]]
[[ range $authButton := .AuthButtons ]] @@ -16,9 +27,4 @@ import Layout from "../layouts/Layout.astro"; [[ end ]]
[[ end ]] - diff --git a/web/web.go b/web/web.go index 88c781d..8ddad9f 100644 --- a/web/web.go +++ b/web/web.go @@ -18,7 +18,7 @@ import ( var ( //go:embed dist - webBuild embed.FS + webDist embed.FS webCombinedDir fs.FS pageTemplates *template.Template @@ -27,6 +27,11 @@ var ( func LoadPages(wd string) error { return loadOnce.Do(func() (err error) { + webBuild, err := fs.Sub(webDist, "dist") + if err != nil { + return err + } + webCombinedDir = webBuild if wd != "" { @@ -44,16 +49,39 @@ func LoadPages(wd string) error { // TODO(melon): figure this out layer webCombinedDir = webBuild - pageTemplates, err = template.New("web").Delims("[[", "]]").Funcs(template.FuncMap{ + pageTemplates, err = findAndParseTemplates(webCombinedDir, template.FuncMap{ "emailHide": utils.EmailHide, "renderOptionTag": renderOptionTag, "renderCheckboxTag": renderCheckboxTag, - }).ParseFS(webCombinedDir, "dist/*.html") - + }) return err }) } +func findAndParseTemplates(rootDir fs.FS, funcMap template.FuncMap) (*template.Template, error) { + root := template.New("") + + err := fs.WalkDir(rootDir, ".", func(p string, d fs.DirEntry, e1 error) error { + if d.IsDir() || !strings.HasSuffix(p, ".html") { + return nil + } + + if e1 != nil { + return e1 + } + + fileContents, err := fs.ReadFile(webCombinedDir, p) + if err != nil { + return err + } + + t := root.New(p).Delims("[[", "]]").Funcs(funcMap) + _, err = t.Parse(string(fileContents)) + return err + }) + return root, err +} + func renderOptionTag(value, display string, selectedValue string) template.HTML { var selectedParam string if value == selectedValue { @@ -70,12 +98,16 @@ func renderCheckboxTag(name, id string, checked bool) template.HTML { return template.HTML("") } -func RenderPageTemplate(wr io.Writer, name string, data any) { +func RenderPageTemplate(wr io.Writer, name string, data any) bool { + logger.Logger.Helper() + p := name + ".html" err := pageTemplates.ExecuteTemplate(wr, p, data) if err != nil { logger.Logger.Warn("Failed to render page", "name", name, "err", err) } + + return err == nil } func RenderWebAsset(rw http.ResponseWriter, req *http.Request, name string) { diff --git a/web/web_test.go b/web/web_test.go index 5fdf002..890ddf1 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -1,16 +1,20 @@ package web import ( + "embed" "fmt" "github.com/1f349/lavender/utils" "github.com/stretchr/testify/assert" "html/template" "io/fs" + "path" + "slices" + "strings" "testing" ) func TestLoadPages_FindErrors(t *testing.T) { - glob, err := fs.Glob(webBuild, "dist/*/index.html") + glob, err := fs.Glob(webDist, "dist/*/index.html") assert.NoError(t, err) fmt.Println(glob) @@ -21,8 +25,46 @@ func TestLoadPages_FindErrors(t *testing.T) { "emailHide": utils.EmailHide, "renderOptionTag": renderOptionTag, "renderCheckboxTag": renderCheckboxTag, - }).ParseFS(webBuild, fileName) + }).ParseFS(webDist, fileName) assert.NoError(t, err) }) } } + +//go:embed src/pages +var webSrcPages embed.FS + +func TestLoadPage_FindMissing(t *testing.T) { + paths := make([]string, 0) + err := fs.WalkDir(webSrcPages, "src/pages", func(p string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + if strings.HasSuffix(path.Base(p), ".astro") { + p = strings.TrimPrefix(p, "src/pages/") + p = strings.TrimSuffix(p, ".astro") + p += ".html" + paths = append(paths, p) + } + return nil + }) + assert.NoError(t, err) + + slices.Sort(paths) + + err = LoadPages("") + assert.NoError(t, err) + + tmpls := make([]string, 0) + + for _, i := range pageTemplates.Templates() { + if i.Name() == "" { + continue + } + tmpls = append(tmpls, i.Name()) + } + + slices.Sort(tmpls) + + assert.ElementsMatch(t, paths, tmpls) +}