Commit 1bfd1ff6 authored by yolossn's avatar yolossn
Browse files

frontend: Add refresh token logic


This patch adds logic to check if the token
is about to expire and refreshes it.
Signed-off-by: default avataryolossn <nssvlr@gmail.com>
parent 603db56c
Showing with 116 additions and 1 deletion
+116 -1
......@@ -69,6 +69,7 @@
"react-dom": "^17.0.1",
"react-hotkeys-hook": "^3.4.3",
"react-i18next": "^11.12.0",
"react-jwt": "^1.1.6",
"react-markdown": "^8.0.0",
"react-redux": "^7.2.5",
"react-router-dom": "^5.3.0",
......@@ -30780,6 +30781,20 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-jwt": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/react-jwt/-/react-jwt-1.1.6.tgz",
"integrity": "sha512-Yp+FmwkTvYUuO5gu6sFApLXFuMAIAlh1NEfxcB2EGnmS1chKqXXv77vp23IphV1UpSyMtm0wrphUZfDSr8GiSA==",
"engines": {
"node": ">=10"
},
"optionalDependencies": {
"fsevents": "^2.3.2"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
......@@ -61050,6 +61065,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"react-jwt": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/react-jwt/-/react-jwt-1.1.6.tgz",
"integrity": "sha512-Yp+FmwkTvYUuO5gu6sFApLXFuMAIAlh1NEfxcB2EGnmS1chKqXXv77vp23IphV1UpSyMtm0wrphUZfDSr8GiSA==",
"requires": {
"fsevents": "^2.3.2"
}
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
......@@ -64,6 +64,7 @@
"react-dom": "^17.0.1",
"react-hotkeys-hook": "^3.4.3",
"react-i18next": "^11.12.0",
"react-jwt": "^1.1.6",
"react-markdown": "^8.0.0",
"react-redux": "^7.2.5",
"react-router-dom": "^5.3.0",
......
......@@ -8,16 +8,21 @@
*/
import { OpPatch } from 'json-patch';
import { decodeToken } from 'react-jwt';
import helpers from '../../helpers';
import { getToken, logout } from '../auth';
import { getToken, logout, setToken } from '../auth';
import { getCluster } from '../util';
import { KubeMetrics, KubeObjectInterface } from './cluster';
import { KubeToken } from './token';
const BASE_HTTP_URL = helpers.getAppUrl();
const BASE_WS_URL = BASE_HTTP_URL.replace('http', 'ws');
const CLUSTERS_PREFIX = 'clusters';
const JSON_HEADERS = { Accept: 'application/json', 'Content-Type': 'application/json' };
const DEFAULT_TIMEOUT = 2 * 60 * 1000; // ms
const MIN_LIFESPAN_FOR_TOKEN_REFRESH = 10; // sec
var TOKEN_REFRESH_INPROGRESS = false;
export interface RequestParams {
timeout?: number; // ms
......@@ -35,6 +40,77 @@ export interface ClusterRequest {
certificateAuthorityData?: string;
}
//refreshToken checks if the token is about to expire and refreshes it if so.
async function refreshToken(token: string | null) {
if (!token || TOKEN_REFRESH_INPROGRESS) {
return;
}
// decode token
const decodedToken: any = decodeToken(token);
// return if the token doesn't have an expiry time
if (!decodedToken.exp) {
return;
}
// convert expiry seconds to date object
const expiry = decodedToken.exp;
const now = new Date().valueOf();
const expDate = new Date(0);
expDate.setUTCSeconds(expiry);
// calculate time to expiry in minutes
const diff = (expDate.valueOf() - now) / 60000;
// If the token is not about to expire return
if (diff > MIN_LIFESPAN_FOR_TOKEN_REFRESH) {
return;
}
let namespace = '';
let serviceAccountName = '';
if (decodedToken && decodedToken['kubernetes.io']) {
const tokenKubeInfo = decodedToken['kubernetes.io'];
namespace = tokenKubeInfo['namespace'] || '';
const serviceAccount = tokenKubeInfo['serviceaccount'] || {};
serviceAccountName = serviceAccount['name'] || '';
}
const cluster = getCluster();
if (!cluster || namespace === '' || serviceAccountName === '') {
return;
}
console.debug('Refreshing token');
TOKEN_REFRESH_INPROGRESS = true;
let tokenUrl = combinePath(BASE_HTTP_URL, `/${CLUSTERS_PREFIX}/${cluster}`);
tokenUrl = combinePath(
tokenUrl,
`api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}/token`
);
const tokenData = {
kind: 'TokenRequest',
apiVersion: 'authentication.k8s.io/v1',
metadata: { creationTimestamp: null },
spec: { expirationSeconds: 86400 },
};
const response = await fetch(tokenUrl, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, ...JSON_HEADERS },
body: JSON.stringify(tokenData),
});
if (response.status === 201) {
const token: KubeToken = await response.json();
setToken(cluster, token.status.token);
}
// logout if token could not be refreshed
if (response.status === 401) {
console.debug('Token could not be refreshed, logging out');
logout();
}
TOKEN_REFRESH_INPROGRESS = false;
}
export async function request(
path: string,
params: RequestParams = {},
......@@ -55,6 +131,9 @@ export async function request(
let fullPath = path;
if (useCluster && cluster) {
const token = getToken(cluster);
await refreshToken(token);
if (!!token) {
opts.headers.Authorization = `Bearer ${token}`;
}
......
import { KubeObjectInterface } from './cluster';
export interface KubeToken extends KubeObjectInterface {
status: {
token: string;
expirationTimestamp: string;
};
spec: {
audiences: string[];
expirationSeconds: number;
};
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment