feat: authentication integration
This commit is contained in:
@ -1,25 +1,25 @@
|
||||
import { handleApiError } from ".";
|
||||
import { handleApiError, axios } from ".";
|
||||
|
||||
export interface CodeResponse {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export const codeApi = async (accessToken: string, nonce: string) => {
|
||||
const response = await fetch("/api/v1/oauth/code", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nonce,
|
||||
}),
|
||||
});
|
||||
const response = await axios.post(
|
||||
"/api/v1/oauth/code",
|
||||
{ nonce },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200 && response.status !== 201)
|
||||
throw await handleApiError(response);
|
||||
|
||||
const data: CodeResponse = await response.json();
|
||||
const data: CodeResponse = response.data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
@ -1,20 +1,74 @@
|
||||
export const handleApiError = async (response: Response) => {
|
||||
try {
|
||||
const json = await response.json();
|
||||
console.log({ json });
|
||||
const text = json.error ?? "unexpected error happpened";
|
||||
return new Error(text[0].toUpperCase() + text.slice(1));
|
||||
} catch (err) {
|
||||
try {
|
||||
console.log(err);
|
||||
const text = await response.text();
|
||||
if (text.length > 0) {
|
||||
return new Error(text[0].toUpperCase() + text.slice(1));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
import { deleteAccount, updateAccountTokens } from "@/repository/account";
|
||||
import { useDbStore } from "@/store/db";
|
||||
import { useAuth } from "@/store/auth";
|
||||
import Axios, { type AxiosResponse } from "axios";
|
||||
import { refreshTokenApi } from "./refresh";
|
||||
|
||||
return new Error("Unexpected error happened");
|
||||
export const axios = Axios.create({
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(request) => {
|
||||
const account = useAuth.getState().activeAccount;
|
||||
if (account?.access) {
|
||||
request.headers["Authorization"] = `Bearer ${account.access}`;
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (error.response.status === 401 && !originalRequest._retry) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
const account = useAuth.getState().activeAccount;
|
||||
if (!account?.refresh) {
|
||||
return Promise.reject(new Error("Unauthorized. No refresh token"));
|
||||
}
|
||||
|
||||
const db = useDbStore.getState().db;
|
||||
if (!db) {
|
||||
return Promise.reject(new Error("No database connection"));
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await refreshTokenApi(account.refresh);
|
||||
|
||||
updateAccountTokens(db, {
|
||||
accountId: account.accountId,
|
||||
access: response.access,
|
||||
refresh: response.refresh,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("token refresh failed:", err);
|
||||
await deleteAccount(db, account.accountId);
|
||||
const loadAccounts = useAuth.getState().loadAccounts;
|
||||
loadAccounts?.();
|
||||
const requireSignIn = useAuth.getState().requireSignIn;
|
||||
requireSignIn?.();
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export const handleApiError = async (response: AxiosResponse) => {
|
||||
const text =
|
||||
response.data?.error ||
|
||||
response.data?.toString?.() ||
|
||||
"unexpected error happened";
|
||||
return new Error(text[0].toUpperCase() + text.slice(1));
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import axios from "axios";
|
||||
import { handleApiError } from ".";
|
||||
|
||||
export interface LoginRequest {
|
||||
@ -15,21 +16,15 @@ export interface LoginResponse {
|
||||
}
|
||||
|
||||
export const loginApi = async (req: LoginRequest) => {
|
||||
const response = await fetch("/api/v1/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
const response = await axios.post("/api/v1/auth/login", {
|
||||
email: req.email,
|
||||
password: req.password,
|
||||
});
|
||||
|
||||
if (response.status !== 200 && response.status !== 201)
|
||||
throw await handleApiError(response);
|
||||
|
||||
const data: LoginResponse = await response.json();
|
||||
const data: LoginResponse = response.data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
@ -1,29 +1,15 @@
|
||||
import { handleApiError } from ".";
|
||||
import type { UserProfile } from "@/types";
|
||||
import { axios, handleApiError } from ".";
|
||||
|
||||
export interface FetchProfileResponse {
|
||||
full_name: string;
|
||||
email: string;
|
||||
phone_number: string;
|
||||
isAdmin: boolean;
|
||||
last_login: string;
|
||||
profile_picture: string | null;
|
||||
updated_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
export type FetchProfileResponse = UserProfile;
|
||||
|
||||
export const fetchProfileApi = async (accessToken: string) => {
|
||||
const response = await fetch("/api/v1/oauth/code", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
export const fetchProfileApi = async () => {
|
||||
const response = await axios.get("/api/v1/auth/profile");
|
||||
|
||||
if (response.status !== 200 && response.status !== 201)
|
||||
throw await handleApiError(response);
|
||||
|
||||
const data: FetchProfileResponse = await response.json();
|
||||
const data: FetchProfileResponse = response.data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
27
web/src/api/refresh.ts
Normal file
27
web/src/api/refresh.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import axios from "axios";
|
||||
import { handleApiError } from ".";
|
||||
|
||||
export interface RefreshTokenResponse {
|
||||
access: string;
|
||||
refresh: string;
|
||||
}
|
||||
|
||||
export const refreshTokenApi = async (refreshToken: string) => {
|
||||
const response = await axios.post(
|
||||
"/api/v1/auth/refresh",
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${refreshToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status !== 200 && response.status !== 201)
|
||||
throw await handleApiError(response);
|
||||
|
||||
const data: RefreshTokenResponse = response.data;
|
||||
|
||||
return data;
|
||||
};
|
Reference in New Issue
Block a user