Feature/Auth: implement user authentication #3
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user