package auth import ( "errors" "net/http" "sync" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "dynatron.me/x/blasphem/pkg/auth/provider" "dynatron.me/x/blasphem/pkg/storage" // providers _ "dynatron.me/x/blasphem/pkg/auth/provider/hass" _ "dynatron.me/x/blasphem/pkg/auth/provider/trustednets" ) var ( ErrDisabled = errors.New("user disabled") ErrInvalidAuth = errors.New("invalid auth") ErrInvalidHandler = errors.New("no such handler") ErrInvalidIP = errors.New("invalid IP") ErrUserAuthRemote = errors.New("user cannot authenticate remotely") ) type authenticator struct { sync.Mutex store AuthStore flows *AuthFlowManager authCodes authCodeStore providers map[string]provider.AuthProvider } type Authenticator interface { ValidateAccessToken(token AccessToken) *RefreshToken InstallRoutes(e *echo.Echo) } 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("/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 New(s storage.Store) (Authenticator, error) { a := &authenticator{ providers: make(map[string]provider.AuthProvider), } for _, pI := range provider.Providers { nProv, err := pI(s) if err != nil { return nil, err } a.providers[nProv.ProviderType()] = nProv } a.flows = NewAuthFlowManager() a.authCodes.init() var err error a.store, err = a.newAuthStore(s) if err != nil { return nil, err } return a, 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 *LoginFlow, req *http.Request, rm map[string]interface{}) (user provider.ProviderUser, clientID string, err error) { cID, hasCID := rm["client_id"] clientID, cidIsStr := cID.(string) if !hasCID || !cidIsStr || clientID == "" || clientID != string(f.ClientID) { return nil, clientID, ErrInvalidAuth } p := a.Provider(f.Handler.String()) if p == nil { return nil, clientID, ErrInvalidAuth } user, success := p.ValidateCreds(req, rm) if success { log.Info().Interface("user", user.UserData()).Msg("Login success") return user, clientID, nil } return nil, clientID, ErrInvalidAuth }