feat: auth flow
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import type {
|
||||
Human,
|
||||
RequestCodeRequest,
|
||||
SignInRequest,
|
||||
SignInResponse,
|
||||
StartupResponse,
|
||||
} from "./types";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ApiError";
|
||||
}
|
||||
}
|
||||
|
||||
interface ApiClientConfig {
|
||||
baseUrl: string;
|
||||
getToken: () => string | null;
|
||||
onUnauthorized: () => void;
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private config: ApiClientConfig;
|
||||
|
||||
constructor(config: ApiClientConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const token = this.config.getToken();
|
||||
if (token) {
|
||||
headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.config.baseUrl}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
this.config.onUnauthorized();
|
||||
throw new ApiError(401, "Unauthorized");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "Unknown error");
|
||||
throw new ApiError(response.status, text);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
async requestCode(data: RequestCodeRequest): Promise<void> {
|
||||
await this.request<void>("POST", "/auth/request-code", data);
|
||||
}
|
||||
|
||||
async signIn(data: SignInRequest): Promise<SignInResponse> {
|
||||
return this.request<SignInResponse>("POST", "/auth/sign-in", data);
|
||||
}
|
||||
|
||||
async me(): Promise<Human> {
|
||||
return this.request<Human>("GET", "/auth/me");
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
await this.request<void>("POST", "/auth/sign-out");
|
||||
}
|
||||
|
||||
async startup(): Promise<StartupResponse> {
|
||||
return this.request<StartupResponse>("GET", "/startup");
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient({
|
||||
baseUrl: "https://orion.dev.flowy.live",
|
||||
getToken: () => useSessionStore.getState().token,
|
||||
onUnauthorized: () => useSessionStore.getState().clearToken(),
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
export interface Human {
|
||||
id: string;
|
||||
email: string;
|
||||
email_prefix: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface RequestCodeRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface SignInRequest {
|
||||
email: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface SignInResponse {
|
||||
human: Human;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface StartupResponse {
|
||||
networks: unknown[];
|
||||
}
|
||||
Reference in New Issue
Block a user