Implement Phase 2: password auth (Argon2id + bearer tokens)
- Argon2id password hashing and verification with configurable params - Bearer token generation (32-byte random), SHA-256 hashed storage, TTL-based expiry - User creation and authentication helpers - auth_tokens table added to migrations - 6 tests: hash/verify, wrong password, create/auth user, token create/validate, token expiry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
internal/auth/users.go
Normal file
48
internal/auth/users.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateUser creates a new user with a hashed password.
|
||||
func CreateUser(database *sql.DB, username, password string, params Argon2Params) (int64, error) {
|
||||
hash, err := HashPassword(password, params)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
res, err := database.Exec(
|
||||
"INSERT INTO users (username, password_hash, created_at, updated_at) VALUES (?, ?, ?, ?)",
|
||||
username, hash, now, now,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("insert user: %w", err)
|
||||
}
|
||||
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
// AuthenticateUser verifies username/password and returns the user ID.
|
||||
func AuthenticateUser(database *sql.DB, username, password string) (int64, error) {
|
||||
var userID int64
|
||||
var hash string
|
||||
err := database.QueryRow(
|
||||
"SELECT id, password_hash FROM users WHERE username = ?", username,
|
||||
).Scan(&userID, &hash)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
ok, err := VerifyPassword(password, hash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid password")
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user