234 lines
5.3 KiB
Go
234 lines
5.3 KiB
Go
package data
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var ErrAccessDenied = errors.New("access denied")
|
|
|
|
type User struct {
|
|
Username string `json:"username"`
|
|
AuthMethods []any `json:"auth_methods"`
|
|
authMethods []AuthMethod
|
|
}
|
|
|
|
type UserPass struct {
|
|
Username string `json:"pass_username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func (u *UserPass) Name() string {
|
|
return "password"
|
|
}
|
|
|
|
func (u *UserPass) Authenticate() error {
|
|
user, err := GetUser(u.Username)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("username", u.Username).Msg("error getting user")
|
|
FakeCycle()
|
|
return ErrAccessDenied
|
|
}
|
|
for _, method := range user.authMethods {
|
|
if method.Name() == "password" {
|
|
userPass := method.(*UserPass)
|
|
if userPass.Password == u.Password {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return ErrAccessDenied
|
|
}
|
|
|
|
type PubKey struct {
|
|
Username string `json:"pubusername"`
|
|
Pub []byte `json:"pubkey"`
|
|
}
|
|
|
|
func (p PubKey) Name() string {
|
|
return "publickey"
|
|
}
|
|
|
|
func (p PubKey) Authenticate() error {
|
|
u, err := GetUser(p.Username)
|
|
if err != nil {
|
|
log.Warn().Err(err).Str("username", p.Username).Msg("error getting user")
|
|
FakeCycle()
|
|
return ErrAccessDenied
|
|
}
|
|
for _, method := range u.authMethods {
|
|
if method.Name() == "publickey" {
|
|
pubKey := method.(*PubKey)
|
|
if bytes.Equal(pubKey.Pub, p.Pub) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return ErrAccessDenied
|
|
}
|
|
|
|
type AuthMethod interface {
|
|
// Name returns the name of the authentication method.
|
|
Name() string
|
|
// Authenticate authenticates the user.
|
|
Authenticate() error
|
|
}
|
|
|
|
func GetUser(username string) (*User, error) {
|
|
res, err := db.With("users").Get([]byte(username))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var user User
|
|
if err := json.Unmarshal(res, &user); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, method := range user.AuthMethods {
|
|
var up UserPass
|
|
var pk PubKey
|
|
jm, err := json.Marshal(method)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if uperr := json.Unmarshal(jm, &up); uperr == nil {
|
|
if up.Username == "" || up.Password == "" {
|
|
continue
|
|
}
|
|
user.authMethods = append(user.authMethods, &up)
|
|
user.AuthMethods = append(user.AuthMethods, &up)
|
|
}
|
|
if pkerr := json.Unmarshal(jm, &pk); pkerr == nil {
|
|
if pk.Username == "" || len(pk.Pub) == 0 {
|
|
continue
|
|
}
|
|
user.authMethods = append(user.authMethods, &pk)
|
|
user.AuthMethods = append(user.AuthMethods, &pk)
|
|
}
|
|
}
|
|
return &user, nil
|
|
}
|
|
|
|
func NewUser(username string, authMethods ...AuthMethod) error {
|
|
if len(username) == 0 {
|
|
return errors.New("username cannot be empty")
|
|
}
|
|
if len(authMethods) == 0 {
|
|
return errors.New("at least one authentication method must be provided")
|
|
}
|
|
var methods []AuthMethod
|
|
var jsonMethods []any
|
|
for _, method := range authMethods {
|
|
if method == nil {
|
|
return errors.New("authentication method cannot be nil")
|
|
}
|
|
methods = append(methods, method)
|
|
jsonMethods = append(jsonMethods, method)
|
|
}
|
|
user := &User{
|
|
Username: username,
|
|
AuthMethods: jsonMethods,
|
|
}
|
|
b, err := json.Marshal(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
spew.Dump(b)
|
|
return db.With("users").Put([]byte(username), b)
|
|
}
|
|
|
|
func (user *User) AddAuthMethod(method AuthMethod) error {
|
|
if method == nil {
|
|
return errors.New("authentication method cannot be nil")
|
|
}
|
|
user.authMethods = append(user.authMethods, method)
|
|
user.AuthMethods = append(user.AuthMethods, method)
|
|
b, err := json.Marshal(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return db.With("users").Put([]byte(user.Username), b)
|
|
}
|
|
|
|
func DelUser(username string) error {
|
|
return db.With("users").Delete([]byte(username))
|
|
}
|
|
|
|
func (user *User) DelPubKey(pubkey []byte) error {
|
|
var found = false
|
|
var jsonMethods []any
|
|
var methods []AuthMethod
|
|
for _, method := range user.authMethods {
|
|
if method.Name() == "publickey" {
|
|
pubKey := method.(*PubKey)
|
|
if bytes.Equal(pubKey.Pub, pubkey) {
|
|
found = true
|
|
continue
|
|
}
|
|
}
|
|
methods = append(methods, method)
|
|
jsonMethods = append(jsonMethods, method)
|
|
}
|
|
if !found {
|
|
return errors.New("public key not found")
|
|
}
|
|
user.AuthMethods = jsonMethods
|
|
user.authMethods = methods
|
|
if b, err := json.Marshal(user); err == nil {
|
|
return db.With("users").Put([]byte(user.Username), b)
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
|
|
func (user *User) ChangePassword(newPassword string) error {
|
|
var ponce = &sync.Once{}
|
|
var methods []any
|
|
var authMethods []AuthMethod
|
|
for _, method := range user.authMethods {
|
|
if method.Name() == "password" {
|
|
ponce.Do(func() {
|
|
method.(*UserPass).Password = newPassword
|
|
})
|
|
}
|
|
methods = append(methods, method)
|
|
authMethods = append(authMethods, method)
|
|
}
|
|
user.AuthMethods = methods
|
|
user.authMethods = authMethods
|
|
b, err := json.Marshal(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return db.With("users").Put([]byte(user.Username), b)
|
|
}
|
|
|
|
func provisionFakeUser() *User {
|
|
err := NewUser("0", &UserPass{Password: "0"})
|
|
if err != nil {
|
|
log.Panic().Err(err).Msg("error creating fake user")
|
|
}
|
|
var user *User
|
|
user, err = GetUser("0")
|
|
if err != nil {
|
|
log.Panic().Err(err).Msg("error getting user")
|
|
}
|
|
return user
|
|
}
|
|
|
|
// FakeCycle chooses the first known user and cycles through all their auth methods to avoid time based user enumeration.
|
|
func FakeCycle() {
|
|
user, err := GetUser("0")
|
|
if err != nil {
|
|
user = provisionFakeUser()
|
|
}
|
|
|
|
for _, method := range user.authMethods {
|
|
_ = method.Authenticate()
|
|
}
|
|
}
|