lavender/auth/oauth.go

97 lines
2.9 KiB
Go

package auth
import (
"context"
"fmt"
"github.com/1f349/cache"
"github.com/1f349/lavender/database"
"github.com/1f349/lavender/issuer"
"github.com/google/uuid"
"golang.org/x/oauth2"
"net/http"
"time"
)
type flowStateData struct {
loginName string
sso *issuer.WellKnownOIDC
redirect string
}
var _ Provider = (*OAuthLogin)(nil)
type OAuthLogin struct {
DB *database.Queries
BaseUrl string
flow *cache.Cache[string, flowStateData]
}
func (o OAuthLogin) Init() {
o.flow = cache.New[string, flowStateData]()
}
func (o OAuthLogin) Factor() Factor { return FactorFirst }
func (o OAuthLogin) Name() string { return "oauth" }
func (o OAuthLogin) RenderData(ctx context.Context, req *http.Request, user *database.User, data map[string]any) error {
//TODO implement me
panic("implement me")
}
func (o OAuthLogin) AttemptLogin(ctx context.Context, req *http.Request, user *database.User) error {
login, ok := ctx.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)
// 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))
// generate oauth2 config and redirect to authorize URL
oa2conf := login.OAuth2Config
oa2conf.RedirectURL = o.BaseUrl + "/callback"
nextUrl := oa2conf.AuthCodeURL(state, oauth2.SetAuthURLParam("login_name", loginUn))
return RedirectError{Target: nextUrl, Code: http.StatusFound}
}
func (o OAuthLogin) OAuthCallback(rw http.ResponseWriter, req *http.Request, info func(req *http.Request, sso *issuer.WellKnownOIDC, token *oauth2.Token) (UserAuth, error), cookie func(rw http.ResponseWriter, authData UserAuth, loginName string) bool, redirect func(rw http.ResponseWriter, req *http.Request)) {
flowState, ok := o.flow.Get(req.FormValue("state"))
if !ok {
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"))
if err != nil {
http.Error(rw, "Failed to exchange code for token", http.StatusInternalServerError)
return
}
userAuth, err := info(req, flowState.sso, token)
if err != nil {
http.Error(rw, "Failed to update external user info", http.StatusInternalServerError)
return
}
if cookie(rw, userAuth, flowState.loginName) {
http.Error(rw, "Failed to save login cookie", http.StatusInternalServerError)
return
}
if flowState.redirect != "" {
req.Form.Set("redirect", flowState.redirect)
}
redirect(rw, req)
}
type oauthServiceLogin int
func WithWellKnown(ctx context.Context, login *issuer.WellKnownOIDC) context.Context {
return context.WithValue(ctx, oauthServiceLogin(0), login)
}