package auth import ( "crypto/rand" "encoding/hex" "errors" "net/http" "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "dynatron.me/x/blasphem/pkg/auth/provider" "dynatron.me/x/blasphem/pkg/auth/provider/hass" "dynatron.me/x/blasphem/pkg/frontend" "dynatron.me/x/blasphem/pkg/storage" ) var ( ErrInvalidAuth = errors.New("invalid auth") ErrInvalidHandler = errors.New("no such handler") ) type Authenticator struct { store AuthStore flows FlowStore sessions SessionStore providers map[string]provider.AuthProvider } type AuthError struct { Error string `json:"error"` Description string `json:"error_description"` } func (a *Authenticator) InstallRoutes(e *echo.Echo) { authG := e.Group("/auth") authG.GET("/authorize", frontend.AliasHandler("authorize.html")) authG.GET("/providers", a.ProvidersHandler) authG.POST("/token", a.TokenHandler) authG.POST("/login_flow", a.BeginLoginFlowHandler) loginFlow := authG.Group("/login_flow") // TODO: add IP address affinity middleware loginFlow.POST("/:flow_id", a.LoginFlowHandler) loginFlow.DELETE("/:flow_id", a.LoginFlowDeleteHandler) } func (a *Authenticator) InitAuth(s storage.Store) error { a.flows = make(FlowStore) a.sessions.init() hap, err := hass.NewHAProvider(s) if err != nil { return err } // XXX: yuck. use init with a registry or something a.providers = map[string]provider.AuthProvider{ hap.ProviderType(): hap, } a.store, err = a.newAuthStore(s) if err != nil { return err } return nil } func (a *Authenticator) Provider(name string) provider.AuthProvider { p, ok := a.providers[name] if !ok { return nil } return p } var HomeAssistant = "homeassistant" // TODO: make this configurable func (a *Authenticator) ProvidersHandler(c echo.Context) error { providers := []provider.AuthProviderBase{ a.Provider(HomeAssistant).ProviderBase(), } return c.JSON(http.StatusOK, providers) } func (a *Authenticator) Check(f *Flow, rm map[string]interface{}) (provider.ProviderUser, error) { cID, hasCID := rm["client_id"] cIDStr, cidIsStr := cID.(string) if !hasCID || !cidIsStr || cIDStr == "" || cIDStr != string(f.request.ClientID) { return nil, ErrInvalidAuth } for _, h := range f.Handler { if h == nil { return nil, ErrInvalidHandler } p := a.Provider(*h) if p == nil { return nil, ErrInvalidAuth } user, success := p.ValidateCreds(rm) if success { log.Info().Interface("user", user.ProviderUserData()).Msg("Login success") return user, nil } } return nil, ErrInvalidAuth } func genUUID() string { // must be addressable u := uuid.New() return hex.EncodeToString(u[:]) } func genHex(l int) string { b := make([]byte, l) if _, err := rand.Read(b); err != nil { panic(err) } return hex.EncodeToString(b) }