Files
jambonz-webapp/src/router/auth.tsx
T
2026-04-15 13:08:17 -04:00

204 lines
5.5 KiB
TypeScript

/**
* Based on https://usehooks.com/useAuth/
*/
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";
import { getMe, postLogin, postLogout } from "src/api";
import { StatusCodes } from "src/api/types";
import {
ROUTE_CREATE_PASSWORD,
ROUTE_INTERNAL_ACCOUNTS,
ROUTE_LOGIN,
ROUTE_REGISTER_SUB_DOMAIN,
} from "./routes";
import {
SESS_OLD_PASSWORD,
SESS_USER_SID,
SESS_FLASH_MSG,
MSG_LOGGED_OUT,
MSG_INCORRECT_CREDS,
MSG_SOMETHING_WRONG,
MSG_SERVER_DOWN,
} from "src/constants";
import type { UserLogin } from "src/api/types";
import { ENABLE_HOSTED_SYSTEM, USER_ACCOUNT } from "src/api/constants";
import type { UserData } from "src/store/types";
import {
clearLocalStorage,
removeLocationBeforeOauth,
removeOauthState,
} from "src/store/localStore";
import { useToast } from "src/components/toast/toast-provider";
interface SignIn {
(username: string, password: string): Promise<UserLogin>;
}
export interface AuthStateContext {
token: string;
signin: SignIn;
signout: () => void;
authorized: boolean;
}
/**
* The auth context for React
*/
export const AuthContext = React.createContext<AuthStateContext>(null!);
/**
* Hook for child components to get the auth object
*/
export const useAuth = (): AuthStateContext => {
return useContext(AuthContext);
};
/**
* The key used to store our token in localStorage
*/
const storageKey = "token";
/**
* Methods to get/set the token from local storage
*/
export const getToken = () => {
return localStorage.getItem(storageKey) || "";
};
export const setToken = (token: string) => {
localStorage.setItem(storageKey, token);
};
/**
* Decode data from a JWT
* https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
*/
export const parseJwt = (token: string) => {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
window
.atob(base64)
.split("")
.map((c) => {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(""),
);
return JSON.parse(jsonPayload);
};
/**
* Provider hook that creates auth object and handles state
*/
export const useProvideAuth = (): AuthStateContext => {
const { toastError } = useToast();
let token = getToken();
let userData: UserData;
const navigate = useNavigate();
const authorized = token ? true : false;
const signin: SignIn = (username, password) => {
return new Promise((resolve, reject) => {
postLogin({ username, password })
.then((response) => {
if (response.status === StatusCodes.OK) {
token = response.json.token;
setToken(token);
userData = parseJwt(token);
if (ENABLE_HOSTED_SYSTEM) {
getMe()
.then(({ json }) => {
if (!json.account?.sip_realm) {
navigate(ROUTE_REGISTER_SUB_DOMAIN);
} else {
navigate(
userData.scope !== USER_ACCOUNT
? ROUTE_INTERNAL_ACCOUNTS
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`,
);
}
})
.catch((error) => {
toastError(error.msg);
});
} else if (response.json.force_change) {
sessionStorage.setItem(SESS_USER_SID, response.json.user_sid);
sessionStorage.setItem(SESS_OLD_PASSWORD, password);
navigate(ROUTE_CREATE_PASSWORD);
} else {
navigate(
userData.scope !== USER_ACCOUNT
? ROUTE_INTERNAL_ACCOUNTS
: `${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`,
);
}
resolve(response.json);
}
})
.catch((error) => {
if (
error.status > StatusCodes.BAD_REQUEST &&
error.status < StatusCodes.INTERNAL_SERVER_ERROR
) {
reject(MSG_INCORRECT_CREDS);
}
if (error.status === StatusCodes.INTERNAL_SERVER_ERROR) {
reject(MSG_SERVER_DOWN);
}
reject(MSG_SOMETHING_WRONG);
})
.finally(() => {
removeOauthState();
removeLocationBeforeOauth();
});
});
};
const signout = () => {
return new Promise((resolve, reject) => {
postLogout()
.then((response) => {
if (response.status === StatusCodes.NO_CONTENT) {
clearLocalStorage();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
window.location.href = ROUTE_LOGIN;
resolve(response.json);
}
})
.catch((error) => {
clearLocalStorage();
sessionStorage.clear();
sessionStorage.setItem(SESS_FLASH_MSG, MSG_LOGGED_OUT);
window.location.href = ROUTE_LOGIN;
if (error) {
reject(error);
}
reject(MSG_SOMETHING_WRONG);
});
});
};
return {
token,
signin,
signout,
authorized,
};
};
/**
* Provider component that wraps your app and makes auth object
*/
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const auth = useProvideAuth();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
};