1
0
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:
2026-04-16 04:05:41 +06:00
parent e8a2217247
commit a3f75300a9
18 changed files with 726 additions and 265 deletions

View File

@ -5,7 +5,7 @@ export type AdminUser = {
email: string;
name: string;
is_admin: boolean;
is_active: boolean;
status: string;
created_at: string;
teams_joined: number;
teams_owned: number;

View File

@ -6,17 +6,26 @@ export type AuthResponse = {
name: string;
};
export type SignupResponse = {
message: string;
};
export type AuthResult = { ok: true; data: AuthResponse } | { ok: false; error: string };
export type SignupResult = { ok: true; data: SignupResponse } | { ok: false; error: string };
export async function apiLogin(email: string, password: string): Promise<AuthResult> {
return authFetch('/api/v1/auth/login', { email, password });
}
export async function apiSignup(email: string, password: string, name: string): Promise<AuthResult> {
export async function apiSignup(email: string, password: string, name: string): Promise<SignupResult> {
return authFetch('/api/v1/auth/signup', { email, password, name });
}
async function authFetch(url: string, body: Record<string, string>): Promise<AuthResult> {
export async function apiActivate(token: string): Promise<AuthResult> {
return authFetch('/api/v1/auth/activate', { token });
}
async function authFetch<T = AuthResponse>(url: string, body: Record<string, string>): Promise<{ ok: true; data: T } | { ok: false; error: string }> {
try {
const res = await fetch(url, {
method: 'POST',
@ -31,7 +40,7 @@ async function authFetch(url: string, body: Record<string, string>): Promise<Aut
return { ok: false, error: message };
}
return { ok: true, data: data as AuthResponse };
return { ok: true, data: data as T };
} catch {
return { ok: false, error: 'Unable to connect to the server' };
}