diff --git a/backend/src/api/auth.ts b/backend/src/api/auth.ts index 0015dcd4..01568742 100644 --- a/backend/src/api/auth.ts +++ b/backend/src/api/auth.ts @@ -86,6 +86,7 @@ export function initAuth(app: express.Express) { const userId = await apiLogins.getUserIdByApiKey(apiKey); if (userId) { + void apiLogins.refreshApiKeyExpiryTime(apiKey); // Refresh expiry time in the background return cb(null, { apiKey, userId }); } @@ -154,6 +155,12 @@ export function initAuth(app: express.Express) { await apiLogins.expireApiKey(req.user!.apiKey); return ok(res); }); + + // API route to refresh the given API token's expiry time + // The actual refreshing happens in the api-token passport strategy above, so we just return 200 OK here + app.post("/auth/refresh", ...apiTokenAuthHandlers(), (req, res) => { + return ok(res); + }); } export function apiTokenAuthHandlers() { diff --git a/backend/src/data/ApiLogins.ts b/backend/src/data/ApiLogins.ts index 22ab35e8..645fcd47 100644 --- a/backend/src/data/ApiLogins.ts +++ b/backend/src/data/ApiLogins.ts @@ -5,7 +5,9 @@ import crypto from "crypto"; import moment from "moment-timezone"; // tslint:disable-next-line:no-submodule-imports import uuidv4 from "uuid/v4"; -import { DBDateFormat } from "../utils"; +import { DAYS, DBDateFormat } from "../utils"; + +const LOGIN_EXPIRY_TIME = 1 * DAYS; export class ApiLogins extends BaseRepository { private apiLogins: Repository; @@ -68,7 +70,7 @@ export class ApiLogins extends BaseRepository { logged_in_at: moment.utc().format(DBDateFormat), expires_at: moment .utc() - .add(1, "day") + .add(LOGIN_EXPIRY_TIME, "ms") .format(DBDateFormat), }); @@ -86,4 +88,19 @@ export class ApiLogins extends BaseRepository { }, ); } + + async refreshApiKeyExpiryTime(apiKey) { + const [loginId, token] = apiKey.split("."); + if (!loginId || !token) return; + + await this.apiLogins.update( + { id: loginId }, + { + expires_at: moment() + .utc() + .add(LOGIN_EXPIRY_TIME, "ms") + .format(DBDateFormat), + }, + ); + } } diff --git a/dashboard/src/store/auth.ts b/dashboard/src/store/auth.ts index db0fd084..84897b32 100644 --- a/dashboard/src/store/auth.ts +++ b/dashboard/src/store/auth.ts @@ -1,6 +1,9 @@ import { get, post } from "../api"; import { ActionTree, Module } from "vuex"; -import { AuthState, RootState } from "./types"; +import { AuthState, IntervalType, RootState } from "./types"; + +// Refresh auth every 15 minutes +const AUTH_REFRESH_INTERVAL = 1000 * 60 * 15; export const AuthStore: Module = { namespaced: true, @@ -8,6 +11,7 @@ export const AuthStore: Module = { state: { apiKey: null, loadedInitialAuth: false, + authRefreshInterval: null, }, actions: { @@ -31,12 +35,34 @@ export const AuthStore: Module = { commit("markInitialAuthLoaded"); }, - setApiKey({ commit, state }, newKey: string) { + setApiKey({ commit, state, dispatch }, newKey: string) { localStorage.setItem("apiKey", newKey); commit("setApiKey", newKey); + + dispatch("startAuthAutoRefresh"); }, - clearApiKey({ commit }) { + async startAuthAutoRefresh({ commit, state, dispatch }) { + // End a previously active auto-refresh, if any + await dispatch("endAuthAutoRefresh"); + + // Start new auto-refresh + const refreshInterval = setInterval(async () => { + await post("auth/refresh", { key: state.apiKey }); + }, AUTH_REFRESH_INTERVAL); + commit("setAuthRefreshInterval", refreshInterval); + }, + + endAuthAutoRefresh({ commit, state }) { + if (state.authRefreshInterval) { + window.clearInterval(state.authRefreshInterval); + } + commit("setAuthRefreshInterval", null); + }, + + async clearApiKey({ commit, dispatch }) { + await dispatch("endAuthAutoRefresh"); + localStorage.removeItem("apiKey"); commit("setApiKey", null); }, @@ -52,6 +78,10 @@ export const AuthStore: Module = { state.apiKey = key; }, + setAuthRefreshInterval(state: AuthState, interval: IntervalType | null) { + state.authRefreshInterval = interval; + }, + markInitialAuthLoaded(state: AuthState) { state.loadedInitialAuth = true; }, diff --git a/dashboard/src/store/types.ts b/dashboard/src/store/types.ts index 3cab32bc..b11193aa 100644 --- a/dashboard/src/store/types.ts +++ b/dashboard/src/store/types.ts @@ -7,9 +7,13 @@ export enum LoadStatus { Done, } +export type TimeoutType = ReturnType; +export type IntervalType = ReturnType; + export interface AuthState { apiKey: string | null; loadedInitialAuth: boolean; + authRefreshInterval: IntervalType | null; } export interface GuildState {