blasphem/pkg/auth/authenticator.go

138 lines
2.9 KiB
Go

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/frontend"
"dynatron.me/x/blasphem/pkg/storage"
// providers
_ "dynatron.me/x/blasphem/pkg/auth/provider/hass"
_ "dynatron.me/x/blasphem/pkg/auth/provider/trustednets"
)
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.providers = make(map[string]provider.AuthProvider)
for _, pI := range provider.Providers {
nProv, err := pI(s)
if err != nil {
return err
}
a.providers[nProv.ProviderType()] = nProv
}
a.flows = make(FlowStore)
a.sessions.init()
var err error
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, req *http.Request, 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(req, 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)
}