Web users had no persistent fingerprint, losing codex/achievements/ rankings on reconnect. Now web users enter nickname + password: - New accounts: set password (min 4 chars, bcrypt hashed) - Existing accounts: verify password to log in - On success: deterministic fingerprint SHA256(web:nickname) assigned - SSH users with real key fingerprints skip password entirely New files: store/passwords.go, store/passwords_test.go Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.3 KiB
Go
52 lines
1.3 KiB
Go
package store
|
|
|
|
import (
|
|
bolt "go.etcd.io/bbolt"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
var bucketPasswords = []byte("passwords")
|
|
|
|
// SavePassword stores a bcrypt-hashed password for the given nickname.
|
|
func (d *DB) SavePassword(nickname, password string) error {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return d.db.Update(func(tx *bolt.Tx) error {
|
|
return tx.Bucket(bucketPasswords).Put([]byte(nickname), hash)
|
|
})
|
|
}
|
|
|
|
// CheckPassword verifies a password against the stored bcrypt hash.
|
|
func (d *DB) CheckPassword(nickname, password string) (bool, error) {
|
|
var hash []byte
|
|
err := d.db.View(func(tx *bolt.Tx) error {
|
|
v := tx.Bucket(bucketPasswords).Get([]byte(nickname))
|
|
if v != nil {
|
|
hash = make([]byte, len(v))
|
|
copy(hash, v)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if hash == nil {
|
|
return false, nil
|
|
}
|
|
err = bcrypt.CompareHashAndPassword(hash, []byte(password))
|
|
return err == nil, nil
|
|
}
|
|
|
|
// HasPassword checks whether an account with a password exists for the nickname.
|
|
func (d *DB) HasPassword(nickname string) bool {
|
|
found := false
|
|
d.db.View(func(tx *bolt.Tx) error {
|
|
v := tx.Bucket(bucketPasswords).Get([]byte(nickname))
|
|
found = v != nil
|
|
return nil
|
|
})
|
|
return found
|
|
}
|