smc-hub
Version:
CoCalc: Backend webserver component
97 lines (86 loc) • 2.73 kB
text/typescript
import LRU from "lru-cache";
import { callback2 } from "smc-util/async-utils";
import getLogger from "../logger";
import { database } from "../servers/database";
const {
user_has_write_access_to_project,
user_has_read_access_to_project,
} = require("../access");
import { generate_hash } from "../auth";
const winston = getLogger("proxy: has-access");
interface Options {
project_id: string;
remember_me: string;
type: "write" | "read";
isPersonal: boolean;
}
// 5 minute cache: grant "yes" for a while
const yesCache = new LRU({ max: 20000, maxAge: 1000 * 60 * 5 });
// 10 second cache: recheck "no" more frequently
const noCache = new LRU({ max: 20000, maxAge: 1000 * 10 });
export default async function hasAccess(opts: Options): Promise<boolean> {
if (opts.isPersonal) {
// In personal mode, anyone who can access localhost has full
// access to everything, since this is meant to be used on
// single-user personal computer.
return true;
}
const { project_id, remember_me, type } = opts;
const key = project_id + remember_me + type;
for (const cache of [yesCache, noCache]) {
if (cache.has(key)) return !!cache.get(key);
}
// not cached, so we have to determine access.
let access: boolean;
const dbg = (m) => {
winston.debug(`${type} access to ${project_id}: ${m}`);
};
try {
dbg("get remember_me message");
const x = remember_me.split("$");
const hash = generate_hash(x[0], x[1], x[2], x[3]);
const signed_in_mesg = await callback2(database.get_remember_me, {
hash,
cache: true,
});
if (signed_in_mesg == null) {
throw Error("not signed in");
}
const { account_id, email_address } = signed_in_mesg;
dbg(`account_id="${account_id}", email_address="${email_address}"`);
dbg(`now check if user has ${type} access to project`);
if (type === "write") {
access = await callback2(user_has_write_access_to_project, {
database,
project_id,
account_id,
});
if (access) {
// Record that user is going to actively access
// this project. This is important since it resets
// the idle timeout.
database.touch({
account_id,
project_id,
});
}
} else if (type == "read") {
access = await callback2(user_has_read_access_to_project, {
database,
project_id,
account_id,
});
} else {
throw Error(`invalid access type ${type}`);
}
} catch (err) {
dbg(`error trying to determine access; denying for now -- ${err}`);
access = false;
}
if (access) {
yesCache.set(key, access);
} else {
noCache.set(key, access);
}
return access;
}