@cocalc/hub
Version:
CoCalc: Backend webserver component
111 lines (98 loc) • 3.28 kB
text/typescript
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<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 = 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;
}