package db import ( "context" "errors" "fmt" "time" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) const bcryptCost = bcrypt.DefaultCost var errNoPassword = errors.New( "account has no password set", ) // SetPassword sets a bcrypt-hashed password on a session, // enabling multi-client login via POST /api/v1/login. func (database *Database) SetPassword( ctx context.Context, sessionID int64, password string, ) error { hash, err := bcrypt.GenerateFromPassword( []byte(password), bcryptCost, ) if err != nil { return fmt.Errorf("hash password: %w", err) } _, err = database.conn.ExecContext(ctx, "UPDATE sessions SET password_hash = ? WHERE id = ?", string(hash), sessionID) if err != nil { return fmt.Errorf("set password: %w", err) } return nil } // LoginUser verifies a nick/password and creates a new // client token. func (database *Database) LoginUser( ctx context.Context, nick, password string, ) (int64, int64, string, error) { var ( sessionID int64 passwordHash string ) err := database.conn.QueryRowContext( ctx, `SELECT id, password_hash FROM sessions WHERE nick = ?`, nick, ).Scan(&sessionID, &passwordHash) if err != nil { return 0, 0, "", fmt.Errorf( "get session for login: %w", err, ) } if passwordHash == "" { return 0, 0, "", fmt.Errorf( "login: %w", errNoPassword, ) } err = bcrypt.CompareHashAndPassword( []byte(passwordHash), []byte(password), ) if err != nil { return 0, 0, "", fmt.Errorf( "verify password: %w", err, ) } clientUUID := uuid.New().String() token, err := generateToken() if err != nil { return 0, 0, "", err } now := time.Now() tokenHash := hashToken(token) res, err := database.conn.ExecContext(ctx, `INSERT INTO clients (uuid, session_id, token, created_at, last_seen) VALUES (?, ?, ?, ?, ?)`, clientUUID, sessionID, tokenHash, now, now) if err != nil { return 0, 0, "", fmt.Errorf( "create login client: %w", err, ) } clientID, _ := res.LastInsertId() _, _ = database.conn.ExecContext( ctx, "UPDATE sessions SET last_seen = ? WHERE id = ?", now, sessionID, ) return sessionID, clientID, token, nil }