2022-09-30 23:54:21 -04:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
2022-11-11 18:02:52 -05:00
|
|
|
"crypto/rand"
|
2022-10-26 19:01:45 -04:00
|
|
|
"encoding/hex"
|
2022-10-25 00:16:29 -04:00
|
|
|
"errors"
|
2022-09-30 23:54:21 -04:00
|
|
|
"net/http"
|
|
|
|
|
2022-10-26 19:01:45 -04:00
|
|
|
"github.com/google/uuid"
|
2022-09-30 23:54:21 -04:00
|
|
|
"github.com/labstack/echo/v4"
|
2022-10-26 18:27:24 -04:00
|
|
|
"github.com/rs/zerolog/log"
|
2022-09-30 23:54:21 -04:00
|
|
|
|
2022-11-12 15:56:17 -05:00
|
|
|
"dynatron.me/x/blasphem/pkg/auth/provider"
|
|
|
|
"dynatron.me/x/blasphem/pkg/auth/provider/hass"
|
2022-09-30 23:54:21 -04:00
|
|
|
"dynatron.me/x/blasphem/pkg/frontend"
|
2022-10-25 00:16:29 -04:00
|
|
|
"dynatron.me/x/blasphem/pkg/storage"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrInvalidAuth = errors.New("invalid auth")
|
|
|
|
ErrInvalidHandler = errors.New("no such handler")
|
2022-09-30 23:54:21 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
type Authenticator struct {
|
2022-11-12 13:34:39 -05:00
|
|
|
store AuthStore
|
2022-10-27 09:51:11 -04:00
|
|
|
flows FlowStore
|
|
|
|
sessions SessionStore
|
2022-11-12 15:56:17 -05:00
|
|
|
providers map[string]provider.AuthProvider
|
2022-09-30 23:54:21 -04:00
|
|
|
}
|
|
|
|
|
2022-11-11 18:02:52 -05:00
|
|
|
type AuthError struct {
|
2022-11-12 12:45:25 -05:00
|
|
|
Error string `json:"error"`
|
2022-11-11 18:02:52 -05:00
|
|
|
Description string `json:"error_description"`
|
|
|
|
}
|
|
|
|
|
2022-10-26 19:01:45 -04:00
|
|
|
func (a *Authenticator) InstallRoutes(e *echo.Echo) {
|
|
|
|
authG := e.Group("/auth")
|
2022-10-29 10:39:06 -04:00
|
|
|
authG.GET("/authorize", frontend.AliasHandler("authorize.html"))
|
2022-10-26 19:01:45 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2022-10-28 18:12:30 -04:00
|
|
|
func (a *Authenticator) InitAuth(s storage.Store) error {
|
2022-10-27 09:51:11 -04:00
|
|
|
a.flows = make(FlowStore)
|
|
|
|
a.sessions.init()
|
2022-11-12 15:56:17 -05:00
|
|
|
hap, err := hass.NewHAProvider(s)
|
2022-10-25 00:16:29 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-10-29 10:39:06 -04:00
|
|
|
// XXX: yuck. use init with a registry or something
|
2022-11-12 15:56:17 -05:00
|
|
|
a.providers = map[string]provider.AuthProvider{
|
2022-10-25 00:16:29 -04:00
|
|
|
hap.ProviderType(): hap,
|
|
|
|
}
|
|
|
|
|
2022-11-12 15:56:17 -05:00
|
|
|
a.store, err = a.newAuthStore(s)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2022-10-25 00:16:29 -04:00
|
|
|
return nil
|
2022-09-30 23:54:21 -04:00
|
|
|
}
|
|
|
|
|
2022-11-12 15:56:17 -05:00
|
|
|
func (a *Authenticator) Provider(name string) provider.AuthProvider {
|
2022-10-28 18:12:30 -04:00
|
|
|
p, ok := a.providers[name]
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2022-09-30 23:54:21 -04:00
|
|
|
var HomeAssistant = "homeassistant"
|
|
|
|
|
|
|
|
// TODO: make this configurable
|
2022-10-25 00:16:29 -04:00
|
|
|
func (a *Authenticator) ProvidersHandler(c echo.Context) error {
|
2022-11-12 15:56:17 -05:00
|
|
|
providers := []provider.AuthProviderBase{
|
2022-10-25 00:16:29 -04:00
|
|
|
a.Provider(HomeAssistant).ProviderBase(),
|
2022-09-30 23:54:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return c.JSON(http.StatusOK, providers)
|
|
|
|
}
|
|
|
|
|
2022-11-12 15:56:17 -05:00
|
|
|
func (a *Authenticator) Check(f *Flow, rm map[string]interface{}) (provider.ProviderUser, error) {
|
2022-10-25 00:16:29 -04:00
|
|
|
cID, hasCID := rm["client_id"]
|
2022-11-11 18:02:52 -05:00
|
|
|
cIDStr, cidIsStr := cID.(string)
|
|
|
|
if !hasCID || !cidIsStr || cIDStr == "" || cIDStr != string(f.request.ClientID) {
|
|
|
|
return nil, ErrInvalidAuth
|
2022-10-25 00:16:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, h := range f.Handler {
|
|
|
|
if h == nil {
|
2022-11-11 18:02:52 -05:00
|
|
|
return nil, ErrInvalidHandler
|
2022-10-25 00:16:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
p := a.Provider(*h)
|
|
|
|
if p == nil {
|
2022-11-11 18:02:52 -05:00
|
|
|
return nil, ErrInvalidAuth
|
2022-10-25 00:16:29 -04:00
|
|
|
}
|
|
|
|
|
2022-11-11 18:02:52 -05:00
|
|
|
user, success := p.ValidateCreds(rm)
|
2022-10-25 00:16:29 -04:00
|
|
|
|
|
|
|
if success {
|
2022-11-12 15:56:17 -05:00
|
|
|
log.Info().Interface("user", user.ProviderUserData()).Msg("Login success")
|
2022-11-11 18:02:52 -05:00
|
|
|
return user, nil
|
2022-10-25 00:16:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-11 18:02:52 -05:00
|
|
|
return nil, ErrInvalidAuth
|
2022-10-25 00:16:29 -04:00
|
|
|
}
|
2022-10-26 19:01:45 -04:00
|
|
|
|
|
|
|
func genUUID() string {
|
|
|
|
// must be addressable
|
|
|
|
u := uuid.New()
|
|
|
|
|
|
|
|
return hex.EncodeToString(u[:])
|
|
|
|
}
|
2022-11-11 18:02:52 -05:00
|
|
|
|
|
|
|
func genHex(l int) string {
|
|
|
|
b := make([]byte, l)
|
|
|
|
if _, err := rand.Read(b); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2022-11-12 12:45:25 -05:00
|
|
|
|
2022-11-11 18:02:52 -05:00
|
|
|
return hex.EncodeToString(b)
|
|
|
|
}
|