UNPKG

@cocalc/database

Version:

CoCalc: code for working with our PostgreSQL database

97 lines (83 loc) 2.92 kB
/* * This file is part of CoCalc: Copyright © 2022 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { Pool } from "pg"; import getPool from "../pool"; // this is a general key/value store with expiration. // each key is prefixed with the passport strategy name. // this is used by some passport strategies to share data regarding // a request and a response, or other information. for example, // if there are 3 hubs and one of them generates an ID that's expected to // be returned by the SSO server, then the possibly different hub receiving the // response can only know that ID, if it is somehow stored in this table. const SAVE_QUERY = ` INSERT INTO passport_store (key, value, expire) VALUES ($1, $2, NOW() + make_interval(secs => $3)) ON CONFLICT (key) DO UPDATE SET value = $2, expire = NOW() + make_interval(secs => $3);`; interface RowType { value: string; expire: Date; } // ATTN: do not change the method names nilly-willy: https://github.com/node-saml/passport-saml#cache-provider class PassportCache { private name: string; private cachedMS: number; private pool: Pool; constructor(name: string, cachedMS: number) { if (typeof name !== "string" || name.length === 0) { throw new Error("name must be a non-empty string"); } if (typeof cachedMS !== "number" || cachedMS < 0) { throw new Error("cachedMS must be a positive number"); } this.name = name; this.cachedMS = cachedMS; this.pool = getPool(); } private getKey(key): string { return `${this.name}::${key}`; } // saves the key with the optional value, returns the saved value async saveAsync(key: string, value: string): Promise<void> { const cacheSecs = Math.floor(this.cachedMS / 1000); await this.pool.query(SAVE_QUERY, [this.getKey(key), value, cacheSecs]); } // returns the value if found, null otherwise async getAsync(key: string): Promise<string | null> { const { rows } = await this.pool.query<RowType>( `SELECT value, expire FROM passport_store WHERE key = $1`, [this.getKey(key)] ); if (rows.length === 0) { return null; } const { value, expire } = rows[0]; if (expire < new Date()) { return null; } else { return value; } } // removes the key from the cache, returns the // key removed, null if no key is removed async removeAsync(key: string) { await this.pool.query(`DELETE FROM passport_store WHERE key = $1`, [ this.getKey(key), ]); } } const samlCaches: { [name: string]: PassportCache } = {}; export function getPassportCache( name: string, cachedMS: number ): PassportCache { if (!samlCaches[name]) { samlCaches[name] = new PassportCache(name, cachedMS); } return samlCaches[name]; } export function getOauthCache(name: string) { return getPassportCache(name, 1000 * 60 * 60); }