WIP: binding issues

This commit is contained in:
Daniel Ponte 2022-11-11 18:02:52 -05:00
parent 46df74226f
commit a65c6bc394
6 changed files with 144 additions and 34 deletions

View file

@ -4,11 +4,16 @@ all: build
build:
go build -o blas ./cmd/blas/
serve:
go run ./cmd/blas/ serve ${BLAS_ARGS}
# pkg/frontend/frontend/hass_frontend:
frontend:
${FE}/script/setup
${FE}/script/build_frontend
todo:
rg -g '!Makefile' -g '!pkg/frontend/frontend/**' -A3 -C3 'XXX:|TODO:' .
debug:
dlv debug ./cmd/blas/ ${ARGS}

View file

@ -1,6 +1,7 @@
package auth
import (
"crypto/rand"
"encoding/hex"
"errors"
"net/http"
@ -24,6 +25,11 @@ type Authenticator struct {
providers map[string]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"))
@ -69,7 +75,11 @@ type AuthProvider interface { // TODO: this should include stepping
ProviderType() string
ProviderBase() AuthProviderBase
FlowSchema() []FlowSchemaItem
ValidateCreds(reqMap map[string]interface{}) bool
ValidateCreds(reqMap map[string]interface{}) (user ProviderUser, success bool)
}
type ProviderUser interface {
ProviderUsername() string
}
type AuthProviderBase struct {
@ -94,31 +104,32 @@ func (a *Authenticator) ProvidersHandler(c echo.Context) error {
return c.JSON(http.StatusOK, providers)
}
func (a *Authenticator) Check(f *Flow, rm map[string]interface{}) error {
func (a *Authenticator) Check(f *Flow, rm map[string]interface{}) (ProviderUser, error) {
cID, hasCID := rm["client_id"]
if !hasCID || cID != f.request.ClientID {
return ErrInvalidAuth
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 ErrInvalidHandler
return nil, ErrInvalidHandler
}
p := a.Provider(*h)
if p == nil {
return ErrInvalidAuth
return nil, ErrInvalidAuth
}
success := p.ValidateCreds(rm)
user, success := p.ValidateCreds(rm)
if success {
log.Info().Str("user", rm["username"].(string)).Msg("Login success")
return nil
log.Info().Str("user", user.ProviderUsername()).Msg("Login success")
return user, nil
}
}
return ErrInvalidAuth
return nil, ErrInvalidAuth
}
func genUUID() string {
@ -127,3 +138,12 @@ func genUUID() string {
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)
}

View file

@ -14,7 +14,7 @@ import (
type FlowStore map[FlowID]*Flow
type FlowRequest struct {
ClientID string `json:"client_id"`
ClientID ClientID `json:"client_id"`
Handler []*string `json:"handler"`
RedirectURI string `json:"redirect_uri"`
}
@ -147,7 +147,7 @@ func (f *Flow) progress(a *Authenticator, c echo.Context) error {
}
}
}
err = a.Check(f, rm)
user, err := a.Check(f, rm)
switch err {
case nil:
var finishedFlow struct {
@ -158,13 +158,13 @@ func (f *Flow) progress(a *Authenticator, c echo.Context) error {
Type FlowType `json:"type"`
Version int `json:"version"`
}
// TODO: setup the session. delete the flow.
a.flows.Remove(f)
copier.Copy(&finishedFlow, f)
finishedFlow.Type = TypeCreateEntry
finishedFlow.Title = common.AppName
finishedFlow.Version = 1
finishedFlow.Result = a.NewToken(c.Request(), f)
finishedFlow.Result = a.NewToken(c.Request(), user, f)
f.redirect(c)

View file

@ -13,14 +13,16 @@ const (
HAProviderKey = "auth_provider.homeassistant"
)
type User struct {
type HAUser struct {
Password string `json:"password"`
Username string `json:"username"`
}
func (h *HAUser) ProviderUsername() string { return h.Username }
type HomeAssistantProvider struct {
AuthProviderBase `json:"-"`
Users []User `json:"users"`
Users []HAUser `json:"users"`
}
func NewHAProvider(s storage.Store) (*HomeAssistantProvider, error) {
@ -43,16 +45,16 @@ func (hap *HomeAssistantProvider) hashPass(p string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(p), bcrypt.DefaultCost)
}
func (hap *HomeAssistantProvider) ValidateCreds(rm map[string]interface{}) bool {
func (hap *HomeAssistantProvider) ValidateCreds(rm map[string]interface{}) (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 false
return nil, false
}
var found *User
var found *HAUser
const dummyHash = "$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO"
@ -64,22 +66,22 @@ func (hap *HomeAssistantProvider) ValidateCreds(rm map[string]interface{}) bool
if found == nil { // one more compare to thwart timing attacks
bcrypt.CompareHashAndPassword([]byte("foo"), []byte(dummyHash))
return false
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 false
return nil, false
}
err = bcrypt.CompareHashAndPassword(hash, []byte(password))
if err == nil {
return true
return found, true
}
return false
return nil, false
}
func (hap *HomeAssistantProvider) FlowSchema() []FlowSchemaItem {

View file

@ -1,6 +1,7 @@
package auth
import (
"errors"
"net/http"
"time"
@ -14,11 +15,18 @@ type SessionStore struct {
type TokenID string
func (t *TokenID) IsValid() bool {
// TODO: more validation than this
return *t != ""
}
type Token struct { // TODO: jwt bro
ID TokenID
Ctime time.Time
Expires time.Time
Addr string
user ProviderUser `json:"-"`
}
func (ss *SessionStore) init() {
@ -42,20 +50,53 @@ func (ss *SessionStore) register(t *Token) {
ss.s[t.ID] = t
}
func (ss *SessionStore) verify(tr *TokenRequest, r *http.Request) bool {
func (ss *SessionStore) verify(tr *TokenRequest, r *http.Request) (ProviderUser, bool) {
if t, hasToken := ss.s[tr.Code]; hasToken {
// TODO: JWT
if t.Expires.After(time.Now()) {
return true
return t.user, true
} else {
delete(ss.s, t.ID)
}
}
return false
return nil, false
}
type Credential struct {
user ProviderUser
}
func (ss *SessionStore) verifyAndGetCredential(tr *TokenRequest, r *http.Request) *Credential {
user, success := ss.verify(tr, r)
if !success {
return nil
}
return &Credential{user: user}
}
type User struct {
Username string
Active bool
}
func (u *User) allowedToAuth() error {
if !u.Active {
return errors.New("user disabled")
}
return nil
}
func (a *Authenticator) getOrCreateUser(c *Credential) (*User, error) {
panic("not implemented")
return &User{}, nil
}
const defaultExpiration = 2 * time.Hour
func (a *Authenticator) NewToken(r *http.Request, f *Flow) TokenID {
func (a *Authenticator) NewToken(r *http.Request, user ProviderUser, f *Flow) TokenID {
id := TokenID(genUUID())
t := &Token{
@ -63,6 +104,8 @@ func (a *Authenticator) NewToken(r *http.Request, f *Flow) TokenID {
Ctime: time.Now(),
Expires: time.Now().Add(defaultExpiration),
Addr: r.RemoteAddr,
user: user,
}
a.sessions.register(t)
@ -70,22 +113,61 @@ func (a *Authenticator) NewToken(r *http.Request, f *Flow) TokenID {
return id
}
type GrantType string
const (
GTAuthorizationCode GrantType = "authorization_code"
GTRefreshToken GrantType = "refresh_token"
)
type ClientID string
func (c *ClientID) IsValid() bool {
// TODO: || !indieauth.VerifyClientID(rq.ClientID)?
return *c != ""
}
type TokenRequest struct {
ClientID string `query:"client_id"` // TODO: validate this?
ClientID ClientID `query:"client_id"`
Code TokenID `query:"code"`
GrantType string `query:"grant_type"`
GrantType GrantType `query:"grant_type"`
}
func (a *Authenticator) TokenHandler(c echo.Context) error {
var rq TokenRequest
err := c.Bind(&rq)
rq := new(TokenRequest)
err := c.Bind(rq)
if err != nil {
return err
}
if a.sessions.verify(&rq, c.Request()) {
// TODO: success
return c.String(http.StatusOK, "token good I guess")
if *rq == (TokenRequest{}) {
panic("it didn't bind")
}
switch rq.GrantType {
case GTAuthorizationCode:
if !rq.ClientID.IsValid() {
return c.JSON(http.StatusBadRequest, AuthError{Error: "invalid_request", Description: "invalid client ID"})
}
if !rq.Code.IsValid() {
return c.JSON(http.StatusBadRequest, AuthError{Error: "invalid_request", Description: "invalid code"})
}
if cred := a.sessions.verifyAndGetCredential(rq, c.Request()); cred != nil {
// TODO: success
user, err := a.getOrCreateUser(cred)
if err != nil {
return c.JSON(http.StatusUnauthorized, AuthError{Error: "access_denied", Description: "bad user"})
}
if err := user.allowedToAuth(); err != nil {
return c.JSON(http.StatusUnauthorized, AuthError{Error: "access_denied", Description: err.Error()})
}
return c.String(http.StatusOK, "token good I guess")
}
case GTRefreshToken:
return c.String(http.StatusNotImplemented, "not implemented")
}
return c.String(http.StatusUnauthorized, "token bad I guess")

View file

@ -36,6 +36,7 @@ func AliasHandler(toFile string) echo.HandlerFunc {
func init() {
var err error
RootFS, err = fs.Sub(root, "frontend/hass_frontend")
if err != nil {
panic(err)