UNPKG

realm-object-server

Version:

Realm Object Server

921 lines 42.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const discovery_1 = require("./discovery"); const realmUtil_1 = require("./shared/realmUtil"); const uuid = require("uuid"); const _ = require("lodash"); const Logger_1 = require("./shared/Logger"); const Token_1 = require("./shared/Token"); const errors = require("./errors"); const RealmFactory_1 = require("./RealmFactory"); const stats_1 = require("./stats"); const pathMatcher_1 = require("./shared/pathMatcher"); const shared_1 = require("./shared"); const Service_1 = require("./Service"); const Express = require("express"); const http = require("http"); const https = require("https"); const bodyParser = require("body-parser"); const morgan = require("morgan"); const cors = require("cors"); const fs = require("fs-extra"); const path = require("path"); const URI = require("urijs"); const NodeRSA = require("node-rsa"); const semver = require("semver"); const jwt = require("jsonwebtoken"); const crypto = require("crypto"); const httpUtil = require("./shared/httpUtil"); const openFiles = require("./shared/openFiles"); const AuthClient_1 = require("./service-clients/AuthClient"); const PermissionsClient_1 = require("./service-clients/PermissionsClient"); const RealmDirectoryClient_1 = require("./service-clients/RealmDirectoryClient"); const HealthClient_1 = require("./service-clients/HealthClient"); const RealmType_1 = require("./realms/RealmType"); const RealmHoover_1 = require("./shared/RealmHoover"); const SyncClient_1 = require("./service-clients/SyncClient"); class ServerValidationError extends Error { } exports.ServerValidationError = ServerValidationError; class ServerStartError extends Error { } exports.ServerStartError = ServerStartError; class Server { constructor() { this.services = []; this.version = require("../package.json").version; this.numberOfOpenFiles = 0; } get started() { return this._started; } get dataPath() { return this.serverConfig.dataPath; } get discovery() { return this.serverConfig.discovery; } get logger() { return this.serverConfig ? this.serverConfig.logger : null; } get publicKeyPath() { return this.serverConfig.publicKeyPath; } get privateKeyPath() { return this.serverConfig.privateKeyPath; } start(config) { return __awaiter(this, void 0, void 0, function* () { realmUtil_1.Realm.Sync.setUserAgent(`ROS/${this.version}`); realmUtil_1.Realm.Sync.enableSessionMultiplexing(); let mixpanelId; let company; const featureToken = config.featureToken || process.env.SYNC_WORKER_FEATURE_TOKEN; if (featureToken) { try { const decoded = jwt.decode(featureToken); if (decoded && typeof decoded !== "string" && decoded.jti) { mixpanelId = decoded.jti; company = decoded.company; } } catch (_a) { } if (!mixpanelId) { mixpanelId = crypto.createHash("md5").update(featureToken).digest("hex"); } } shared_1.mixpanel.identify(mixpanelId, company || "Unknown Company"); if (this.serverConfig !== undefined) { throw new Error("The server has already been started."); } const validatedConfig = yield this.validateStartConfig(config); this.serverConfig = validatedConfig.config; this.privateKey = validatedConfig.privateKey; this.statsSink = this.serverConfig.statsSink; this.statsStorage = this.serverConfig.statsStorage; realmUtil_1.Realm.Sync.setLogger((level, message) => { const syncLogLevel = Logger_1.SyncLogLevel[level]; const mapping = ["all", "trace", "debug", "detail", "detail", "detail", "detail", "detail", "detail"]; const realLevel = mapping[level]; if (this.serverConfig) { this.logger[realLevel](`sync-client: ${message}`, { level: syncLogLevel }); } }); this.checkNodeVersion(); this.logger.info(`Realm Object Server version ${this.version} is starting`); this.publicKey = yield fs.readFile(this.serverConfig.publicKeyPath, "utf-8"); this.adminToken = Token_1.generateAdminToken({ privateKey: this.privateKey, canSkipRevocationCheck: true, }); const serviceAgent = this.serverConfig.httpsAgentForInternalComponents; this.authClient = new AuthClient_1.AuthClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName, serviceAgent); this.permissionsClient = new PermissionsClient_1.PermissionsClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName, serviceAgent); this.realmDirectoryClient = new RealmDirectoryClient_1.RealmDirectoryClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName, serviceAgent); this.healthClient = new HealthClient_1.HealthClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName, serviceAgent); const realmFactorySSLSettings = { agent: this.serverConfig.httpsAgentForInternalComponents, httpsCertificatePath: this.serverConfig.httpsCertChainPath }; this.realmFactory = new RealmFactory_1.RealmFactory({ discovery: this.discovery, adminToken: this.adminToken, dataPath: this.dataPath, logger: this.logger, realmDirectoryClient: this.realmDirectoryClient, ssl: this.serverConfig.httpsAgentForInternalComponents ? realmFactorySSLSettings : null, realmsEncryptionKey: this.serverConfig.realmsEncryptionKey, }); if (this.serverConfig.vacuumIntervalInSeconds > 0) { this.realmHoover = new RealmHoover_1.RealmHoover({ logger: this.logger, realmFactory: this.realmFactory, intervalSeconds: this.serverConfig.vacuumIntervalInSeconds, getSyncClient: (tags) => new SyncClient_1.SyncClient(this.discovery, this.adminToken, this.serverConfig.authorizationHeaderName, this.serverConfig.serviceAgent, tags), }); } this.tokenValidator = new shared_1.TokenValidator({ logger: this.logger, publicKey: this.publicKey, realmFactory: this.realmFactory, disableRevocation: this.serverConfig.disableTokenRevocation, refreshTokenValidators: this.serverConfig.refreshTokenValidators, }); this.injectLogger(); this.expressApp = Express(); this.expressApp.disable("x-powered-by"); this.expressApp.use((req, res, next) => { req["id"] = uuid.v4(); next(); }); this.expressApp.use(bodyParser.json({ limit: this.serverConfig.jsonBodyLimit || "100kb" })); this.expressApp.use((_, res, next) => { res.header("Server", httpUtil.makeResponseHeaderServer(this.version)); next(); }); this.expressApp.use((req, res, next) => { if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) { const msg = "\nRequest: " + req["id"] + "\n" + httpUtil.stringifyRequest(req); this.httpAccessLogger.trace(msg); } next(); }); if (this.serverConfig.middlewares) { this.expressApp.use(this.serverConfig.middlewares); } this.httpBytesSentCounter = this.statsSink.counter({ name: "ros_http_bytes_sent", help: "Realm Object Server Sent HTTP size counter in bytes", labelNames: [], }); this.httpBytesReceivedCounter = this.statsSink.counter({ name: "ros_http_bytes_received", help: "Realm Object Server Received HTTP size counter in bytes", labelNames: [], }); this.httpServer = http.createServer(this.expressApp); this.httpServer.on("upgrade", this.upgradeHandler.bind(this)); this.httpServer.on("connection", this.connectionHandler.bind(this)); if (this.serverConfig.https) { const key = yield fs.readFile(this.serverConfig.httpsKeyPath); try { new NodeRSA(key); } catch (err) { throw new ServerStartError("Could not create HTTPS server: Private key is malformed."); } const cert = yield fs.readFile(this.serverConfig.httpsCertChainPath, "utf-8"); const regex = /-+BEGIN CERTIFICATE-+([\s\S]+?)-+END CERTIFICATE-+/; if (!regex.test(cert)) { throw new ServerStartError("Could not create HTTPS server: Certificate is malformed."); } try { this.httpsServer = https.createServer({ key, cert }, this.expressApp); } catch (err) { throw new ServerStartError(`Could not create HTTPS server: ${err.stack}`); } this.httpsServer.on("upgrade", this.upgradeHandler.bind(this)); this.httpsServer.on("connection", this.connectionHandler.bind(this)); } this.httpAccessLogger = this.logger.withContext({ service: "http" }); const formatFunction = morgan.compile(":remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms"); const generateLog = (tokens, req, res) => { let result = formatFunction(tokens, req, res); try { const enhancement = this.services.map(s => { return s.enhanceLogFunctionName && s[s.enhanceLogFunctionName]((token) => tokens[token](req, res), req.body); }).filter(l => l).join(" "); if (enhancement) { result += ` ${enhancement}`; } } catch (err) { this.logger.error("Error while logging request metadata", err); } return result; }; this.expressApp.use(morgan(generateLog, { skip: (req, res) => res.statusCode >= 400, stream: { write: (msg) => { this.httpAccessLogger.debug(msg.trim()); } } })); this.expressApp.use(morgan(generateLog, { skip: (req, res) => res.statusCode < 400, stream: { write: (msg) => { this.httpAccessLogger.detail(msg.trim()); } } })); this.expressApp.use(this.authenticationMiddleware.bind(this)); this.expressApp.set("view engine", "html"); this.expressApp.engine("html", require("hbs").__express); yield shared_1.Promisify(this.httpServer.listen.bind(this.httpServer), this.serverConfig.port, this.serverConfig.address); if (this.httpsServer) { yield shared_1.Promisify(this.httpsServer.listen.bind(this.httpsServer), this.serverConfig.httpsPort, this.serverConfig.httpsAddress); } yield this.startServices(); this.logger.detail("All services have started"); yield this.tokenValidator.start().catch((err) => { this.logger.error(`Token Validator failed to start: ${err}`); }); this.expressApp.use(this.errorHandler.bind(this)); this._started = true; const address = this.httpServer.address(); for (const service of this.services) { const serverStartedFunctionName = service.serverStartedFunctionName; if (serverStartedFunctionName) { yield Promise.resolve(service[serverStartedFunctionName](this)); } } const listeningOn = [`http://${address.address}:${address.port}`]; if (this.httpsServer) { const httpsAddress = this.httpsServer.address(); listeningOn.push(`https://${httpsAddress.address}:${httpsAddress.port}`); } this.periodicLoggingOfOpenFiles(); this.logger.info(`Realm Object Server has started and is listening on ${listeningOn.join(" and ")}`); }); } get address() { if (this.httpServer) { const address = this.httpServer.address(); return `${address.address}:${address.port}`; } return undefined; } get secureAddress() { if (this.httpsServer) { const address = this.httpsServer.address(); return `${address.address}:${address.port}`; } return undefined; } shutdown() { return __awaiter(this, void 0, void 0, function* () { if (this.realmHoover) { yield this.realmHoover.stop(); delete this.realmHoover; } yield Promise.all(this.services .filter(service => service.stoppingFunctionName) .map(service => shared_1.timeout(Promise.resolve(service[service.stoppingFunctionName]()), 30000).catch())); if (this.tokenValidator) { this.tokenValidator.stop(); } yield Promise.all(this.services.map((service) => __awaiter(this, void 0, void 0, function* () { service.state = Service_1.ServiceState.Stopping; const serviceName = service.constructor.serviceName; if (serviceName) { this.logger.detail(`Stopping service ${serviceName}`); } yield this.discovery.deregisterService(service); if (service.stopFunctionName) { yield Promise.resolve(service[service.stopFunctionName]()); } service.state = Service_1.ServiceState.Stopped; if (serviceName) { this.logger.detail(`Stopped service ${serviceName}`); } }))); this.services = []; if (this.httpServer) { yield shared_1.Promisify(this.httpServer.close.bind(this.httpServer)).catch((err) => { }); delete this.httpServer; } if (this.httpsServer) { yield shared_1.Promisify(this.httpsServer.close.bind(this.httpsServer)).catch((err) => { }); delete this.httpsServer; } if (this.realmFactory) { yield this.realmFactory.close(); delete this.realmFactory; } if (this.logger) { this.logger.info("Realm Object Server has stopped"); } delete this.tokenValidator; delete this.serverConfig; delete this.expressApp; this._started = false; }); } addServices(...services) { services.forEach(s => this.addService(s)); return this; } addService(service, config) { if (this.started) { throw new Error("Services can only be added before starting the server."); } let instance; if (typeof (service) === "string") { const Services = require("./services"); instance = new Services[service](config); } else { instance = service; } this.services.push(instance); return this; } removeService(service) { if (this.started) { throw new Error("Services can only be removed before starting the server."); } if (typeof service === "string") { const survivingServices = []; for (const instance of this.services) { if (instance.constructor.serviceName !== service) { survivingServices.push(service); } } this.services = survivingServices; } else { const index = this.services.indexOf(service); if (index > -1) { this.services.splice(index, 1); } } return this; } getService(name) { return this.services.find((s) => s.constructor.serviceName === name); } upgradeHandler(req, socket, head) { return __awaiter(this, void 0, void 0, function* () { const requestPath = URI.parse(req.url).path; req["id"] = uuid.v4(); if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) { const msg = "\nRequest: " + req["id"] + "\n" + httpUtil.stringifyRequest(req); this.httpAccessLogger.trace(msg); } let numberOfMatches = 0; for (const service of this.services) { const baseRoute = service.constructor.baseRoute; if (!baseRoute) { continue; } for (const upgradeRoute of (service.upgradeRoutes || [])) { let concatenatedServicePath = baseRoute; if (upgradeRoute.path !== "/") { concatenatedServicePath = URI.joinPaths(baseRoute, upgradeRoute.path).toString(); } const params = pathMatcher_1.pathMatcher(concatenatedServicePath, requestPath); if (params === false) { continue; } req.params = params; numberOfMatches = numberOfMatches + 1; let parameterArguments = service.parameterArguments || []; parameterArguments = parameterArguments.filter(p => p.functionName === upgradeRoute.functionName); const handleError = (err) => { if (socket.destroyed) { return; } const problem = this.processException(err); const headers = { "Server": httpUtil.makeResponseHeaderServer(this.version) }; const response = httpUtil.makeResponse(problem.status, headers); const msg = "HTTP upgrade failed (service did not respond properly) " + JSON.stringify(err) + "\n" + "Request: " + req["id"] + "\n" + httpUtil.stringifyRequest(req) + "\nResponse:\n" + response; this.httpAccessLogger.error(msg); socket.write(response); socket.end(); }; try { if (parameterArguments.length > 0) { throw new Error("Decorator Annotations for @Upgrade are not yet implemented"); } if (service.state !== Service_1.ServiceState.Running) { throw new errors.realm.ServiceUnavailable(); } const promise = service[upgradeRoute.functionName](req, socket, head); yield Promise.resolve(promise); } catch (err) { handleError(err); } } } if (numberOfMatches === 0 && !socket.destroyed) { const headers = { "Server": httpUtil.makeResponseHeaderServer(this.version) }; const response = httpUtil.makeResponse(404, headers); const msg = "HTTP upgrade failed(no service found)\nRequest: " + req["id"] + "\n" + httpUtil.stringifyRequest(req) + "\nResponse:\n" + response; this.httpAccessLogger.error(msg); socket.write(response); socket.end(); } }); } connectionHandler(socket) { let sent = 0; let received = 0; const flushStats = () => { if (this.shouldIgnoreTrafficForSocket(socket)) { clearInterval(timer); } else { this.httpBytesReceivedCounter.inc({}, socket.bytesRead - received); received = socket.bytesRead; this.httpBytesSentCounter.inc({}, socket.bytesWritten - sent); sent = socket.bytesWritten; } }; const timer = setInterval(flushStats, 30 * 1000); socket.on("close", () => { clearInterval(timer); flushStats(); }); } shouldIgnoreTrafficForSocket(socket) { if (socket.ignoreHttpTraffic !== undefined) { return socket.ignoreHttpTraffic; } return this.serverConfig && this.serverConfig.internalIdentities.indexOf(socket.identity) > -1; } injectLogger() { for (const service of this.services) { let functionName = service.unmuteFunctionName; if (functionName && service[functionName]) { service[functionName](this.logger); } functionName = service.unmuteContextualFunctionName; if (functionName && service[functionName]) { const serviceName = service.constructor.serviceName; service[functionName](this.logger.withContext({ service: serviceName })); } } } addServiceEndpoints(service) { const serviceConstructor = service.constructor; const baseRoute = serviceConstructor.baseRoute; const routes = service.routes || []; const serviceStaticRoutes = service.serveStaticRoutes || []; const corsPaths = serviceConstructor.corsPaths || {}; if (serviceStaticRoutes.length === 0 && routes.length === 0) { return; } else if (!baseRoute) { throw new Error(`${service.constructor.name} does not have a base route`); } const router = Express.Router(); const middlewares = serviceConstructor.middlewaresBeforeService; if (middlewares) { router.use(middlewares); } router.use((req, res, next) => { if (service.state !== Service_1.ServiceState.Running) { throw new errors.realm.ServiceUnavailable(); } next(); }); for (const serviceStaticRoute of serviceStaticRoutes) { router.use(serviceStaticRoute.path, Express.static(serviceStaticRoute.staticRoot)); } for (const path in corsPaths) { const options = corsPaths[path] || {}; router.use(path, cors(options)); } for (const route of routes) { const m = service.middlewaresBeforeFunction || []; const middlewaresForThisFunction = m.filter(m => m.functionName === route.functionName); const middlewares = [].concat.apply([], middlewaresForThisFunction.map(x => x.middlewares)); const handler = (req, res, next) => __awaiter(this, void 0, void 0, function* () { const functionName = route.functionName; let parameterArgumentMetaData = service.parameterArguments || []; parameterArgumentMetaData = parameterArgumentMetaData.filter(x => x.functionName === functionName); try { let allowAnonymous = route.allowAnonymous; if (allowAnonymous === undefined) { allowAnonymous = serviceConstructor.allowAnonymous; } if (!allowAnonymous && !req.authToken) { throw new errors.realm.InvalidCredentials({ title: "Missing authorization header" }); } let result; if (parameterArgumentMetaData.length > 0) { const args = []; for (const p of parameterArgumentMetaData) { switch (p.type) { case "body": args[p.argumentIndex] = p.keyPath ? _.get(req.body, p.keyPath) : req.body; break; case "query": args[p.argumentIndex] = p.keyPath ? _.get(req.query, p.keyPath) : req.query; break; case "headers": args[p.argumentIndex] = p.keyPath ? req.headers[p.keyPath.toLowerCase()] : req.headers; break; case "params": args[p.argumentIndex] = p.keyPath ? _.get(req.params, p.keyPath) : req.params; break; case "request": args[p.argumentIndex] = req; break; case "response": args[p.argumentIndex] = res; break; case "next": args[p.argumentIndex] = next; break; } } result = yield Promise.resolve(service[functionName](...args)); if (result) { if (result.toJSON) { res.json(result.toJSON()); } else { res.json(result); } } } else { result = yield Promise.resolve(service[functionName](req, res, next)); if (result) { res.json(result); } } if (Logger_1.SyncLogLevel[this.serverConfig.logLevel] <= Logger_1.SyncLogLevel.trace) { const msg = "\nResponse: " + req["id"] + "\n" + httpUtil.stringifyResponse(res, req.baseUrl, result); this.httpAccessLogger.trace(msg); } } catch (err) { const msg = "HTTP response: " + req["id"] + "\n" + JSON.stringify(err); this.httpAccessLogger.error(msg); next(err); } }); router[route.httpMethod](route.path, middlewares, handler); } this.expressApp.use(baseRoute, router); } startServices() { return __awaiter(this, void 0, void 0, function* () { yield Promise.all(this.services.map(this.startService.bind(this))); }); } startService(service) { return __awaiter(this, void 0, void 0, function* () { service.state = Service_1.ServiceState.Starting; service.stats = this.statsSink; service.statsStorage = this.statsStorage; const serviceName = service.constructor.serviceName; if (serviceName) { this.logger.detail(`Starting service ${serviceName}`); } const startFunctionName = service.startFunctionName; if (startFunctionName && service[startFunctionName]) { yield Promise.resolve(service[startFunctionName](this)); } this.addServiceEndpoints(service); service.state = Service_1.ServiceState.Running; let address; const addressFunctionName = service.addressFunctionName; if (addressFunctionName) { address = service[addressFunctionName](); } else if (this.serverConfig.httpsForInternalComponents) { address = this.httpsServer.address(); } else { address = this.httpServer.address(); } yield this.discovery.registerService(service, address.address, address.port); if (serviceName) { this.logger.detail(`Started service ${serviceName}`); } }); } authenticationMiddleware(req, res, next) { try { const authorization = req.headers[this.serverConfig.authorizationHeaderName] || req.headers["authorization"] || req.headers["x-realm-access-token"]; if (authorization) { const tokenData = authorization; req.authToken = this.tokenValidator.parse(tokenData); req.socket.identity = req.authToken.identity; } next(); } catch (err) { if (!(err instanceof errors.realm.InvalidCredentials) && !(err instanceof errors.realm.AccessDenied)) { err = new errors.realm.InvalidCredentials({ detail: err.message, }); } next(err); } } processException(error) { if (error instanceof errors.JSONError) { this.logger.debug("Returning error response", Object.assign({}, error, { class: error.constructor.name })); this.logger.debug(error.stack); return error; } else { const status = error.status || error.statusCode; if (status) { return new errors.JSONError({ status: status, stack: error.stack, title: error.message, }); } this.logger.error(`Internal server error: ${error.stack}`); return new errors.JSONError({ status: 500, stack: error.stack, title: "Internal Server Error", }); } } errorHandler(error, req, res, next) { if (res.headersSent) { next(error); return; } const jsonError = this.processException(error); res.setHeader("Content-Type", "application/json"); res.status(jsonError.status).json(jsonError.toJSON()); } validateStartConfig(config) { return __awaiter(this, void 0, void 0, function* () { if (!config.discovery) { config.discovery = new discovery_1.SingleProcessDiscovery(); } if (!config.logger) { config.logger = new Logger_1.ConsoleLogger(config.logLevel || "info"); } if (!config.address) { config.address = "0.0.0.0"; } if (!config.httpsAddress) { config.httpsAddress = config.address; } if (config.port === undefined) { config.port = 9080; } if (config.httpsPort === undefined) { config.httpsPort = 9443; } if (config.autoKeyGen === undefined) { config.autoKeyGen = true; } if (config.writeAdminTokenToJson === undefined) { config.writeAdminTokenToJson = true; } if (fs.existsSync(config.dataPath)) { const stat = fs.statSync(config.dataPath); if (!stat.isDirectory()) { throw new ServerValidationError(`The data directory '${config.dataPath}' is not a directory`); } try { fs.accessSync(config.dataPath, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK); } catch (e) { throw new ServerValidationError(`The data directory '${config.dataPath}' does not have 'rwx' permissions`); } } else { try { fs.mkdirSync(config.dataPath, 0o700); } catch (e) { throw new ServerValidationError(`The data directory '${config.dataPath}' cannot be created.`); } } if (!config.privateKeyPath) { config.privateKeyPath = path.join(config.dataPath, "keys", "auth.key"); } let privateKey; try { privateKey = yield fs.readFile(config.privateKeyPath, "utf-8"); } catch (e) { if (config.autoKeyGen) { const key = new NodeRSA({ b: config.autoKeyGenBits || 2048 }); privateKey = key.exportKey("pkcs8-private-pem"); yield fs.mkdirp(path.dirname(config.privateKeyPath)); yield fs.writeFile(config.privateKeyPath, privateKey); } else { throw new ServerValidationError(`The private key '${config.privateKeyPath}' does not exist or is not readable.`); } } const adminJsonPath = path.join(config.dataPath, "keys", "admin.json"); if (config.writeAdminTokenToJson && !fs.pathExistsSync(adminJsonPath)) { const adminToken = Token_1.generateAdminToken({ privateKey: privateKey, }); const stringifiedJSON = JSON.stringify({ "ADMIN_TOKEN": adminToken }, null, 4); yield fs.mkdirp(path.dirname(adminJsonPath)); yield fs.writeFile(adminJsonPath, stringifiedJSON); } if (!config.publicKeyPath) { config.publicKeyPath = path.join(config.dataPath, "keys", "auth.pub"); } try { fs.openSync(config.publicKeyPath, "r"); } catch (e) { if (config.autoKeyGen) { const privateKeyData = yield fs.readFile(config.privateKeyPath); const privateKey = new NodeRSA(privateKeyData); const keyData = privateKey.exportKey("pkcs8-public-pem"); yield fs.mkdirp(path.dirname(config.publicKeyPath)); yield fs.writeFile(config.publicKeyPath, keyData); } else { throw new ServerValidationError(`The public key '${config.publicKeyPath}' does not exist or is not readable.`); } } if (config.https) { if (!config.httpsKeyPath) { throw new ServerValidationError("HTTPS was enabled but a path to the key was not provided."); } try { fs.openSync(config.httpsKeyPath, "r"); } catch (e) { throw new ServerValidationError("The HTTPS key could not be read."); } if (!config.httpsCertChainPath) { throw new ServerValidationError("HTTPS was enabled but a path to the certificate chain was not provided."); } try { fs.openSync(config.httpsCertChainPath, "r"); } catch (e) { throw new ServerValidationError("The HTTPS certificate chain could not be read."); } } if (config.httpsForInternalComponents && !config.https) { throw new ServerValidationError("HTTPS communication between internal components was enabled, but server HTTPS was not."); } if ((config.statsSink && !config.statsStorage) || (config.statsStorage && !config.statsSink)) { throw new ServerValidationError("You must supply statsSink and statsStorage together"); } if (!config.statsSink && !config.statsStorage) { config.statsSink = config.statsStorage = new stats_1.StandaloneStats(); } if (!config.authorizationHeaderName) { config.authorizationHeaderName = "authorization"; } else { config.authorizationHeaderName = config.authorizationHeaderName.toLowerCase(); } if (!config.internalIdentities) { config.internalIdentities = new Array(); } if (config.serviceAgent && !config.httpsAgentForInternalComponents) { config.httpsAgentForInternalComponents = config.serviceAgent; } if (config.httpsForInternalComponents && !config.httpsAgentForInternalComponents) { config.httpsAgentForInternalComponents = new https.Agent(); } if (config.vacuumIntervalInSeconds === undefined) { config.vacuumIntervalInSeconds = -1; } if (config.refreshTokenValidators) { const issuersSet = new Set(); for (const validator of config.refreshTokenValidators) { if (validator.issuer === "realm") { throw new ServerValidationError("'realm' is not a valid value for a CustomTokenValidatorConfig.issuer"); } if (issuersSet.has(validator.issuer)) { throw new ServerValidationError(`Duplicate issuer set in the 'refreshTokenValidators' collection: '${validator.issuer}'`); } issuersSet.add(validator.issuer); } } return { config, privateKey }; }); } periodicLoggingOfOpenFiles() { const server = this; setInterval(function () { return __awaiter(this, void 0, void 0, function* () { const summary = yield openFiles.getSummary(); if (summary.total !== server.numberOfOpenFiles) { server.numberOfOpenFiles = summary.total; if (server.logger) { server.logger.debug(`Open files in Realm Object Server: ${JSON.stringify(summary)}`); } } }); }, 10000); } openRealm(pathOrDefinition, schema) { if (typeof pathOrDefinition === "string") { return this.realmFactory.open({ remotePath: pathOrDefinition, schema: schema }); } return this.realmFactory.open(pathOrDefinition); } applyPermissions(condition, realmPath, accessLevel) { const accessLevels = ["none", "read", "write", "admin"]; if (!realmPath) { throw new errors.realm.MissingParameters("realmPath"); } if (!condition) { throw new errors.realm.MissingParameters("condition"); } if (accessLevels.indexOf(accessLevel) === -1) { throw new errors.realm.InvalidParameters("accessLevel"); } return this.permissionsClient.applyPermissions({ condition, realmPath, accessLevel }); } ensureRealmExists(realmPath, ownerId) { return __awaiter(this, void 0, void 0, function* () { if (!realmPath) { throw new errors.realm.MissingParameters("realmPath"); } if (ownerId) { const homeFolder = new URI(realmPath).segment(0); if (homeFolder !== ownerId && homeFolder !== "~") { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: "realmPath must point to a Realm in the owner's home directory (/ownerId/some-realm)." }); } } else if (realmPath.indexOf("~") !== -1) { throw new errors.realm.InvalidParameters({ name: "realmPath", reason: "realmPath must not contain ~ when ownerId is not specified" }); } const shouldCreate = true; const realmType = RealmType_1.RealmType.full; return this.realmDirectoryClient.findByPath({ realmPath: realmPath, shouldCreate: shouldCreate, realmType: realmType, ownerId: ownerId }); }); } checkNodeVersion() { const minNodeVersion = "8.9.0"; if (semver.lt(process.versions.node, minNodeVersion)) { const logMessage = `Support for Node ${process.versions.node} has been deprecated. Please update at least to Node ${minNodeVersion}.`; this.logger.warn(logMessage); setTimeout(() => this.logger.warn(logMessage), 5000); setTimeout(() => this.logger.warn(logMessage), 15000); } } } exports.Server = Server; //# sourceMappingURL=Server.js.map