CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/database/postgres/passport-store.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { Pool } from "pg";
7
import getPool from "../pool";
8
9
// this is a general key/value store with expiration.
10
// each key is prefixed with the passport strategy name.
11
// this is used by some passport strategies to share data regarding
12
// a request and a response, or other information. for example,
13
// if there are 3 hubs and one of them generates an ID that's expected to
14
// be returned by the SSO server, then the possibly different hub receiving the
15
// response can only know that ID, if it is somehow stored in this table.
16
17
const SAVE_QUERY = `
18
INSERT INTO passport_store (key, value, expire)
19
VALUES ($1, $2, NOW() + make_interval(secs => $3))
20
ON CONFLICT (key)
21
DO UPDATE SET value = $2, expire = NOW() + make_interval(secs => $3);`;
22
23
interface RowType {
24
value: string;
25
expire: Date;
26
}
27
28
// ATTN: do not change the method names nilly-willy: https://github.com/node-saml/passport-saml#cache-provider
29
class PassportCache {
30
private name: string;
31
private cachedMS: number;
32
private pool: Pool;
33
34
constructor(name: string, cachedMS: number) {
35
if (typeof name !== "string" || name.length === 0) {
36
throw new Error("name must be a non-empty string");
37
}
38
if (typeof cachedMS !== "number" || cachedMS < 0) {
39
throw new Error("cachedMS must be a positive number");
40
}
41
this.name = name;
42
this.cachedMS = cachedMS;
43
this.pool = getPool();
44
}
45
46
private getKey(key): string {
47
return `${this.name}::${key}`;
48
}
49
50
// saves the key with the optional value, returns the saved value
51
async saveAsync(key: string, value: string): Promise<void> {
52
const cacheSecs = Math.floor(this.cachedMS / 1000);
53
await this.pool.query(SAVE_QUERY, [this.getKey(key), value, cacheSecs]);
54
}
55
56
// returns the value if found, null otherwise
57
async getAsync(key: string): Promise<string | null> {
58
const { rows } = await this.pool.query<RowType>(
59
`SELECT value, expire FROM passport_store WHERE key = $1`,
60
[this.getKey(key)]
61
);
62
if (rows.length === 0) {
63
return null;
64
}
65
const { value, expire } = rows[0];
66
if (expire < new Date()) {
67
return null;
68
} else {
69
return value;
70
}
71
}
72
73
// removes the key from the cache, returns the
74
// key removed, null if no key is removed
75
async removeAsync(key: string) {
76
await this.pool.query(`DELETE FROM passport_store WHERE key = $1`, [
77
this.getKey(key),
78
]);
79
}
80
}
81
82
const samlCaches: { [name: string]: PassportCache } = {};
83
84
export function getPassportCache(
85
name: string,
86
cachedMS: number
87
): PassportCache {
88
if (!samlCaches[name]) {
89
samlCaches[name] = new PassportCache(name, cachedMS);
90
}
91
return samlCaches[name];
92
}
93
94
export function getOauthCache(name: string) {
95
return getPassportCache(name, 1000 * 60 * 60);
96
}
97
98