import LRU from "lru-cache"; import { callback2 } from "@cocalc/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 generateHash from "@cocalc/server/auth/hash"; import addUserToProject from "@cocalc/server/projects/add-user-to-project"; import isSandboxProject from "@cocalc/server/projects/is-sandbox"; 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 { 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 = generateHash(x[0], x[1], parseInt(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) { // if the project is a sandbox project, we add the user as a collaborator // and grant access. if (await isSandboxProject(project_id)) { dbg("granting sandbox access"); await addUserToProject({ project_id, account_id }); access = true; } } 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; } dbg(`determined that access=${access}`); if (access) { yesCache.set(key, access); } else { noCache.set(key, access); } return access; }