147 lines
3.1 KiB
Go
147 lines
3.1 KiB
Go
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,
|
|
}
|
|
}
|
|
|
|
type UserData struct {
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
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) 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 {
|
|
return found, true
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func (hap *HomeAssistantProvider) NewCredData() interface{} {
|
|
return &HAUser{}
|
|
}
|
|
|
|
func (hap *HomeAssistantProvider) FlowSchema() flow.FlowSchema {
|
|
return []flow.FlowSchemaItem{
|
|
{
|
|
Type: "string",
|
|
Name: "username",
|
|
Required: true,
|
|
},
|
|
{
|
|
Type: "string",
|
|
Name: "password",
|
|
Required: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
provider.Register(HomeAssistant, NewHAProvider)
|
|
}
|