package hass import ( "net/http" "encoding/base64" "github.com/rs/zerolog/log" "golang.org/x/crypto/bcrypt" "dynatron.me/x/blasphem/pkg/auth/provider" "dynatron.me/x/blasphem/pkg/flow" "dynatron.me/x/blasphem/pkg/storage" ) const ( HAProviderKey = "auth_provider.homeassistant" ) type HAUser struct { Password string `json:"password"` Username string `json:"username"` provider.AuthProvider `json:"-"` } func (hau *HAUser) UserData() provider.ProviderUser { return &UserData{ // strip secret Username: hau.Username, AuthProvider: hau.AuthProvider, } } func (hau *HAUser) Provider() provider.AuthProvider { return hau.AuthProvider } func (hau *UserData) Provider() provider.AuthProvider { return hau.AuthProvider } type UserData struct { Username string `json:"username"` provider.AuthProvider `json:"-"` } func (ud *UserData) UserData() provider.ProviderUser { return ud } const HomeAssistant = "homeassistant" func (h *HAUser) ProviderUserData() interface{} { return h.UserData() } type HomeAssistantProvider struct { provider.AuthProviderBase `json:"-"` Users []HAUser `json:"users"` userMap map[string]*HAUser } func NewHAProvider(s storage.Store) (provider.AuthProvider, error) { hap := &HomeAssistantProvider{ AuthProviderBase: provider.AuthProviderBase{ Name: "Home Assistant Local", Type: HomeAssistant, }, } err := s.Get(HAProviderKey, hap) if err != nil { return hap, err } hap.userMap = make(map[string]*HAUser) for i, u := range hap.Users { hap.Users[i].AuthProvider = hap hap.userMap[u.Username] = &hap.Users[i] } return hap, nil } func (hap *HomeAssistantProvider) Lookup(pu provider.ProviderUser) provider.ProviderUser { u, has := hap.userMap[pu.(*HAUser).Username] if !has { return nil } return u } func (hap *HomeAssistantProvider) hashPass(p string) ([]byte, error) { return bcrypt.GenerateFromPassword([]byte(p), bcrypt.DefaultCost) } func (hap *HomeAssistantProvider) EqualCreds(c1, c2 provider.ProviderUser) bool { switch c1c := c1.(type) { case *HAUser: switch c2c := c2.(type) { case *HAUser: return c2c.Username == c1c.Username case *UserData: return c2c.Username == c1c.Username } case *UserData: switch c2c := c2.(type) { case *HAUser: return c2c.Username == c1c.Username case *UserData: return c2c.Username == c1c.Username } } return false } func (hap *HomeAssistantProvider) ValidateCreds(r *http.Request, rm map[string]interface{}) (provider.ProviderUser, bool) { usernameE, hasU := rm["username"] passwordE, hasP := rm["password"] username, unStr := usernameE.(string) password, paStr := passwordE.(string) if !hasU || !hasP || !unStr || !paStr || username == "" || password == "" { return nil, false } var found *HAUser const dummyHash = "$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO" for _, v := range hap.Users { // iterate all to thwart timing attacks if v.Username == username { found = &v } } if found == nil { // one more compare to thwart timing attacks bcrypt.CompareHashAndPassword([]byte("foo"), []byte(dummyHash)) return nil, false } var hash []byte hash, err := base64.StdEncoding.DecodeString(found.Password) if err != nil { log.Error().Err(err).Msg("b64 encode fail") return nil, false } err = bcrypt.CompareHashAndPassword(hash, []byte(password)) if err == nil { found.AuthProvider = hap return found, true } return nil, false } func (hap *HomeAssistantProvider) NewCredData() interface{} { return &HAUser{} } func (hap *HomeAssistantProvider) FlowSchema() flow.Schema { return flow.NewSchema(flow.RequiredString("username"), flow.RequiredString("password")) } func init() { provider.Register(HomeAssistant, NewHAProvider) }