forked from wrenn/wrenn
Add email activation flow and replace is_active with status column
Email signup now creates inactive users who must activate via a 30-minute email token before signing in. Team creation is deferred to first login after activation, while OAuth users continue to get teams immediately. - Replace boolean is_active with status column (inactive/active/disabled/deleted) - Add POST /v1/auth/activate endpoint with Redis-backed token consumption - Signup returns message instead of JWT, sends activation email - Login differentiates error messages by user status - Add confirm password field to signup form - Add /activate frontend page that auto-logs in on success - Handle inactive user cleanup on re-signup (30-min cooldown) and OAuth collision
This commit is contained in:
@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
|
||||
|
||||
-- Backfill from existing columns.
|
||||
UPDATE users SET status = 'deleted' WHERE deleted_at IS NOT NULL;
|
||||
UPDATE users SET status = 'disabled' WHERE is_active = false AND deleted_at IS NULL;
|
||||
|
||||
ALTER TABLE users DROP COLUMN is_active;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT TRUE;
|
||||
|
||||
UPDATE users SET is_active = false WHERE status IN ('inactive', 'disabled', 'deleted');
|
||||
|
||||
ALTER TABLE users DROP COLUMN status;
|
||||
@ -14,6 +14,11 @@ INSERT INTO users (id, email, name)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING *;
|
||||
|
||||
-- name: InsertUserInactive :one
|
||||
INSERT INTO users (id, email, password_hash, name, status)
|
||||
VALUES ($1, $2, $3, $4, 'inactive')
|
||||
RETURNING *;
|
||||
|
||||
-- name: SetUserAdmin :exec
|
||||
UPDATE users SET is_admin = $2, updated_at = NOW() WHERE id = $1;
|
||||
|
||||
@ -38,6 +43,9 @@ SELECT EXISTS(
|
||||
-- name: CountUsers :one
|
||||
SELECT COUNT(*) FROM users;
|
||||
|
||||
-- name: CountActiveUsers :one
|
||||
SELECT COUNT(*) FROM users WHERE status = 'active';
|
||||
|
||||
-- name: SearchUsersByEmailPrefix :many
|
||||
SELECT id, email FROM users WHERE email LIKE $1 || '%' ORDER BY email LIMIT 10;
|
||||
|
||||
@ -50,7 +58,7 @@ SELECT
|
||||
u.email,
|
||||
u.name,
|
||||
u.is_admin,
|
||||
u.is_active,
|
||||
u.status,
|
||||
u.created_at,
|
||||
(SELECT COUNT(*) FROM users_teams ut WHERE ut.user_id = u.id)::int AS teams_joined,
|
||||
(SELECT COUNT(*) FROM users_teams ut WHERE ut.user_id = u.id AND ut.role = 'owner')::int AS teams_owned
|
||||
@ -64,14 +72,14 @@ SELECT COUNT(*)::int AS total
|
||||
FROM users
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
-- name: SetUserActive :exec
|
||||
UPDATE users SET is_active = $2, updated_at = NOW() WHERE id = $1;
|
||||
-- name: SetUserStatus :exec
|
||||
UPDATE users SET status = $2, updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: UpdateUserPassword :exec
|
||||
UPDATE users SET password_hash = $2, updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: SoftDeleteUser :exec
|
||||
UPDATE users SET deleted_at = NOW(), is_active = false, updated_at = NOW() WHERE id = $1;
|
||||
UPDATE users SET deleted_at = NOW(), status = 'deleted', updated_at = NOW() WHERE id = $1;
|
||||
|
||||
-- name: CountUserOwnedTeamsWithOtherMembers :one
|
||||
SELECT COUNT(DISTINCT ut.team_id)::int
|
||||
@ -85,3 +93,6 @@ WHERE ut.user_id = $1
|
||||
|
||||
-- name: HardDeleteExpiredUsers :exec
|
||||
DELETE FROM users WHERE deleted_at IS NOT NULL AND deleted_at < NOW() - INTERVAL '15 days';
|
||||
|
||||
-- name: HardDeleteUser :exec
|
||||
DELETE FROM users WHERE id = $1;
|
||||
|
||||
Reference in New Issue
Block a user