implement account lockout after 3 failed login attempts with 5-minute cooldown period
This commit is contained in:
@@ -33,8 +33,10 @@ type Delivery struct {
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
Username string `db:"username" json:"username"`
|
||||
PasswordHash string `db:"password_hash" json:"password_hash"`
|
||||
CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"`
|
||||
ID pgtype.UUID `db:"id" json:"id"`
|
||||
Username string `db:"username" json:"username"`
|
||||
PasswordHash string `db:"password_hash" json:"password_hash"`
|
||||
CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"`
|
||||
FailedLoginAttempts int32 `db:"failed_login_attempts" json:"failed_login_attempts"`
|
||||
LockedUntil pgtype.Timestamptz `db:"locked_until" json:"locked_until"`
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ type Querier interface {
|
||||
GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error)
|
||||
GetDeliveryCount(ctx context.Context) ([]GetDeliveryCountRow, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||
RecordFailedLogin(ctx context.Context, username string) (RecordFailedLoginRow, error)
|
||||
ResetLoginFailures(ctx context.Context, username string) error
|
||||
UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error
|
||||
UpdateDeliveryStatus(ctx context.Context, arg UpdateDeliveryStatusParams) error
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func (q *Queries) CreateDelivery(ctx context.Context, arg CreateDeliveryParams)
|
||||
const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO users (username, password_hash)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, username, password_hash, created_at
|
||||
RETURNING id, username, password_hash, created_at, failed_login_attempts, locked_until
|
||||
`
|
||||
|
||||
type CreateUserParams struct {
|
||||
@@ -107,6 +107,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.CreatedAt,
|
||||
&i.FailedLoginAttempts,
|
||||
&i.LockedUntil,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -231,7 +233,7 @@ func (q *Queries) GetDeliveryCount(ctx context.Context) ([]GetDeliveryCountRow,
|
||||
}
|
||||
|
||||
const getUserByUsername = `-- name: GetUserByUsername :one
|
||||
SELECT id, username, password_hash, created_at FROM users WHERE username = $1
|
||||
SELECT id, username, password_hash, created_at, failed_login_attempts, locked_until FROM users WHERE username = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
|
||||
@@ -242,10 +244,50 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.CreatedAt,
|
||||
&i.FailedLoginAttempts,
|
||||
&i.LockedUntil,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const recordFailedLogin = `-- name: RecordFailedLogin :one
|
||||
UPDATE users
|
||||
SET failed_login_attempts = CASE
|
||||
WHEN failed_login_attempts + 1 >= 3 THEN 3
|
||||
ELSE failed_login_attempts + 1
|
||||
END,
|
||||
locked_until = CASE
|
||||
WHEN failed_login_attempts + 1 >= 3 THEN NOW() + INTERVAL '5 minutes'
|
||||
ELSE locked_until
|
||||
END
|
||||
WHERE username = $1
|
||||
RETURNING failed_login_attempts, locked_until
|
||||
`
|
||||
|
||||
type RecordFailedLoginRow struct {
|
||||
FailedLoginAttempts int32 `db:"failed_login_attempts" json:"failed_login_attempts"`
|
||||
LockedUntil pgtype.Timestamptz `db:"locked_until" json:"locked_until"`
|
||||
}
|
||||
|
||||
func (q *Queries) RecordFailedLogin(ctx context.Context, username string) (RecordFailedLoginRow, error) {
|
||||
row := q.db.QueryRow(ctx, recordFailedLogin, username)
|
||||
var i RecordFailedLoginRow
|
||||
err := row.Scan(&i.FailedLoginAttempts, &i.LockedUntil)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const resetLoginFailures = `-- name: ResetLoginFailures :exec
|
||||
UPDATE users
|
||||
SET failed_login_attempts = 0,
|
||||
locked_until = NULL
|
||||
WHERE username = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ResetLoginFailures(ctx context.Context, username string) error {
|
||||
_, err := q.db.Exec(ctx, resetLoginFailures, username)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateDelivery = `-- name: UpdateDelivery :exec
|
||||
UPDATE deliveries SET
|
||||
date = $1,
|
||||
|
||||
Reference in New Issue
Block a user