Feature/Auth: implement user authentication #3

Merged
homeburger merged 48 commits from feature/auth into main 2026-03-22 20:52:22 -05:00
2 changed files with 74 additions and 11 deletions
Showing only changes of commit 04e2b6acc3 - Show all commits

View File

@@ -2,7 +2,7 @@
// service to interact with the api/auth endpoints // service to interact with the api/auth endpoints
// handles user registration, user logins, tokens, password reset, etc. // handles user registration, user logins, tokens, password reset, etc.
import api from "./axios.ts" import { api, authStorage } from "./axios.ts"
import type { User, RegisterDto, LoginDto } from "../models/User.ts"; import type { User, RegisterDto, LoginDto } from "../models/User.ts";
const API_URL: string = "/auth"; const API_URL: string = "/auth";
@@ -10,9 +10,8 @@ const API_URL: string = "/auth";
export const register = async (user: RegisterDto) => { export const register = async (user: RegisterDto) => {
try { try {
console.log(user);
// TODO: if valid const response = await api.post(`${API_URL}/register`, user);
return true; return true;
@@ -28,9 +27,7 @@ export const login = async (user: LoginDto ) => {
try { try {
const response = await api.post(`${API_URL}/login`, user); const response = await api.post(`${API_URL}/login`, user);
const token = response.data.token; localStorage.setTokens(response.data);
localStorage.setItem("token", token);
return true; return true;
@@ -41,9 +38,9 @@ export const login = async (user: LoginDto ) => {
} }
export const logout = () => { export const logout = () => {
localStorage.removeItem("token"); authStorage.clear();
} }
export const getToken = () => { export const getToken = () => {
return localStorage.getItem("token"); authStorage.getAccessToken();
} }

View File

@@ -5,13 +5,41 @@
import axios from "axios"; import axios from "axios";
const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api" const baseUrl: string = import.meta.env.DEV ? import.meta.env.VITE_DEV_API_URL : "https://app.vxbard.net/api"
const api = axios.create({ export const api = axios.create({
baseURL: baseUrl baseURL: baseUrl
}); });
api.interceptors.request.use(config => { type FailedRequest = { resolve: (token: string) => void, reject: (error: unknown) => void}
let isRefreshing: boolean = false;
let failedQueue: FailedRequest[] = [];
const token = localStorage.getItem("token"); export const authStorage = {
getAccessToken: () => localStorage.getItem("accessToken"),
getRefreshToken: () => localStorage.getItem("refreshToken"),
setTokens: ({ accessToken, refreshToken } : { accessToken: string, refreshToken: string }) => {
localStorage.setItem("accessToken", accessToken)
localStorage.setItem("refreshToken", refreshToken)
},
clear: () => {
localStorage.removeItem("accessToken")
localStorage.removeItem("refreshToken")
}
}
const processQueue = (error: unknown, token: string | null = null): void => {
failedQueue.forEach(prom => {
if (error) prom.reject(error);
else prom.resolve(token as string);
})
failedQueue = [];
}
// intercept on each request
api.interceptors.request.use(config => { // add access token to request headers
const token = localStorage.getAccessToken();
if (token) { if (token) {
config.headers.Authorization = `Bearer ${token}`; config.headers.Authorization = `Bearer ${token}`;
@@ -21,4 +49,42 @@ api.interceptors.request.use(config => {
}); });
// intercept on each response
api.interceptors.response.use(response => response, async error => { // mainly for authentication refreshTokens
const originalRequest = error.config;
// if un authorized then refresh the token
if(error.response?.status === 401 && !originalRequest._retry) {
if(isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
}).then(token => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return api(originalRequest);
}).catch(err => Promise.reject(err));
}
originalRequest._retry = true;
isRefreshing = true;
const refreshToken = authStorage.getRefreshToken();
try {
// request refresh endpoint get back a new accessToken
const res = await axios.post(`${baseUrl}/auth/refresh`, { refreshToken });
const { accessToken, refreshToken: newRefresh } = res.data;
authStorage.setTokens({ accessToken, refreshToken: newRefresh });
processQueue(null, accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return api(originalRequest);
} catch (err) {
processQueue(err, null);
authStorage.clear()
window.location.href = "/login";
return Promise.reject(err);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
})
export default api; export default api;