diff options
author | 2024-09-05 15:28:09 +0100 | |
---|---|---|
committer | 2024-09-05 15:28:09 +0100 | |
commit | b3ef66859af7df58edbe4e59e5ac72dbd3418035 (patch) | |
tree | ba77c6d6a0abbf3d058f21adfe4174e52bd61485 | |
parent | Add jwt-decode package (diff) |
Add JWT decoding utilities to API
-rw-r--r-- | thallium-frontend/src/api/jwt.ts | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/thallium-frontend/src/api/jwt.ts b/thallium-frontend/src/api/jwt.ts new file mode 100644 index 0000000..be58d1d --- /dev/null +++ b/thallium-frontend/src/api/jwt.ts @@ -0,0 +1,101 @@ +import { jwtDecode } from "jwt-decode"; + +import store from "../store"; +import { setRefreshTask, setVoucherToken } from "../slices/authorization"; + +import { refreshToken } from "./login"; + +const EXPIRY_PRE_BUFFER = 120; + +enum JWTType { + VOUCHER, + USER, +} + +interface ThalliumJWT { + sub: string; + iss: JWTType; + exp: number; + nbf: number; + iat: number; +} + +interface RawJWT { + sub: string; + iss: string; + exp: number; + nbf: number; + iat: number; +} + +const expectedKeys = ["sub", "iss", "exp", "nbf", "iat"]; + +const decodeJWT = (jwt: string): ThalliumJWT => { + const decoded = jwtDecode<RawJWT>(jwt); + + const issType = decoded.iss == "thallium:voucher" ? JWTType.VOUCHER : JWTType.USER; + + if (expectedKeys.every((key) => key in Object.keys(decoded))) { + throw new Error(`Invalid JWT format, found keys: ${Object.keys(decoded).join(", ")}`); + } + + return { + sub: decoded.sub, + iss: issType, + exp: decoded.exp, + nbf: decoded.nbf, + iat: decoded.iat, + }; +}; + +const isJWTExpired = (jwt: ThalliumJWT): boolean => { + return jwt.exp < (Date.now() / 1000); +}; + +const secondsToExpiry = (jwt: ThalliumJWT): number => { + return jwt.exp - (Date.now() / 1000); +}; + +const maybeRefreshTask = () => { + const state = store.getState().authorization; + + if (state.refreshTask) { + clearInterval(state.refreshTask); + console.log("Cleared existing refresh task"); + } + + const foundToken = state.voucherToken; + + if (!foundToken) { + console.log("No token found, not setting refresh task"); + return; + } + + const parsed = decodeJWT(foundToken); + + if (isJWTExpired(parsed)) { + console.log("Token is expired, not setting refresh task"); + return; + } + + const task = setTimeout(() => { + console.log("Refreshing token"); + refreshToken(foundToken) + .then((newToken) => { + store.dispatch(setVoucherToken(newToken.jwt)); + + console.log("Refreshed token"); + + maybeRefreshTask(); + }) + .catch((e: unknown) => { + console.error("Failed to refresh token", e); + }); + }, (secondsToExpiry(parsed) - EXPIRY_PRE_BUFFER) * 1000); + + store.dispatch(setRefreshTask(task)); + + console.log("Set refresh task"); +}; + +export { decodeJWT, isJWTExpired, secondsToExpiry, maybeRefreshTask }; |