mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
71c877a67f
- replace token-based launcher auth with password-based login and sessions - migrate legacy launcher_token values into bcrypt-backed password storage - add one-shot local auto-login bootstrap - update config UI, i18n strings, docs, and auth-related tests
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
package launcherconfig
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const passwordBcryptCost = 12
|
|
|
|
// PasswordStore keeps the dashboard bcrypt hash in launcher-config.json.
|
|
// It is used on platforms where the SQLite-backed dashboard auth store is not
|
|
// available.
|
|
type PasswordStore struct {
|
|
path string
|
|
fallback Config
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewPasswordStore returns a config-backed password store.
|
|
func NewPasswordStore(path string, fallback Config) *PasswordStore {
|
|
return &PasswordStore{
|
|
path: path,
|
|
fallback: fallback,
|
|
}
|
|
}
|
|
|
|
// IsInitialized reports whether a dashboard password hash exists in config.
|
|
func (s *PasswordStore) IsInitialized(ctx context.Context) (bool, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return false, err
|
|
}
|
|
cfg, err := s.load()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return strings.TrimSpace(cfg.DashboardPasswordHash) != "", nil
|
|
}
|
|
|
|
// SetPassword hashes plain with bcrypt and writes it to launcher-config.json.
|
|
func (s *PasswordStore) SetPassword(ctx context.Context, plain string) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
if len([]rune(plain)) == 0 {
|
|
return errors.New("password must not be empty")
|
|
}
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(plain), passwordBcryptCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
cfg, err := Load(s.path, s.fallback)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg.DashboardPasswordHash = string(hash)
|
|
cfg.LegacyLauncherToken = ""
|
|
return Save(s.path, cfg)
|
|
}
|
|
|
|
// VerifyPassword returns true iff plain matches the stored bcrypt hash.
|
|
func (s *PasswordStore) VerifyPassword(ctx context.Context, plain string) (bool, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return false, err
|
|
}
|
|
cfg, err := s.load()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
hash := strings.TrimSpace(cfg.DashboardPasswordHash)
|
|
if hash == "" {
|
|
return false, nil
|
|
}
|
|
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(plain))
|
|
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
|
return false, nil
|
|
}
|
|
return err == nil, err
|
|
}
|
|
|
|
func (s *PasswordStore) load() (Config, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return Load(s.path, s.fallback)
|
|
}
|