check / check (push) Waiting to run
The server binary carried exported names that only tests call, including raw SQL access and a way to weaken the password hash. Delete testing.go, GetDB and SetBcryptCost from the shipped code, so passwords always hash at bcrypt.DefaultCost. Move GetDB, GetOrCreateChannel, JoinChannel and HasChannelInvite into export_test.go. Delete GetClientHostInfo, whose remaining check GetLatestClientForSession covers. Tests in other packages now build the database with db.New and run raw SQL through dbtest.Exec. Handler tests and the relay tests' aliceWithHTTPClient helper need a password only to log in, so they store one hashed at bcrypt.MinCost. Tests of PASS itself keep the real cost. 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), bcrypt.DefaultCost,
|
|
)
|
|
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
|
|
}
|