UNPKG

realm-object-server

Version:

Realm Object Server

785 lines 55.3 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 chai_1 = require("chai"); const chai = require("chai"); chai.use(require("chai-as-promised")); const sinon = require("sinon"); const faker = require("faker"); const uuid = require("uuid"); const TestServer_1 = require("../TestServer"); const Token_1 = require("../shared/Token"); const auth_1 = require("../auth"); const errors = require("../errors"); const index_1 = require("../shared/index"); const index_2 = require("../index"); const realms_1 = require("../realms"); describe("AuthService", () => { describe("build time API", () => { let server; let service; let provider; let passwordProvider; let passwordEmailHandler; let adminAuthToken; before(() => __awaiter(this, void 0, void 0, function* () { server = new TestServer_1.TestServer(); provider = new auth_1.NicknameAuthProvider(); passwordEmailHandler = { resetPassword: (email, token, userAgent, remoteIp) => __awaiter(this, void 0, void 0, function* () { }) }; passwordProvider = new auth_1.PasswordAuthProvider({ emailHandler: passwordEmailHandler }); yield server.start({ authProviders: [provider, passwordProvider] }); service = server.getService("auth"); adminAuthToken = server.tokenValidator.parse(server.adminToken); })); after(() => __awaiter(this, void 0, void 0, function* () { yield server.shutdown(); })); describe("createOrUpdateUser", () => { describe("without a provider ID and provider", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { const err = yield chai_1.assert.isRejected(service.createOrUpdateUser(undefined, undefined, false), errors.realm.MissingParameters); chai_1.assert.includeDeepMembers(err.invalidParams, [{ name: "providerId", reason: "Missing parameter 'providerId'!", }, { name: "provider", reason: "Missing parameter 'provider'!", }]); })); }); describe("without a provider", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { const err = yield chai_1.assert.isRejected(service.createOrUpdateUser("some-user", undefined, false), errors.realm.MissingParameters); chai_1.assert.includeDeepMembers(err.invalidParams, [{ name: "provider", reason: "Missing parameter 'provider'!", }]); })); }); describe("without a providerId", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { const err = yield chai_1.assert.isRejected(service.createOrUpdateUser(undefined, "nickname", false), errors.realm.MissingParameters); chai_1.assert.includeDeepMembers(err.invalidParams, [{ name: "providerId", reason: "Missing parameter 'providerId'!", }]); })); }); describe("with a userId that requires URL encoding", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { const err = yield chai_1.assert.isRejected(service.createOrUpdateUser("some-user", "nickname", false, {}, "some/user"), errors.realm.InvalidParameters); chai_1.assert.includeDeepMembers(err.invalidParams, [{ name: "userId", reason: "Realm requires the userId that does not require URI encoding. Supplied: 'some/user', encoded: 'some%2Fuser'", }]); })); }); describe("when the user already exists", () => { let user; let username; beforeEach(() => __awaiter(this, void 0, void 0, function* () { username = faker.internet.userName(); user = yield service.createOrUpdateUser(username, "nickname", false); })); it("without userId should not create a new user", () => __awaiter(this, void 0, void 0, function* () { const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(username, "nickname", false)); chai_1.assert.isFalse(result.created); })); it("with userId should not create a new user", () => __awaiter(this, void 0, void 0, function* () { const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(username, "nickname", false, {}, user.userId)); chai_1.assert.isFalse(result.created); })); it("with incorrect userId should reject with an error", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isRejected(service.createOrUpdateUser(username, "nickname", false, {}, "some-other-userid"), errors.realm.InvalidParameters); })); describe("with different provider ids but the same userId", () => { it("when providers are the same should have two accounts", () => __awaiter(this, void 0, void 0, function* () { const firstProviderId = faker.internet.userName(); const userId = uuid.v4(); yield chai_1.assert.isFulfilled(service.createOrUpdateUser(firstProviderId, "nickname", false, {}, userId)); const secondProviderId = faker.internet.userName(); const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(secondProviderId, "nickname", false, {}, userId)); chai_1.assert.isTrue(result.created); chai_1.assert.equal(result.accounts.length, 2); chai_1.expect(result.accounts.map(a => { return { providerId: a.providerId, provider: a.provider, }; })).to.eql([ { providerId: firstProviderId, provider: "nickname" }, { providerId: secondProviderId, provider: "nickname" }, ]); })); it("when providers are different should have two accounts", () => __awaiter(this, void 0, void 0, function* () { const nicknameId = faker.internet.userName(); const userId = uuid.v4(); yield chai_1.assert.isFulfilled(service.createOrUpdateUser(nicknameId, "nickname", false, {}, userId)); const secondProviderId = faker.internet.userName(); const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(secondProviderId, "anonymous", false, {}, userId)); chai_1.assert.isTrue(result.created); chai_1.assert.equal(result.accounts.length, 2); chai_1.expect(result.accounts.map(a => { return { providerId: a.providerId, provider: a.provider, }; })).to.eql([ { providerId: nicknameId, provider: "nickname" }, { providerId: secondProviderId, provider: "anonymous" }, ]); })); it("when the user role changes should update the user", () => __awaiter(this, void 0, void 0, function* () { const firstProviderId = faker.internet.userName(); const userId = uuid.v4(); yield chai_1.assert.isFulfilled(service.createOrUpdateUser(firstProviderId, "nickname", false, {}, userId)); const secondProviderId = faker.internet.userName(); const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(secondProviderId, "nickname", true, {}, userId)); chai_1.assert.isTrue(result.created); chai_1.assert.equal(result.accounts.length, 2); chai_1.assert.isTrue(result.isAdmin); })); }); }); describe("when the user does not already exist", () => { it("without userId should create a new user", () => __awaiter(this, void 0, void 0, function* () { const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(faker.internet.userName(), "nickname", false)); chai_1.assert.isTrue(result.created); })); it("with userId should create a new user with the userId", () => __awaiter(this, void 0, void 0, function* () { const username = faker.internet.userName(); const result = yield chai_1.assert.isFulfilled(service.createOrUpdateUser(username, "nickname", false, {}, "some-userid")); chai_1.assert.equal(result.userId, "some-userid"); chai_1.assert.isTrue(result.created); chai_1.assert.equal(result.accounts[0].provider, "nickname"); chai_1.assert.equal(result.accounts[0].providerId, username); })); }); }); describe("getUserById", () => { describe("when the user does not exist", () => { it("should not return a user", () => __awaiter(this, void 0, void 0, function* () { try { yield server.authClient.getUserById("some-userId"); chai_1.assert.fail(); } catch (e) { const expected = new errors.realm.UnknownAccount(); chai_1.expect(e.status).to.equal(expected.status); chai_1.expect(e.code).to.equal(expected.code); chai_1.expect(e.title).to.equal(expected.title); } })); }); describe("when the user exists", () => { let user; let username; before(() => __awaiter(this, void 0, void 0, function* () { username = faker.internet.userName(); user = yield service.createOrUpdateUser(username, "nickname", false); })); it("should return the user", () => __awaiter(this, void 0, void 0, function* () { const foundUser = yield server.authClient.getUserById(user.userId); chai_1.assert.isDefined(foundUser); chai_1.assert.equal(foundUser.userId, user.userId); chai_1.assert.isFalse(foundUser.isAdmin); chai_1.assert.equal(foundUser.accounts[0].provider, "nickname"); chai_1.assert.equal(foundUser.accounts[0].providerId, username); })); }); }); describe("getUserByProviderId", () => { describe("when the user exists", () => { let username; before(() => __awaiter(this, void 0, void 0, function* () { username = faker.internet.userName(); yield service.createOrUpdateUser(username, "nickname", false); })); it("should be able to get a user", () => __awaiter(this, void 0, void 0, function* () { const fetchedUser = service.getUserByProviderId("nickname", username); chai_1.assert.isDefined(fetchedUser); chai_1.assert.isDefined(fetchedUser.userId); chai_1.assert.isFalse(fetchedUser.isAdmin); chai_1.assert.equal(fetchedUser.accounts[0].provider, "nickname"); chai_1.assert.equal(fetchedUser.accounts[0].providerId, username); })); }); describe("when the user does not exist", () => { it("should be able to get a user", () => __awaiter(this, void 0, void 0, function* () { const fetchedUser = service.getUserByProviderId("nickname", "some-user"); chai_1.assert.isUndefined(fetchedUser); })); }); }); describe("updateProviderData", () => { describe("with an existing user", () => { let user; before(() => __awaiter(this, void 0, void 0, function* () { const username = faker.internet.userName(); user = yield service.createOrUpdateUser(username, "nickname", false); })); it("should call the provider's update function", () => __awaiter(this, void 0, void 0, function* () { const spy = sinon.spy(provider, "update"); const updateData = { debuggy: "mcbuggerson" }; yield chai_1.assert.isFulfilled(server.authClient.updateProviderData("nickname", user.userId, updateData)); sinon.assert.calledOnce(spy); const args = spy.getCall(0).args; chai_1.expect(args[0].userId).to.equal(user.userId); chai_1.expect(args[1]).to.deep.equal(updateData); })); }); describe("without an existing user", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { try { yield server.authClient.updateProviderData("nickname", "some-bogus-userId", {}); chai_1.assert.fail(); } catch (e) { const expected = new errors.realm.UnknownAccount(); chai_1.expect(e.status).to.equal(expected.status); chai_1.expect(e.code).to.equal(expected.code); chai_1.expect(e.title).to.equal(expected.title); } })); }); describe("without a valid provider", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { try { yield server.authClient.updateProviderData("snapchat", "lololol", {}); chai_1.assert.fail(); } catch (e) { const expected = new errors.realm.InvalidParameters(); chai_1.expect(e.status).to.equal(expected.status); chai_1.expect(e.code).to.equal(expected.code); chai_1.expect(e.title).to.equal(expected.title); chai_1.expect(e.invalidParams).to.include.deep.members([{ name: "provider", reason: "Invalid parameter 'provider'!" }]); } })); }); }); describe("deleteUser", () => { describe("without an existing user", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { try { yield server.authClient.deleteUser("some-bogus-userId"); chai_1.assert.fail(); } catch (e) { const expected = new errors.realm.UnknownUser(); chai_1.expect(e.status).to.equal(expected.status); chai_1.expect(e.code).to.equal(expected.code); chai_1.expect(e.title).to.equal(expected.title); } })); }); describe("Delete a user twice", () => { let userId; before(() => __awaiter(this, void 0, void 0, function* () { const username = faker.internet.userName(); const user = yield service.createOrUpdateUser(username, "nickname", false); userId = user.userId; })); it("should accept", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.becomes(service["deleteUser"](userId, { authToken: adminAuthToken }), {}); })); it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { try { yield server.authClient.deleteUser(userId); chai_1.assert.fail(); } catch (e) { const expected = new errors.realm.UnknownUser(); chai_1.expect(e.status).to.equal(expected.status); chai_1.expect(e.code).to.equal(expected.code); chai_1.expect(e.title).to.equal(expected.title); } })); }); describe("when a user Realm is open by RealmFactory", () => { it("should close it", () => __awaiter(this, void 0, void 0, function* () { const userId = uuid.v4(); const user = yield service.createOrUpdateUser(userId, "nickname", false, undefined, userId); const realmPath = `/${user.userId}/foo`; const realmDefinition = { remotePath: realmPath, schema: [{ name: "Foo", properties: { bar: "int" } }], syncLabel: "default" }; yield server.ensureRealmExists(realmPath, userId); const preDeleteRealm = yield server.openRealm(realmDefinition); chai_1.assert.isFalse(preDeleteRealm.isClosed); preDeleteRealm.write(() => { preDeleteRealm.create("Foo", { bar: 1 }); }); chai_1.assert.equal(preDeleteRealm.objects("Foo").length, 1); yield chai_1.assert.isFulfilled(service["deleteUser"](userId, { authToken: adminAuthToken })); chai_1.assert.isTrue(preDeleteRealm.isClosed); const postDeleteRealm = yield server.openRealm(realmDefinition); chai_1.assert.isFalse(postDeleteRealm.isClosed); chai_1.assert.notEqual(preDeleteRealm, postDeleteRealm); chai_1.assert.equal(postDeleteRealm.objects("Foo").length, 0); })); }); }); describe("revoke", () => { it("revokes tokens with no expiration", () => __awaiter(this, void 0, void 0, function* () { const token = index_1.generateAdminToken({ privateKey: server.privateKey }); chai_1.expect(() => server.tokenValidator.parse(token)).to.not.throw; yield server.authClient.revokeToken(token); chai_1.expect(() => server.tokenValidator.parse(token)).to.throw(errors.realm.AccessDenied); })); it("revokes tokens with expiration", () => __awaiter(this, void 0, void 0, function* () { const token = index_1.generateAdminToken({ privateKey: server.privateKey, expires: new Date().getTime() / 1000 + 10000 }); chai_1.expect(() => server.tokenValidator.parse(token)).to.not.throw; yield server.authClient.revokeToken(token); chai_1.expect(() => server.tokenValidator.parse(token)).to.throw(errors.realm.AccessDenied); })); }); describe("prune", () => { let tokenRevocationRealm; before(() => __awaiter(this, void 0, void 0, function* () { tokenRevocationRealm = yield server.openRealm(index_2.TokenRevocationRealm); })); after(() => { tokenRevocationRealm.close(); }); it("prunes expired tokens", () => __awaiter(this, void 0, void 0, function* () { const token = index_1.generateAdminToken({ privateKey: server.privateKey, expires: new Date().getTime() / 1000 + 1 }); const tokenId = server.tokenValidator.parse(token).tokenId; yield server.authClient.revokeToken(token); let revocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", tokenId); chai_1.expect(revocation).to.be.ok; yield index_1.delay(1100); yield service["pruneRevocationTokens"](); revocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", tokenId); chai_1.expect(revocation).to.be.undefined; })); it("doesn't prune non-expiring tokens", () => __awaiter(this, void 0, void 0, function* () { const noExpirationToken = index_1.generateAdminToken({ privateKey: server.privateKey }); const futureExpirationToken = index_1.generateAdminToken({ privateKey: server.privateKey, expires: new Date().getTime() / 1000 + 10000 }); const noExpirationTokenId = server.tokenValidator.parse(noExpirationToken).tokenId; const futureExpirationTokenId = server.tokenValidator.parse(futureExpirationToken).tokenId; yield server.authClient.revokeToken(noExpirationToken); yield server.authClient.revokeToken(futureExpirationToken); let noExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", noExpirationTokenId); let futureExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", futureExpirationTokenId); chai_1.expect(noExpirationRevocation).to.be.ok; chai_1.expect(futureExpirationRevocation).to.be.ok; yield service["pruneRevocationTokens"](); noExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", noExpirationTokenId); futureExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey("TokenRevocation", futureExpirationTokenId); chai_1.expect(noExpirationRevocation).to.be.ok; chai_1.expect(futureExpirationRevocation).to.be.ok; })); }); describe("updateProviderAccount", () => { describe("when provider supports updating", () => { let user; let username; beforeEach(() => __awaiter(this, void 0, void 0, function* () { provider.updateProviderAccount = (providerId, user, data, isAuthenticated, userAgent, remoteIp) => __awaiter(this, void 0, void 0, function* () { return {}; }); username = faker.internet.userName(); user = yield service.createOrUpdateUser(username, "nickname", false); })); const updateAccount = (updateData, providerId, token = null) => __awaiter(this, void 0, void 0, function* () { const spy = sinon.spy(provider, "updateProviderAccount"); yield chai_1.assert.isFulfilled(server.authClient.updateProviderAccount("nickname", updateData, providerId, token)); sinon.assert.calledOnce(spy); spy.restore(); return spy.getCall(0).args; }); describe("when providerId provided", () => { it("passes the user", () => __awaiter(this, void 0, void 0, function* () { const updateData = { debuggy: "mcbuggerson" }; const args = yield updateAccount(updateData, username); chai_1.assert.equal(args[0], username); chai_1.assert.equal(args[1].userId, user.userId); chai_1.assert.deepEqual(args[2], updateData); chai_1.assert.isFalse(args[3]); })); describe("when token provided", () => { it("passes isAuthenticated: true", () => __awaiter(this, void 0, void 0, function* () { const updateData = { debuggy: "mcbuggerson" }; const args = yield updateAccount(updateData, username, server.adminToken); chai_1.assert.equal(args[0], username); chai_1.assert.equal(args[1].userId, user.userId); chai_1.assert.deepEqual(args[2], updateData); chai_1.assert.isTrue(args[3]); })); }); }); describe("when providerId not provided", () => { it("passes user: null", () => __awaiter(this, void 0, void 0, function* () { const updateData = { debuggy: "mcbuggerson" }; const args = yield updateAccount(updateData, null); chai_1.assert.isNull(args[0]); chai_1.assert.isNull(args[1]); chai_1.assert.deepEqual(args[2], updateData); chai_1.assert.isFalse(args[3]); })); describe("when token provided", () => { it("passes isAuthenticated: true", () => __awaiter(this, void 0, void 0, function* () { const updateData = { debuggy: "mcbuggerson" }; const args = yield updateAccount(updateData, null, server.adminToken); chai_1.assert.isNull(args[0]); chai_1.assert.isNull(args[1]); chai_1.assert.deepEqual(args[2], updateData); chai_1.assert.isTrue(args[3]); })); }); }); }); describe("without a valid provider", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { const error = yield chai_1.assert.isRejected(server.authClient.updateProviderAccount("snapchat", {})); const expected = new errors.realm.InvalidParameters(); chai_1.expect(error.status).to.equal(expected.status); chai_1.expect(error.code).to.equal(expected.code); chai_1.expect(error.title).to.equal(expected.title); chai_1.expect(error.invalidParams).to.include.deep.members([{ name: "provider", reason: "Invalid parameter 'provider'!" }]); })); }); describe("when provider doesn't support updating", () => { it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () { provider.updateProviderAccount = undefined; const error = yield chai_1.assert.isRejected(server.authClient.updateProviderAccount("nickname", {})); const expected = new errors.realm.InvalidParameters(); chai_1.expect(error.status).to.equal(expected.status); chai_1.expect(error.code).to.equal(expected.code); chai_1.expect(error.title).to.equal(expected.title); chai_1.expect(error.invalidParams).to.include.deep.members([{ name: "provider", reason: "this provider does not support updating user accounts." }]); })); }); }); describe("reset password flow", () => { let user; let username; let password; let spy; let emailSpy; beforeEach(() => __awaiter(this, void 0, void 0, function* () { spy = sinon.spy(passwordProvider, "updateProviderAccount"); emailSpy = sinon.spy(passwordEmailHandler, "resetPassword"); username = faker.internet.email(); password = faker.internet.password(); user = yield passwordProvider.authenticateOrCreateUser({ username, password, register: true }); })); afterEach(() => { spy.restore(); emailSpy.restore(); }); describe("when password reset", () => { describe("and username is valid", () => { it("calls passwordProvider.updateProviderAccount and email sender", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(server.authClient.requestPasswordReset(username)); sinon.assert.calledOnce(spy); const args = spy.getCall(0).args; chai_1.assert.equal(args[0], username); chai_1.assert.equal(args[1].userId, user.userId); chai_1.assert.isFalse(args[3]); sinon.assert.calledOnce(emailSpy); const emailArgs = emailSpy.getCall(0).args; chai_1.assert.equal(emailArgs[0], username); })); }); describe("and username is invalid", () => { it("calls passwordProvider.updateProviderAccount but not email sender", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(server.authClient.requestPasswordReset("some-non-existent-name")); sinon.assert.calledOnce(spy); const args = spy.getCall(0).args; chai_1.assert.equal(args[0], "some-non-existent-name"); chai_1.assert.isUndefined(args[1]); chai_1.assert.isFalse(args[3]); sinon.assert.notCalled(emailSpy); })); }); }); describe("when completePasswordReset", () => { describe("and token is valid", () => { it("resets the user's password", () => __awaiter(this, void 0, void 0, function* () { yield chai_1.assert.isFulfilled(server.authClient.requestPasswordReset(username)); sinon.assert.calledOnce(emailSpy); const token = emailSpy.getCall(0).args[1]; yield chai_1.assert.isFulfilled(server.authClient.completePasswordReset(token, "new_password")); const error = yield chai_1.assert.isRejected(passwordProvider.authenticateOrCreateUser({ username, password, register: false })); const expected = new errors.realm.InvalidCredentials(); chai_1.assert.equal(error.status, expected.status); const newUser = yield chai_1.assert.isFulfilled(passwordProvider.authenticateOrCreateUser({ username, password: "new_password", register: false })); chai_1.assert.equal(newUser.userId, user.userId); })); }); describe("and token is invalid", () => { it("throws an error", () => __awaiter(this, void 0, void 0, function* () { const error = yield chai_1.assert.isRejected(server.authClient.completePasswordReset("invalid-token", "new_password")); const expected = new errors.realm.AccessDenied(); chai_1.assert.equal(error.status, expected.status); })); }); }); }); describe("get access token", () => { const testCases = [ { isAdmin: false, isPartial: false, isGlobal: false, existingRealmType: undefined, expected: undefined }, { isAdmin: false, isPartial: false, isGlobal: false, existingRealmType: index_2.RealmType.full, expected: undefined }, { isAdmin: false, isPartial: false, isGlobal: false, existingRealmType: index_2.RealmType.reference, expected: new errors.realm.InvalidRealmType(index_2.RealmType.full, index_2.RealmType.reference) }, { isAdmin: false, isPartial: false, isGlobal: true, existingRealmType: undefined, expected: new errors.realm.AccessDenied() }, { isAdmin: false, isPartial: false, isGlobal: true, existingRealmType: index_2.RealmType.full, expected: new errors.realm.AccessDenied() }, { isAdmin: false, isPartial: false, isGlobal: true, existingRealmType: index_2.RealmType.reference, expected: new errors.realm.InvalidRealmType(index_2.RealmType.full, index_2.RealmType.reference) }, { isAdmin: false, isPartial: true, isGlobal: false, existingRealmType: undefined, expected: undefined }, { isAdmin: false, isPartial: true, isGlobal: false, existingRealmType: index_2.RealmType.full, expected: new errors.realm.InvalidRealmType(index_2.RealmType.reference, index_2.RealmType.full) }, { isAdmin: false, isPartial: true, isGlobal: false, existingRealmType: index_2.RealmType.reference, expected: undefined }, { isAdmin: false, isPartial: true, isGlobal: true, existingRealmType: undefined, expected: new errors.realm.AccessDenied() }, { isAdmin: false, isPartial: true, isGlobal: true, existingRealmType: index_2.RealmType.full, expected: new errors.realm.InvalidRealmType(index_2.RealmType.reference, index_2.RealmType.full) }, { isAdmin: false, isPartial: true, isGlobal: true, existingRealmType: index_2.RealmType.reference, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: false, existingRealmType: undefined, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: false, existingRealmType: index_2.RealmType.full, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: false, existingRealmType: index_2.RealmType.reference, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: true, existingRealmType: undefined, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: true, existingRealmType: index_2.RealmType.full, expected: undefined }, { isAdmin: true, isPartial: false, isGlobal: true, existingRealmType: index_2.RealmType.reference, expected: undefined }, { isAdmin: true, isPartial: true, isGlobal: false, existingRealmType: undefined, expected: undefined }, { isAdmin: true, isPartial: true, isGlobal: false, existingRealmType: index_2.RealmType.full, expected: new errors.realm.InvalidRealmType(index_2.RealmType.reference, index_2.RealmType.full) }, { isAdmin: true, isPartial: true, isGlobal: false, existingRealmType: index_2.RealmType.reference, expected: undefined }, { isAdmin: true, isPartial: true, isGlobal: true, existingRealmType: undefined, expected: undefined }, { isAdmin: true, isPartial: true, isGlobal: true, existingRealmType: index_2.RealmType.full, expected: new errors.realm.InvalidRealmType(index_2.RealmType.reference, index_2.RealmType.full) }, { isAdmin: true, isPartial: true, isGlobal: true, existingRealmType: index_2.RealmType.reference, expected: undefined } ]; for (const testCase of testCases) { const description = `when user is ${testCase.isAdmin ? "" : "NOT "}admin ` + `and path is ${testCase.isPartial ? "" : "NOT "}partial and ${testCase.isGlobal ? "" : "NOT "}global ` + `and existing Realm is ${testCase.existingRealmType}`; describe(description, () => { it(`should ${testCase.expected === undefined ? "succeed" : "throw an error"}`, function () { return __awaiter(this, void 0, void 0, function* () { this.timeout(10000000); const user = yield service.createOrUpdateUser(uuid.v4(), "password", testCase.isAdmin); let realmPath = `/${uuid.v4()}`; if (!testCase.isGlobal) { realmPath = `/${user.userId}${realmPath}`; } if (testCase.existingRealmType) { yield server.realmDirectoryClient.findByPath({ shouldCreate: true, realmType: testCase.existingRealmType, realmPath, ownerId: user.userId }); } if (testCase.isPartial) { realmPath += `/__partial/${user.userId}/${uuid.v4()}`; } const refreshToken = new Token_1.RefreshToken({ isAdmin: testCase.isAdmin, appId: "", identity: user.userId }); const promise = service["accessToken"](user, realmPath, refreshToken, refreshToken.sign(server.privateKey)); if (testCase.expected === undefined) { const result = yield chai_1.assert.isFulfilled(promise); chai_1.assert.equal(result.access_token.token_data.identity, user.userId); chai_1.assert.equal(result.access_token.token_data.path, realmPath); } else { const error = yield chai_1.assert.isRejected(promise); chai_1.assert.equal(error.code, testCase.expected.code); chai_1.assert.equal(error.status, testCase.expected.status); } }); }); }); } }); }); describe("runtime configuration API", () => { const defaultProviders = ["Password", "Anonymous", "Nickname"].map(p => `${p}AuthProvider`); const allProviders = ["Azure", "Cloudkit", "Facebook", "Google", "Jwt"].map(p => `${p}AuthProvider`).concat(defaultProviders); let server; let service; const isEmpty = (obj) => { if (!obj) { return true; } for (const key in obj) { if (obj.hasOwnProperty(key)) { return false; } } return true; }; beforeEach(() => __awaiter(this, void 0, void 0, function* () { server = new TestServer_1.TestServer(); yield server.start(); service = server.getService("auth"); })); afterEach(() => __awaiter(this, void 0, void 0, function* () { yield server.shutdown(); })); describe("get providers", () => { describe("when default", () => { it("should contain password, anonymous, and nickname", () => __awaiter(this, void 0, void 0, function* () { const providers = yield server.authClient.getProviders(); chai_1.assert.equal(providers.length, 3); for (const item of defaultProviders) { const provider = providers.find(p => p.type === item); chai_1.assert.isDefined(provider); chai_1.assert.equal(provider.name, provider.type); if (provider.name === "PasswordAuthProvider") { chai_1.assert.isDefined(provider.config); chai_1.assert.isTrue(provider.config.autoCreateAdminUser); } else { chai_1.assert.isTrue(isEmpty(provider.config)); } } })); }); describe("when nickname has been removed", () => { it("should contain password and anonymous", () => __awaiter(this, void 0, void 0, function* () { let providers = yield server.authClient.getProviders(); chai_1.assert.equal(providers.length, 3); yield server.authClient.removeProvider("NicknameAuthProvider"); providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 2); chai_1.assert.equal(providers.length, 2); const expected = defaultProviders.filter(p => p !== "NicknameAuthProvider"); for (const item of expected) { const provider = providers.find(p => p.type === item); chai_1.assert.isDefined(provider); } })); }); describe("when facebook is added", () => { it("should contain password, anonymous, nickname, facebook", () => __awaiter(this, void 0, void 0, function* () { let providers = yield server.authClient.getProviders(); chai_1.assert.equal(providers.length, 3); yield server.authClient.addProvider("FacebookAuthProvider"); providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 4); chai_1.assert.equal(providers.length, 4); const expected = defaultProviders.concat("FacebookAuthProvider"); for (const item of expected) { const provider = providers.find(p => p.type === item); chai_1.assert.isDefined(provider); } })); }); }); describe("add provider", () => { beforeEach(function () { return __awaiter(this, void 0, void 0, function* () { yield Promise.all(defaultProviders.map(p => { return server.authClient.removeProvider(p); })); yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 0); }); }); for (const provider of allProviders) { it(`should be able to add ${provider}`, () => __awaiter(this, void 0, void 0, function* () { yield server.authClient.addProvider(provider, {}); const providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 1); chai_1.assert.equal(providers.length, 1); chai_1.assert.equal(providers[0].name, provider); chai_1.assert.equal(providers[0].type, provider); })); } const jwtConfig = { publicKey: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsPJV7FJ5iOgkI6B9o+sE\n/aNHAzfXDNt059CPQathgUvdbpjanfRkNmYhmx7Bfa8ybPFxJZTs53KoLXuAN3uR\nuxFPL6Ki9320JRxlA/zLN7OKklY+M3ftQcvqYuyLvUHwDEG33p5gHngUP7MH+67x\nLWUGSRq2oi6261LwloCceiyzv4UYbxOTvgMC7RYTAcIRfreJTSuww4deffSCtaqd\nbyJLW3G4qUMfEAF5dwxZzx3hSyg9mCyimzdsD5s7TjpQq+mdHudj5mFvN07y11A0\nZqP6KyWQX7JETLJZTJgelqpynqmnNzdGgnxwGkM8h2LlOCXk+z3OpSzXvwfw4AXo\nJwIDAQAB\n-----END PUBLIC KEY-----" }; const validUserAccessToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJmOTVjYzdjZi02MGI3LTQ5YjItYWI0Yi0xZmRjMjgwZWIwYmQiLCJpYXQiOjE1MTEyNzM2NzEsImV4cCI6NDY2NzAzMzY3MX0.AUZiMB-0LyraIWL-CjHGdZR-KdRzqfWPxhyuzsdISgdQdGeXPDd78b_D1mo5c4lBUjJsuTUBJ38Ao5EVnKixe-BbsnasWLNEXErIMD2v3ip4edfqW3oOz1MIdyw-_UEBAlfDqzu3cVtDJlMk1kSbsvxILPxvf0K8mS282qxDR-bSGUhP0rfDZ26A4G3gCOeHes8hZCk5RHIovmIz3u7rmUXogNZ9gC6hwf7YE8HoAZrOIRxQPfPv4UQTpb0i6eOAaA2UL-UDb9PlfrKkw9Z35CmPqDq3DVCWX51mMSNP3edVeDTx2dA7Vv86yRVa1SL5HRcts_l0WHrq4C6qRDTq3Q"; it("should be able to add provider without config", () => __awaiter(this, void 0, void 0, function* () { yield server.authClient.addProvider("AnonymousAuthProvider"); let providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 1); chai_1.assert.equal(providers.length, 1); chai_1.assert.isTrue(isEmpty(providers[0].config)); chai_1.assert.equal(providers[0].name, "AnonymousAuthProvider"); chai_1.assert.equal(providers[0].type, "AnonymousAuthProvider"); const user = yield chai_1.assert.isFulfilled(Realm.Sync.User.login(server.url, Realm.Sync.Credentials.anonymous())); chai_1.assert.isDefined(user); chai_1.assert.isDefined(user.identity); chai_1.assert.isFalse(user.isAdmin); yield server.authClient.removeProvider("AnonymousAuthProvider"); providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 0); chai_1.assert.equal(providers.length, 0); })); it("should set its config", () => __awaiter(this, void 0, void 0, function* () { yield server.authClient.addProvider("JwtAuthProvider", jwtConfig); const providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 1); chai_1.assert.equal(providers.length, 1); chai_1.assert.deepEqual(providers[0].config, jwtConfig); yield Realm.Sync.User.login(server.url, Realm.Sync.Credentials.jwt(validUserAccessToken)); const user = yield chai_1.assert.isFulfilled(Realm.Sync.User.login(server.url, Realm.Sync.Credentials.jwt(validUserAccessToken))); chai_1.assert.isDefined(user); chai_1.assert.isDefined(user.identity); chai_1.assert.isFalse(user.isAdmin); })); it("should set its name", () => __awaiter(this, void 0, void 0, function* () { const providerName = "Foo"; const namedJwtConfig = Object.assign({ providerName }, jwtConfig); yield server.authClient.addProvider("JwtAuthProvider", namedJwtConfig, providerName); let providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 1); chai_1.assert.equal(providers.length, 1); chai_1.assert.deepEqual(providers[0].config, namedJwtConfig); chai_1.assert.equal(providers[0].name, providerName); chai_1.assert.equal(providers[0].type, "JwtAuthProvider"); const user = yield chai_1.assert.isFulfilled(Realm.Sync.User.login(server.url, Realm.Sync.Credentials.jwt(validUserAccessToken, providerName))); chai_1.assert.isDefined(user); chai_1.assert.isDefined(user.identity); chai_1.assert.isFalse(user.isAdmin); yield se