File size: 2,207 Bytes
013ff92
00a4363
 
 
 
 
 
 
 
 
 
 
013ff92
 
 
 
 
 
 
 
00a4363
 
013ff92
00a4363
013ff92
 
 
00a4363
 
 
 
 
 
 
 
 
1e1198f
 
 
34b0226
00a4363
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { api, BASE, post } from "./client";

export type User = {
  id: string;
  email: string;
  name: string | null;
  avatar_url: string | null;
  auth_provider: "google" | "email";
};

export type LoginResult = { access_token: string; refresh_token: string; user: User; created: boolean };

export type Providers = { google: "redirect" | "popup" | null };

/** Only same site paths, mirroring the backend check, so `next` cannot become an open redirect. */
export function safeNext(path: string | null | undefined): string {
  if (!path || !path.startsWith("/") || path.startsWith("//") || path.includes("\\")) return "/app";
  return path;
}

export const authApi = {
  me: () => api<User>("/api/auth/me"),
  providers: () => api<Providers>("/api/auth/providers"),
  google: (credential: string) => post<LoginResult>("/api/auth/google", { credential }),
  /** Full page navigation target for the redirect flow (server side code exchange). */
  googleStartUrl: (next: string) => `${BASE}/api/auth/google/start?${new URLSearchParams({ next })}`,
  redeemTicket: (ticket: string) => post<LoginResult>("/api/auth/ticket", { ticket }),
  registerStart: (email: string, password: string, name?: string) =>
    post<{ ok: true }>("/api/auth/email/register/start", { email, password, name }),
  registerVerify: (email: string, code: string) =>
    post<LoginResult>("/api/auth/email/register/verify", { email, code }),
  registerResend: (email: string) => post<{ ok: true }>("/api/auth/email/register/resend", { email }),
  login: (email: string, password: string) => post<LoginResult>("/api/auth/email/login", { email, password }),
  forgotStart: (email: string) => post<{ ok: true }>("/api/auth/password/forgot/start", { email }),
  forgotComplete: (email: string, code: string, password: string) =>
    post<LoginResult>("/api/auth/password/forgot/complete", { email, code, password }),
  /** Takes the token explicitly: sign out clears local tokens before revoking on the server. */
  logout: (access: string) =>
    api<{ ok: true }>("/api/auth/logout", { method: "POST", headers: { Authorization: `Bearer ${access}` } }, false),
  deleteAccount: () => post<{ ok: true }>("/api/auth/delete-account"),
};