UNPKG

realm-object-server

Version:

Realm Object Server

391 lines 19.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; 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 chai_1 = require("chai"); const sinon = require("sinon"); const Server_1 = require("./Server"); const decorators_1 = require("./decorators"); const Token_1 = require("./shared/Token"); const Logger_1 = require("./shared/Logger"); const tmp = require("tmp"); const fs = require("fs-extra"); const path = require("path"); const superagent = require("superagent"); const TestServer_1 = require("./TestServer"); const RealmFactory_1 = require("./RealmFactory"); let StartService = class StartService { start() { return __awaiter(this, void 0, void 0, function* () { this.started = true; }); } getAuthDetails(req) { const ret = { authToken: req.authToken, }; return ret; } testPost(t, req) { return req.body; } rejectingWebsocketHandler(req, socket, head) { return __awaiter(this, void 0, void 0, function* () { throw new Error("you were only supposed to blow the bloody doors off!"); }); } throwingWebsocketHandler(req, socket, head) { throw new Error("you were only supposed to blow the bloody doors off!"); } serverStarted(server) { this.server = server; } }; __decorate([ decorators_1.Start(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], StartService.prototype, "start", null); __decorate([ decorators_1.Get("/auth_details"), __param(0, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", void 0) ], StartService.prototype, "getAuthDetails", null); __decorate([ decorators_1.Post("/test_post"), __param(0, decorators_1.Body("some")), __param(1, decorators_1.Request()), __metadata("design:type", Function), __metadata("design:paramtypes", [String, Object]), __metadata("design:returntype", void 0) ], StartService.prototype, "testPost", null); __decorate([ decorators_1.Upgrade("/reject"), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, Object]), __metadata("design:returntype", Promise) ], StartService.prototype, "rejectingWebsocketHandler", null); __decorate([ decorators_1.Upgrade("/throw"), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, Object]), __metadata("design:returntype", void 0) ], StartService.prototype, "throwingWebsocketHandler", null); __decorate([ decorators_1.ServerStarted(), __metadata("design:type", Function), __metadata("design:paramtypes", [Server_1.Server]), __metadata("design:returntype", void 0) ], StartService.prototype, "serverStarted", null); StartService = __decorate([ decorators_1.BaseRoute("/"), decorators_1.ServiceName("start") ], StartService); describe("Server", () => { let server; let dataPath; let logger; beforeEach(() => { dataPath = tmp.dirSync().name; logger = new Logger_1.MuteLogger(); server = new Server_1.Server(); RealmFactory_1.RealmFactory["sessionStopPolicy"] = "immediately"; }); function startServer(args = {}) { return __awaiter(this, void 0, void 0, function* () { yield TestServer_1.clearTestState(); yield fs.remove("./realm-object-server"); yield fs.mkdirs("./realm-object-server/io.realm.object-server-utility/metadata/"); yield server.start(Object.assign({ dataPath, logger, autoKeyGen: false, disableTokenRevocation: true, privateKeyPath: path.join(__dirname, "..", "fixtures", "keys", "auth.key"), publicKeyPath: path.join(__dirname, "..", "fixtures", "keys", "auth.pub") }, args)); }); } afterEach(() => __awaiter(this, void 0, void 0, function* () { yield Realm.Sync.removeAllListeners(); yield server.shutdown().catch((err) => { }); })); describe("start", () => { it("should support autokeygen", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(startServer({ autoKeyGen: true, autoKeyGenBits: 512, privateKeyPath: null, publicKeyPath: null, })); chai_1.assert.isTrue(fs.existsSync(path.resolve(dataPath, "keys", "auth.key"))); chai_1.assert.isTrue(fs.existsSync(path.resolve(dataPath, "keys", "auth.pub"))); })); it("should support skipping autokeygen", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ autoKeyGen: false, privateKeyPath: null, publicKeyPath: null })); chai_1.assert.isFalse(fs.existsSync(path.resolve(dataPath, "keys", "auth.pub"))); chai_1.assert.isFalse(fs.existsSync(path.resolve(dataPath, "keys", "auth.key"))); })); it("should start by default with no services", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.isEmpty(server.services); })); describe("with HTTPS", () => { describe("when started without httpsKeyPath", () => { it("should not start", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ https: true, }), "HTTPS was enabled but a path to the key was not provided."); })); }); describe("when started without httpsCertChainPath", () => { it("should not start", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ https: true, httpsKeyPath: path.join(__dirname, "..", "fixtures", "https.key"), }), "HTTPS was enabled but a path to the certificate chain was not provided."); })); }); describe("when started with invalid httpsCertChain", () => { it("should not start", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ https: true, httpsKeyPath: path.join(__dirname, "..", "fixtures", "https.key"), httpsCertChainPath: path.join(__dirname, "..", "fixtures", "https-malformed.crt"), }), "Could not create HTTPS server: Certificate is malformed."); })); }); describe("when started with invalid httpsKey", () => { it("should not start", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ https: true, httpsKeyPath: path.join(__dirname, "..", "fixtures", "https-malformed.key"), httpsCertChainPath: path.join(__dirname, "..", "fixtures", "https.crt"), }), "Could not create HTTPS server: Private key is malformed."); })); }); describe("when started with all required options", () => { it("should start", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(startServer({ https: true, httpsKeyPath: path.join(__dirname, "..", "fixtures", "https.key"), httpsCertChainPath: path.join(__dirname, "..", "fixtures", "https.crt"), })); })); }); }); describe("with refreshTokenValidators", () => { it("should fail when issuer is 'realm'", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ refreshTokenValidators: [ { issuer: "realm", publicKey: "foo" } ] }), "'realm' is not a valid value for a CustomTokenValidatorConfig.issuer"); })); it("should fail when there are duplicate issuers", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(startServer({ refreshTokenValidators: [ { issuer: "foo", publicKey: "foo" }, { issuer: "foo", publicKey: "bar" } ] }), "Duplicate issuer set in the 'refreshTokenValidators' collection: 'foo'"); })); it("should start when there are multiple validators", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(startServer({ refreshTokenValidators: [ { issuer: "foo", publicKey: "foo" }, { issuer: "bar", publicKey: "bar" } ] })); })); }); }); it("should be able to add a new service", () => __awaiter(this, void 0, void 0, function* () { server.addService(new StartService()); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.isObject(server.services.find(service => service instanceof StartService)); chai_1.assert.equal(server.services.length, 1); })); it("should be register @ServerStarted decorator", () => __awaiter(this, void 0, void 0, function* () { const startService = new StartService(); server.addService(startService); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.isObject(server.services.find(service => service instanceof StartService)); chai_1.assert.equal(server.services.length, 1); chai_1.assert.exists(startService.server); chai_1.assert.equal(startService.server, server); })); it("should be able to add an array of services with one method: addServices", () => __awaiter(this, void 0, void 0, function* () { server.addServices(new StartService(), new StartService(), new StartService()); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.isObject(server.services.find(service => service instanceof StartService)); chai_1.assert.equal(server.services.length, 3); })); it("should be able to remove a service by name", () => __awaiter(this, void 0, void 0, function* () { server.addService(new StartService()); chai_1.assert.equal(server.services.length, 1); server.removeService("start"); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.equal(server.services.length, 0); })); it("should be able to remove two services with the same name", () => __awaiter(this, void 0, void 0, function* () { server.addService(new StartService()); server.addService(new StartService()); chai_1.assert.equal(server.services.length, 2); server.removeService("start"); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.equal(server.services.length, 0); })); it("should be able to remove a service by instance", () => __awaiter(this, void 0, void 0, function* () { const startService = new StartService(); server.addService(startService); chai_1.assert.equal(server.services.length, 1); server.removeService(startService); yield chai_1.assert.isFulfilled(startServer()); chai_1.assert.equal(server.services.length, 0); })); describe("HTTP Server", () => { let startArgs; let serverUrl; beforeEach(() => __awaiter(this, void 0, void 0, function* () { server.addService(new StartService()); yield startServer(startArgs); serverUrl = `http://${server.address}`; })); describe("authentication", () => { describe("Authorization header", () => { it("should accept nothing", () => __awaiter(this, void 0, void 0, function* () { yield superagent.get(`${serverUrl}/auth_details`); })); it("should accept a signed token", () => __awaiter(this, void 0, void 0, function* () { const refreshToken = new Token_1.RefreshToken({ appId: "io.realm.Test", identity: "user1", isAdmin: true, }); const r = yield superagent.get(`${serverUrl}/auth_details`) .set("Authorization", refreshToken.sign(server.privateKey)); chai_1.assert.deepEqual(r.body.authToken, refreshToken.toJSON()); })); it("should throw an error with a malformed token", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(superagent.get(`${serverUrl}/auth_details`).set("Authorization", "gibberish stuff"), "Forbidden"); })); describe("with a custom name", () => { before(() => { startArgs = { authorizationHeaderName: "MyCustomHeader" }; }); it("should accept a signed token", () => __awaiter(this, void 0, void 0, function* () { const refreshToken = new Token_1.RefreshToken({ appId: "io.realm.Test", identity: "user1", isAdmin: true, }); const r = yield superagent.get(`${serverUrl}/auth_details`) .set("MyCustomHeader", refreshToken.sign(server.privateKey)); chai_1.assert.deepEqual(r.body.authToken, refreshToken.toJSON()); })); after(() => { startArgs = undefined; }); }); }); }); describe("input parsing", () => { it("should reject with 400 error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(superagent.post(`${serverUrl}/test_post`) .type("application/json") .send("{ some: \"incomplete JSON\""), "Bad Request"); })); }); describe("websocket handler", () => { describe("where service returns a rejecting promise", () => { it("should properly respond with an error", () => __awaiter(this, void 0, void 0, function* () { const req = { url: "/reject", rawHeaders: [], }; const socket = { write: sinon.stub(), end: sinon.stub(), }; const head = Buffer.from(""); yield server["upgradeHandler"](req, socket, head); sinon.assert.calledOnce(socket.write); const response = socket.write.getCall(0).args[0]; const expected_500 = "HTTP/1.1 500 Internal Server Error\r\nServer: Realm-Object-Server/"; chai_1.assert(response.startsWith(expected_500), "Response must start with HTTP/1.1 500 Internal Server Error"); sinon.assert.calledOnce(socket.end); })); }); describe("where service throws an error", () => { it("should properly respond with an error", () => __awaiter(this, void 0, void 0, function* () { const req = { url: "/throw", rawHeaders: [], }; const socket = { write: sinon.stub(), end: sinon.stub(), }; const head = Buffer.from(""); yield server["upgradeHandler"](req, socket, head); sinon.assert.calledOnce(socket.write); const response = socket.write.getCall(0).args[0]; const expected_500 = "HTTP/1.1 500 Internal Server Error\r\nServer: Realm-Object-Server/"; chai_1.assert(response.startsWith(expected_500), "Response must start with HTTP/1.1 500 Internal Server Error"); sinon.assert.calledOnce(socket.end); })); }); describe("where route is not found", () => { it("should properly respond with an error", () => __awaiter(this, void 0, void 0, function* () { const req = { url: "/reject-not-found", rawHeaders: [], }; const socket = { write: sinon.stub(), end: sinon.stub(), }; const head = Buffer.from(""); yield server["upgradeHandler"](req, socket, head); sinon.assert.calledOnce(socket.write); const response = socket.write.getCall(0).args[0]; const expected_404 = "HTTP/1.1 404 Not Found\r\nServer: Realm-Object-Server/"; chai_1.assert(response.startsWith(expected_404), "Response must start with HTTP/1.1 404 Not Found"); sinon.assert.calledOnce(socket.end); })); }); }); }); }); //# sourceMappingURL=Server.spec.js.map