"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_1 = 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");
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 = Token_1.Token.parse(server.adminToken, server.publicKey);
        }));
        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.'
                        }]);
                }));
            });
            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("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 user;
                let username;
                before(() => __awaiter(this, void 0, void 0, function* () {
                    username = faker.internet.userName();
                    user = 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 authToken", () => {
                it("should reject with an error", () => __awaiter(this, void 0, void 0, function* () {
                    yield chai_1.assert.isRejected(service['deleteUser']("some-bogus-userId", { noAuthToken: "something" }), errors.realm.AccessDenied);
                }));
            });
            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_1.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)).not.to.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)).not.to.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 tokenData = Token_1.Token.parse(token, server.publicKey).encodedData();
                yield server.authClient.revokeToken(token);
                let revocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', tokenData);
                chai_1.expect(revocation).to.be.ok;
                yield index_1.delay(1100);
                service['pruneRevocationTokens']();
                revocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', tokenData);
                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 noExpirationTokenData = Token_1.Token.parse(noExpirationToken, server.publicKey).encodedData();
                const futureExpirationTokenData = Token_1.Token.parse(futureExpirationToken, server.publicKey).encodedData();
                yield server.authClient.revokeToken(noExpirationToken);
                yield server.authClient.revokeToken(futureExpirationToken);
                let noExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', noExpirationTokenData);
                let futureExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', futureExpirationTokenData);
                chai_1.expect(noExpirationRevocation).to.be.ok;
                chai_1.expect(futureExpirationRevocation).to.be.ok;
                service['pruneRevocationTokens']();
                noExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', noExpirationTokenData);
                futureExpirationRevocation = tokenRevocationRealm.objectForPrimaryKey('TokenRevocation', futureExpirationTokenData);
                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_1.v4(), 'password', testCase.isAdmin);
                            let realmPath = `/${uuid_1.v4()}`;
                            if (!testCase.isGlobal) {
                                realmPath = `/${user.userId}${realmPath}`;
                            }
                            if (testCase.existingRealmType) {
                                server.realmDirectoryClient.findByPath({
                                    shouldCreate: true,
                                    realmType: testCase.existingRealmType,
                                    realmPath,
                                    ownerId: user.userId
                                });
                            }
                            if (testCase.isPartial) {
                                realmPath += `/__partial/${user.userId}/${uuid_1.v4()}`;
                            }
                            const refreshToken = new Token_1.RefreshToken({
                                isAdmin: testCase.isAdmin,
                                appId: '',
                                identity: user.userId
                            });
                            const promise = service['accessToken'](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.registerWithProvider(server.url, {
                    provider: 'anonymous',
                    providerToken: undefined,
                    userInfo: undefined
                }));
                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);
                const user = yield chai_1.assert.isFulfilled(Realm.Sync.User.registerWithProvider(server.url, {
                    provider: 'jwt',
                    providerToken: validUserAccessToken,
                    userInfo: undefined
                }));
                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.registerWithProvider(server.url, {
                    provider: providerName,
                    providerToken: validUserAccessToken,
                    userInfo: undefined
                }));
                chai_1.assert.isDefined(user);
                chai_1.assert.isDefined(user.identity);
                chai_1.assert.isFalse(user.isAdmin);
                yield server.authClient.removeProvider(providerName);
                providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 0);
                chai_1.assert.equal(providers.length, 0);
            }));
            describe('when config has placeholders', () => {
                before(() => {
                    process.env.smtp_username = 'foo';
                    process.env.smtp_password = 'bar';
                });
                after(() => {
                    delete process.env.smtp_username;
                    delete process.env.smtp_password;
                });
                it("they are replaced at runtime", () => __awaiter(this, void 0, void 0, function* () {
                    const passwordConfig = {
                        emailHandlerConfig: {
                            connectionString: 'smtp://%%smtp_username%%:%%smtp_password%%@smtp.example.com',
                            from: 'foo@bar.com'
                        }
                    };
                    yield server.authClient.addProvider('PasswordAuthProvider', passwordConfig);
                    const providers = yield index_1.waitAsync(() => server.authClient.getProviders(), providers => providers.length === 1);
                    chai_1.assert.equal(providers.length, 1);
                    const passwordProvider = service['providers'][0];
                    chai_1.assert.isDefined(passwordProvider);
                    chai_1.assert.instanceOf(passwordProvider, auth_1.PasswordAuthProvider);
                    const actualPasswordConfig = passwordProvider['config'];
                    chai_1.assert.isDefined(actualPasswordConfig.emailHandlerConfig);
                    chai_1.assert.isDefined(actualPasswordConfig.emailHandlerConfig.connectionString);
                    chai_1.assert.equal(actualPasswordConfig.emailHandlerConfig.connectionString, 'smtp://foo:bar@smtp.example.com');
                    const retrievedProvider = providers.find(p => p.name === 'PasswordAuthProvider');
                    const retrievedPasswordConfig = retrievedProvider.config;
                    chai_1.assert.isDefined(retrievedPasswordConfig.emailHandlerConfig);
                    chai_1.assert.isDefined(retrievedPasswordConfig.emailHandlerConfig.connectionString);
                    chai_1.assert.equal(retrievedPasswordConfig.emailHandlerConfig.connectionString, passwordConfig.emailHandlerConfig.connectionString);
                }));
            });
        });
    });
});
//# sourceMappingURL=AuthService.spec.js.map