aboutsummaryrefslogtreecommitdiffstats
path: root/thallium-frontend/src/api/jwt.ts
blob: be58d1d69ab156ae1237236ab77a3b3ed286289e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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 };