check / check (push) Waiting to run
The slowest test packages spent their time waiting rather than testing. db.Params gains an optional PasswordCost (zero means bcrypt.DefaultCost), and the db, ircserver and handlers tests hash at bcrypt.MinCost. A test IRC server's relay checks its queue on broker notification instead of polling. The service and handlers tests build config.Config directly and run in parallel; config exports DefaultTrustedProxies and ParseTrustedProxies for them. Fixed sleeps give way to waits for the event. Production defaults are unchanged. What still takes time is CPU work under -race or a timeout a test is about. Model: opus-5-5
113 lines
2.2 KiB
Go
113 lines
2.2 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
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), database.passwordCost,
|
|
)
|
|
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, remoteIP, hostname 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, ip, hostname,
|
|
created_at, last_seen)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
clientUUID, sessionID, tokenHash,
|
|
remoteIP, hostname, 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
|
|
}
|