smc-hub
Version:
CoCalc: Backend webserver component
395 lines (394 loc) • 16.7 kB
JavaScript
"use strict";
/*
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
* License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.get_public_paths = exports.PublicPaths = void 0;
/*
Synchronized table of all public paths.
DESIGN NOTE:
The approach below preloads into RAM info about all public paths.
It should in theory easily scale up to probably 100K+ distinct public paths.
By keeping everything in RAM, the share servers will be faster (basically
never hitting the DB before returning results). And, since we have everything
in memory, we can do a lot of stupid things involving iterating over everything
before writing proper queries.
*/
var events_1 = require("events");
var immutable = __importStar(require("immutable"));
var async_utils_1 = require("smc-util/async-utils");
var misc_1 = require("smc-util/misc");
var PublicPaths = /** @class */ (function (_super) {
__extends(PublicPaths, _super);
function PublicPaths(database) {
var _this = _super.call(this) || this;
_this.is_ready = false;
_this.vhosts = {};
_this.public_paths_in_project = {};
misc_1.bind_methods(_this, ["is_public"]); // it gets passed around
_this.database = database;
_this.do_init();
return _this;
}
PublicPaths.prototype.do_init = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, async_utils_1.retry_until_success({
f: this.init.bind(this),
})];
case 1:
_a.sent();
this.is_ready = true;
this.emit("ready");
return [2 /*return*/];
}
});
});
};
PublicPaths.prototype.get = function (id) {
if (!this.is_ready)
throw Error("not yet ready");
return this.synctable.get(id);
};
PublicPaths.prototype.get_all = function () {
if (!this.is_ready)
throw Error("not yet ready");
return this.synctable.get();
};
PublicPaths.prototype.add_vhost = function (info) {
var e_1, _a;
var t = info.get("vhost");
if (t == null)
return;
try {
for (var _b = __values(t.split(",")), _c = _b.next(); !_c.done; _c = _b.next()) {
var host = _c.value;
this.vhosts[host] = info;
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
};
PublicPaths.prototype.delete_vhost = function (info) {
var e_2, _a;
var t = info.get("vhost");
if (t == null)
return;
try {
for (var _b = __values(t.split(",")), _c = _b.next(); !_c.done; _c = _b.next()) {
var host = _c.value;
delete this.vhosts[host];
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
};
// returns immutable.js public path with given vhost or undefined.
PublicPaths.prototype.get_vhost = function (hostname) {
return this.vhosts[hostname];
};
PublicPaths.prototype.init_public_paths = function () {
var _this = this;
var v = this.public_paths_in_project;
// TWe track in order to deal with deletes.
this.last_public_paths = this.synctable.get();
// TODO: This may be horribly inefficient as the number of public
// paths gets large, and we need to rewrite this.
if (this.last_public_paths == null)
throw Error("bug!");
this.last_public_paths.forEach(function (info) {
var project_id = info.get("project_id");
if (project_id == null)
throw Error("bug");
if (v[project_id] == null) {
v[project_id] = new Set([info.get("path")]);
}
else {
v[project_id].add(info.get("path"));
}
_this.add_vhost(info);
});
};
PublicPaths.prototype.update_public_paths = function (id) {
var info = this.get(id);
if (info == null) {
if (this.last_public_paths == null)
throw Error("bug");
info = this.last_public_paths.get(id);
if (info != null) {
var x = this.public_paths_in_project[info.get("project_id")];
if (x != null) {
x.delete(info.get("path"));
}
this.delete_vhost(info);
}
}
else {
var x = this.public_paths_in_project[info.get("project_id")];
if (x == null) {
x = this.public_paths_in_project[info.get("project_id")] = new Set([
info.get("path"),
]);
}
else {
x.add(info.get("path"));
}
this.add_vhost(info);
}
this.last_public_paths = this.synctable.get(); // TODO: very inefficient?
};
PublicPaths.prototype.get_id = function (project_id, path) {
return this.database.sha1(project_id, path);
};
PublicPaths.prototype.get_info = function (project_id, path) {
var id = this.get_id(project_id, path);
return this.get(id);
};
// Returns the required token, if one is required. A token is required
// if the share is unlisted *and* a token is set for that share.
PublicPaths.prototype.required_token = function (project_id, path) {
var info = this.get_info(project_id, path);
if (info == null)
return; // not even public
if (info.get("unlisted"))
return info.get("token");
return undefined; // not unlisted
};
PublicPaths.prototype.public_path = function (project_id, path) {
var paths = this.public_paths_in_project[project_id];
if (paths == null) {
return undefined;
}
return misc_1.containing_public_path(path, paths);
};
// True if project_id/path is contained in some public path,
// which may or may not be unlisted.
PublicPaths.prototype.is_public = function (project_id, path) {
var paths = this.public_paths_in_project[project_id];
if (paths == null) {
return false;
}
return misc_1.containing_public_path(path, paths) != null;
};
PublicPaths.prototype.is_access_allowed = function (project_id, // the project_id
path, // a path of an actual share.
token // token for access (ignored unless unlisted *and* set in database)
) {
var required_token = this.required_token(project_id, path);
if (required_token) {
return required_token == token;
}
else {
return true;
}
};
PublicPaths.prototype.get_views = function (project_id, path) {
var info = this.get_info(project_id, path);
if (info == null)
return;
return info.get("counter");
};
PublicPaths.prototype.increment_view_counter = function (project_id, // the project_id
path // a path of an actual share.
) {
return __awaiter(this, void 0, void 0, function () {
var id;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
id = this.get_id(project_id, path);
return [4 /*yield*/, async_utils_1.callback2(this.database._query, {
query: "UPDATE public_paths SET counter=coalesce(counter,0)+1 WHERE id='" + id + "'",
})];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
// Immutables List of ids that sorts the public_paths from
// newest (last edited) to oldest. This only includes paths
// that are not unlisted.
PublicPaths.prototype.order = function () {
if (this._order != null) {
return this._order;
}
var v = [];
this.synctable
.get()
.forEach(function (info, id) {
if (!info.get("unlisted")) {
v.push([info.get("last_edited", 0), id]);
}
});
v.sort(function (a, b) { return -misc_1.cmp(a[0], b[0]); });
var ids = v.map(function (x) { return x[1]; });
this._order = immutable.fromJS(ids);
if (this._order == null)
throw Error("bug"); // make typescript happier
return this._order;
};
PublicPaths.prototype.init = function () {
return __awaiter(this, void 0, void 0, function () {
var _a;
var _this = this;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = this;
return [4 /*yield*/, async_utils_1.callback2(this.database.synctable, {
table: "public_paths",
columns: [
"id",
"project_id",
"path",
"description",
"created",
"last_edited",
"last_saved",
"counter",
"vhost",
"auth",
"unlisted",
"license",
"token",
"compute_image",
],
where: "disabled IS NOT TRUE",
})];
case 1:
_a.synctable = _b.sent();
this.synctable.on("change", function (id) {
// TODO: just delete cached for now..., but
// this is horrible and we must make this
// way more efficient!
delete _this._order;
_this.update_public_paths(id);
});
this.init_public_paths();
return [2 /*return*/];
}
});
});
};
return PublicPaths;
}(events_1.EventEmitter));
exports.PublicPaths = PublicPaths;
var the_public_paths = undefined;
function get_public_paths(database) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (the_public_paths != null) {
if (the_public_paths.is_ready) {
return [2 /*return*/, the_public_paths];
}
}
else {
the_public_paths = new PublicPaths(database);
}
return [4 /*yield*/, async_utils_1.once(the_public_paths, "ready")];
case 1:
_a.sent();
return [2 /*return*/, the_public_paths];
}
});
});
}
exports.get_public_paths = get_public_paths;
//# sourceMappingURL=public-paths.js.map