diff --git a/packages/auth/e2e/auth.e2e.js b/packages/auth/e2e/auth.e2e.js index 5c89134e8a..b4672b848a 100644 --- a/packages/auth/e2e/auth.e2e.js +++ b/packages/auth/e2e/auth.e2e.js @@ -23,1073 +23,2220 @@ const DISABLED_PASS = 'test1234'; const { clearAllUsers, disableUser, getLastOob, resetPassword } = require('./helpers'); -describe('auth()', function () { - before(async function () { - await clearAllUsers(); - await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - const disabledUserCredential = await firebase - .auth() - .createUserWithEmailAndPassword(DISABLED_EMAIL, DISABLED_PASS); - await disableUser(disabledUserCredential.user.uid); - }); - - beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); - await Utils.sleep(50); - } - }); +describe('auth() modular', function () { + describe('firebase v8 compatibility', function () { + before(async function () { + await clearAllUsers(); + await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + const disabledUserCredential = await firebase + .auth() + .createUserWithEmailAndPassword(DISABLED_EMAIL, DISABLED_PASS); + await disableUser(disabledUserCredential.user.uid); + }); - describe('namespace', function () { - it('accessible from firebase.app()', function () { - const app = firebase.app(); - should.exist(app.auth); - app.auth().app.should.equal(app); + beforeEach(async function () { + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); + } }); - // removing as pending if module.options.hasMultiAppSupport = true - it('supports multiple apps', async function () { - firebase.auth().app.name.should.equal('[DEFAULT]'); + describe('namespace', function () { + it('accessible from firebase.app()', function () { + const app = firebase.app(); + should.exist(app.auth); + app.auth().app.should.equal(app); + }); - firebase - .auth(firebase.app('secondaryFromNative')) - .app.name.should.equal('secondaryFromNative'); + // removing as pending if module.options.hasMultiAppSupport = true + it('supports multiple apps', async function () { + firebase.auth().app.name.should.equal('[DEFAULT]'); - firebase.app('secondaryFromNative').auth().app.name.should.equal('secondaryFromNative'); - }); - }); + firebase + .auth(firebase.app('secondaryFromNative')) + .app.name.should.equal('secondaryFromNative'); - describe('applyActionCode()', function () { - // Needs a different setup to work against the auth emulator - xit('works as expected', async function () { - await firebase - .auth() - .applyActionCode('fooby shooby dooby') - .then($ => $); + firebase.app('secondaryFromNative').auth().app.name.should.equal('secondaryFromNative'); + }); }); - it('errors on invalid code', async function () { - try { + + describe('applyActionCode()', function () { + // Needs a different setup to work against the auth emulator + xit('works as expected', async function () { await firebase .auth() .applyActionCode('fooby shooby dooby') .then($ => $); - } catch (e) { - e.message.should.containEql('code is invalid'); - } + }); + it('errors on invalid code', async function () { + try { + await firebase + .auth() + .applyActionCode('fooby shooby dooby') + .then($ => $); + } catch (e) { + e.message.should.containEql('code is invalid'); + } + }); }); - }); - describe('checkActionCode()', function () { - it('errors on invalid code', async function () { - try { - await firebase.auth().checkActionCode('fooby shooby dooby'); - } catch (e) { - e.message.should.containEql('code is invalid'); - } + describe('checkActionCode()', function () { + it('errors on invalid code', async function () { + try { + await firebase.auth().checkActionCode('fooby shooby dooby'); + } catch (e) { + e.message.should.containEql('code is invalid'); + } + }); }); - }); - describe('reload()', function () { - it('Meta data returns as expected with annonymous sign in', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(50); - const firstUser = firebase.auth().currentUser; - await firstUser.reload(); + describe('reload()', function () { + it('Meta data returns as expected with annonymous sign in', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(50); + const firstUser = firebase.auth().currentUser; + await firstUser.reload(); - await firebase.auth().signOut(); + await firebase.auth().signOut(); - await firebase.auth().signInAnonymously(); - await Utils.sleep(50); - const secondUser = firebase.auth().currentUser; - await secondUser.reload(); + await firebase.auth().signInAnonymously(); + await Utils.sleep(50); + const secondUser = firebase.auth().currentUser; + await secondUser.reload(); - firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); - }); + firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); + }); - it('Meta data returns as expected with email and password sign in', async function () { - const random = Utils.randString(12, '#aA'); - const email1 = `${random}@${random}.com`; - const pass = random; + it('Meta data returns as expected with email and password sign in', async function () { + const random = Utils.randString(12, '#aA'); + const email1 = `${random}@${random}.com`; + const pass = random; - await firebase.auth().createUserWithEmailAndPassword(email1, pass); - const firstUser = firebase.auth().currentUser; - await firstUser.reload(); + await firebase.auth().createUserWithEmailAndPassword(email1, pass); + const firstUser = firebase.auth().currentUser; + await firstUser.reload(); - await firebase.auth().signOut(); + await firebase.auth().signOut(); - const anotherRandom = Utils.randString(12, '#aA'); - const email2 = `${anotherRandom}@${anotherRandom}.com`; + const anotherRandom = Utils.randString(12, '#aA'); + const email2 = `${anotherRandom}@${anotherRandom}.com`; - await firebase.auth().createUserWithEmailAndPassword(email2, pass); - const secondUser = firebase.auth().currentUser; - await secondUser.reload(); + await firebase.auth().createUserWithEmailAndPassword(email2, pass); + const secondUser = firebase.auth().currentUser; + await secondUser.reload(); - firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); + firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); + }); }); - }); - describe('confirmPasswordReset()', function () { - it('errors on invalid code via API', async function () { - try { - await firebase.auth().confirmPasswordReset('fooby shooby dooby', 'passwordthing'); - } catch (e) { - e.message.should.containEql('code is invalid'); - } + describe('confirmPasswordReset()', function () { + it('errors on invalid code via API', async function () { + try { + await firebase.auth().confirmPasswordReset('fooby shooby dooby', 'passwordthing'); + } catch (e) { + e.message.should.containEql('code is invalid'); + } + }); }); - }); - describe('signInWithCustomToken()', function () { - // Needs a different setup when running against the emulator - xit('signs in with a admin sdk created custom auth token', async function () { - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql(TEST_EMAIL); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(false); - - return currentUser; - }; - - const user = await firebase - .auth() - .signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS) - .then(successCb); + describe('signInWithCustomToken()', function () { + // Needs a different setup when running against the emulator + xit('signs in with a admin sdk created custom auth token', async function () { + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return currentUser; + }; + + const user = await firebase + .auth() + .signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS) + .then(successCb); - const IdToken = await firebase.auth().currentUser.getIdToken(); + const IdToken = await firebase.auth().currentUser.getIdToken(); - firebase.auth().signOut(); - await Utils.sleep(50); + firebase.auth().signOut(); + await Utils.sleep(50); - const token = await new TestAdminApi(IdToken).auth().createCustomToken(user.uid, {}); + const token = await new TestAdminApi(IdToken).auth().createCustomToken(user.uid, {}); - await firebase.auth().signInWithCustomToken(token); + await firebase.auth().signInWithCustomToken(token); - firebase.auth().currentUser.email.should.equal(TEST_EMAIL); + firebase.auth().currentUser.email.should.equal(TEST_EMAIL); + }); }); - }); - describe('onAuthStateChanged()', function () { - it('calls callback with the current user and when auth state changes', async function () { - await firebase.auth().signInAnonymously(); + describe('onAuthStateChanged()', function () { + it('calls callback with the current user and when auth state changes', async function () { + await firebase.auth().signInAnonymously(); - await Utils.sleep(50); + await Utils.sleep(50); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out + // Sign out - await firebase.auth().signOut(); - - // Assertions + await firebase.auth().signOut(); - await Utils.sleep(50); + // Assertions - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); + await Utils.sleep(50); - // Tear down + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); - unsubscribe(); - }); + // Tear down - it('accept observer instead callback as well', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + unsubscribe(); + }); - // Test - const observer = { - next(user) { - // Test this access - this.onNext(); - this.user = user; - }, - }; + it('accept observer instead callback as well', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); + + // Test + const observer = { + next(user) { + // Test this access + this.onNext(); + this.user = user; + }, + }; - let unsubscribe; - await new Promise(resolve => { - observer.onNext = resolve; - unsubscribe = firebase.auth().onAuthStateChanged(observer); - }); - should.exist(observer.user); + let unsubscribe; + await new Promise(resolve => { + observer.onNext = resolve; + unsubscribe = firebase.auth().onAuthStateChanged(observer); + }); + should.exist(observer.user); - // Sign out + // Sign out - await firebase.auth().signOut(); + await firebase.auth().signOut(); - // Assertions + // Assertions - await Utils.sleep(50); + await Utils.sleep(50); - should.not.exist(observer.user); + should.not.exist(observer.user); - // Tear down + // Tear down - unsubscribe(); - }); + unsubscribe(); + }); - it('stops listening when unsubscribed', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + it('stops listening when unsubscribed', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out - await firebase.auth().signOut(); - await Utils.sleep(50); + // Sign out + await firebase.auth().signOut(); + await Utils.sleep(50); - // Assertions - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); - // Unsubscribe - unsubscribe(); + // Unsubscribe + unsubscribe(); - // Sign back in - await firebase.auth().signInAnonymously(); + // Sign back in + await firebase.auth().signInAnonymously(); - // Assertions - callback.should.be.calledTwice(); + // Assertions + callback.should.be.calledTwice(); - // Tear down - await firebase.auth().signOut(); - await Utils.sleep(50); - }); + // Tear down + await firebase.auth().signOut(); + await Utils.sleep(50); + }); - it('returns the same user with multiple subscribers #1815', async function () { - const callback = sinon.spy(); + it('returns the same user with multiple subscribers #1815', async function () { + const callback = sinon.spy(); - let unsubscribe1; - let unsubscribe2; - let unsubscribe3; + let unsubscribe1; + let unsubscribe2; + let unsubscribe3; - await new Promise(resolve => { - unsubscribe1 = firebase.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + unsubscribe1 = firebase.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - await new Promise(resolve => { - unsubscribe2 = firebase.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + unsubscribe2 = firebase.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - await new Promise(resolve => { - unsubscribe3 = firebase.auth().onAuthStateChanged(user => { - callback(user); - resolve(); + await new Promise(resolve => { + unsubscribe3 = firebase.auth().onAuthStateChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledThrice(); - callback.should.be.calledWith(null); + callback.should.be.calledThrice(); + callback.should.be.calledWith(null); - await firebase.auth().signInAnonymously(); - await Utils.sleep(800); + await firebase.auth().signInAnonymously(); + await Utils.sleep(800); - unsubscribe1(); - unsubscribe2(); - unsubscribe3(); + unsubscribe1(); + unsubscribe2(); + unsubscribe3(); - callback.should.be.callCount(6); + callback.should.be.callCount(6); - const uid = callback.getCall(3).args[0].uid; + const uid = callback.getCall(3).args[0].uid; - callback.getCall(4).args[0].uid.should.eql(uid); - callback.getCall(5).args[0].uid.should.eql(uid); + callback.getCall(4).args[0].uid.should.eql(uid); + callback.getCall(5).args[0].uid.should.eql(uid); - await firebase.auth().signOut(); - await Utils.sleep(50); + await firebase.auth().signOut(); + await Utils.sleep(50); + }); }); - }); - describe('onIdTokenChanged()', function () { - it('calls callback with the current user and when auth state changes', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + describe('onIdTokenChanged()', function () { + it('calls callback with the current user and when auth state changes', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onIdTokenChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onIdTokenChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); - // Sign out - await firebase.auth().signOut(); - await Utils.sleep(50); + // Sign out + await firebase.auth().signOut(); + await Utils.sleep(50); - // Assertions - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); - // Tear down - unsubscribe(); - }); + // Tear down + unsubscribe(); + }); - it('stops listening when unsubscribed', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + it('stops listening when unsubscribed', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onIdTokenChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onIdTokenChanged(user => { + callback(user); + resolve(); + }); }); - }); - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); + + // Sign out + await firebase.auth().signOut(); + await Utils.sleep(50); - // Sign out - await firebase.auth().signOut(); - await Utils.sleep(50); + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); - // Assertions - callback.should.be.calledWith(null); - callback.should.be.calledTwice(); + // Unsubscribe + unsubscribe(); - // Unsubscribe - unsubscribe(); + // Sign back in + await firebase.auth().signInAnonymously(); - // Sign back in - await firebase.auth().signInAnonymously(); + // Assertions + callback.should.be.calledTwice(); - // Assertions - callback.should.be.calledTwice(); + // Tear down + await firebase.auth().signOut(); + await Utils.sleep(50); + }); - // Tear down - await firebase.auth().signOut(); - await Utils.sleep(50); - }); + it('listens to a null user when auth result is not defined', async function () { + let unsubscribe; - it('listens to a null user when auth result is not defined', async function () { - let unsubscribe; + const callback = sinon.spy(); - const callback = sinon.spy(); + await new Promise(resolve => { + unsubscribe = firebase.auth().onIdTokenChanged(user => { + callback(user); + resolve(); + }); - await new Promise(resolve => { - unsubscribe = firebase.auth().onIdTokenChanged(user => { - callback(user); - resolve(); + unsubscribe(); }); - - unsubscribe(); }); }); - }); - describe('onUserChanged()', function () { - it('calls callback with the current user and when auth state changes', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + describe('onUserChanged()', function () { + it('calls callback with the current user and when auth state changes', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - // Test - const callback = sinon.spy(); + // Test + const callback = sinon.spy(); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onUserChanged(user => { - callback(user); - resolve(); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onUserChanged(user => { + callback(user); + resolve(); + }); }); - }); - - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); - - // Sign out - await firebase.auth().signOut(); - await Utils.sleep(500); - // Assertions - callback.should.be.calledWith(null); - // Because of the way onUserChanged works, it will be called double - // - once for onAuthStateChanged - // - once for onIdTokenChanged - callback.should.have.callCount(4); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); - // Tear down - unsubscribe(); - }); - - it('stops listening when unsubscribed', async function () { - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + // Sign out + await firebase.auth().signOut(); + await Utils.sleep(500); - // Test - const callback = sinon.spy(); + // Assertions + callback.should.be.calledWith(null); + // Because of the way onUserChanged works, it will be called double + // - once for onAuthStateChanged + // - once for onIdTokenChanged + callback.should.have.callCount(4); - let unsubscribe; - await new Promise(resolve => { - unsubscribe = firebase.auth().onUserChanged(user => { - callback(user); - resolve(); - }); + // Tear down + unsubscribe(); }); - callback.should.be.calledWith(firebase.auth().currentUser); - callback.should.be.calledOnce(); - - // Sign out - await firebase.auth().signOut(); - await Utils.sleep(200); + it('stops listening when unsubscribed', async function () { + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - // Assertions - callback.should.be.calledWith(null); - // Because of the way onUserChanged works, it will be called double - // - once for onAuthStateChanged - // - once for onIdTokenChanged - callback.should.have.callCount(4); + // Test + const callback = sinon.spy(); - // Unsubscribe - unsubscribe(); - - // Sign back in - await firebase.auth().signInAnonymously(); - await Utils.sleep(200); + let unsubscribe; + await new Promise(resolve => { + unsubscribe = firebase.auth().onUserChanged(user => { + callback(user); + resolve(); + }); + }); - // Assertions - callback.should.have.callCount(4); + callback.should.be.calledWith(firebase.auth().currentUser); + callback.should.be.calledOnce(); - // Tear down - await firebase.auth().signOut(); - }); - }); + // Sign out + await firebase.auth().signOut(); + await Utils.sleep(200); - describe('signInAnonymously()', function () { - it('it should sign in anonymously', function () { - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - - return firebase.auth().signOut(); - }; - - return firebase.auth().signInAnonymously().then(successCb); - }); - }); + // Assertions + callback.should.be.calledWith(null); + // Because of the way onUserChanged works, it will be called double + // - once for onAuthStateChanged + // - once for onIdTokenChanged + callback.should.have.callCount(4); - describe('signInWithEmailAndPassword()', function () { - it('it should login with email and password', function () { - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql(TEST_EMAIL); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(false); - - return firebase.auth().signOut(); - }; - - return firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS).then(successCb); - }); + // Unsubscribe + unsubscribe(); - it('it should error on login if user is disabled', function () { - const successCb = () => Promise.reject(new Error('Did not error.')); + // Sign back in + await firebase.auth().signInAnonymously(); + await Utils.sleep(200); - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.containEql('The user account has been disabled by an administrator.'); - return Promise.resolve(); - }; + // Assertions + callback.should.have.callCount(4); - return firebase - .auth() - .signInWithEmailAndPassword(DISABLED_EMAIL, DISABLED_PASS) - .then(successCb) - .catch(failureCb); + // Tear down + await firebase.auth().signOut(); + }); }); - it('it should error on login if password incorrect', function () { - const successCb = () => Promise.reject(new Error('Did not error.')); - - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.containEql( - 'The password is invalid or the user does not have a password.', - ); - return Promise.resolve(); - }; + describe('signInAnonymously()', function () { + it('it should sign in anonymously', function () { + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + + return firebase.auth().signOut(); + }; - return firebase - .auth() - .signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS + '666') - .then(successCb) - .catch(failureCb); + return firebase.auth().signInAnonymously().then(successCb); + }); }); - it('it should error on login if user not found', function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = () => Promise.reject(new Error('Did not error.')); + describe('signInWithEmailAndPassword()', function () { + it('it should login with email and password', function () { + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.auth().signOut(); + }; - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.containEql( - 'There is no user record corresponding to this identifier. The user may have been deleted.', - ); - return Promise.resolve(); - }; + return firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS).then(successCb); + }); - return firebase - .auth() - .signInWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); + it('it should error on login if user is disabled', function () { + const successCb = () => Promise.reject(new Error('Did not error.')); - describe('signInWithCredential()', function () { - it('it should login with email and password', function () { - const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.containEql( + 'The user account has been disabled by an administrator.', + ); + return Promise.resolve(); + }; - const successCb = currentUserCredential => { - const currentUser = currentUserCredential.user; - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql(TEST_EMAIL); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); + return firebase + .auth() + .signInWithEmailAndPassword(DISABLED_EMAIL, DISABLED_PASS) + .then(successCb) + .catch(failureCb); + }); - const { additionalUserInfo } = currentUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(false); + it('it should error on login if password incorrect', function () { + const successCb = () => Promise.reject(new Error('Did not error.')); - return firebase.auth().signOut(); - }; + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.containEql( + 'The password is invalid or the user does not have a password.', + ); + return Promise.resolve(); + }; - return firebase.auth().signInWithCredential(credential).then(successCb); - }); + return firebase + .auth() + .signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS + '666') + .then(successCb) + .catch(failureCb); + }); - it('it should error on login if user is disabled', function () { - const credential = firebase.auth.EmailAuthProvider.credential(DISABLED_EMAIL, DISABLED_PASS); + it('it should error on login if user not found', function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; - const successCb = () => Promise.reject(new Error('Did not error.')); + const successCb = () => Promise.reject(new Error('Did not error.')); - const failureCb = error => { - error.code.should.equal('auth/user-disabled'); - error.message.should.containEql('The user account has been disabled by an administrator.'); - return Promise.resolve(); - }; + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.containEql( + 'There is no user record corresponding to this identifier. The user may have been deleted.', + ); + return Promise.resolve(); + }; - return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + return firebase + .auth() + .signInWithEmailAndPassword(email, pass) + .then(successCb) + .catch(failureCb); + }); }); - it('it should error on login if password incorrect', function () { - const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS + '666'); + describe('signInWithCredential()', function () { + it('it should login with email and password', function () { + const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return firebase.auth().signOut(); + }; - const successCb = () => Promise.reject(new Error('Did not error.')); + return firebase.auth().signInWithCredential(credential).then(successCb); + }); - const failureCb = error => { - error.code.should.equal('auth/wrong-password'); - error.message.should.containEql( - 'The password is invalid or the user does not have a password.', + it('it should error on login if user is disabled', function () { + const credential = firebase.auth.EmailAuthProvider.credential( + DISABLED_EMAIL, + DISABLED_PASS, ); - return Promise.resolve(); - }; - - return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); - }); - it('it should error on login if user not found', function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; + const successCb = () => Promise.reject(new Error('Did not error.')); - const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.containEql( + 'The user account has been disabled by an administrator.', + ); + return Promise.resolve(); + }; - const successCb = () => Promise.reject(new Error('Did not error.')); + return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); - const failureCb = error => { - error.code.should.equal('auth/user-not-found'); - error.message.should.containEql( - 'There is no user record corresponding to this identifier. The user may have been deleted.', + it('it should error on login if password incorrect', function () { + const credential = firebase.auth.EmailAuthProvider.credential( + TEST_EMAIL, + TEST_PASS + '666', ); - return Promise.resolve(); - }; - return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); - }); - }); - - describe('createUserWithEmailAndPassword()', function () { - it('it should create a user with an email and password', function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = newUserCredential => { - const newUser = newUserCredential.user; - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - newUser.should.equal(firebase.auth().currentUser); - const { additionalUserInfo } = newUserCredential; - additionalUserInfo.should.be.an.Object(); - additionalUserInfo.isNewUser.should.equal(true); - - return newUser.delete(); - }; - - return firebase.auth().createUserWithEmailAndPassword(email, pass).then(successCb); - }); + const successCb = () => Promise.reject(new Error('Did not error.')); - it('it should error on create with invalid email', function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}${random}.com.boop.shoop`; - const pass = random; + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.containEql( + 'The password is invalid or the user does not have a password.', + ); + return Promise.resolve(); + }; - const successCb = () => Promise.reject(new Error('Did not error.')); + return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); - const failureCb = error => { - error.code.should.equal('auth/invalid-email'); - error.message.should.containEql('The email address is badly formatted.'); - return Promise.resolve(); - }; + it('it should error on login if user not found', function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; - return firebase - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); - it('it should error on create if email in use', function () { - const successCb = () => Promise.reject(new Error('Did not error.')); + const successCb = () => Promise.reject(new Error('Did not error.')); - const failureCb = error => { - error.code.should.equal('auth/email-already-in-use'); - error.message.should.containEql('The email address is already in use by another account.'); - return Promise.resolve(); - }; + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.containEql( + 'There is no user record corresponding to this identifier. The user may have been deleted.', + ); + return Promise.resolve(); + }; - return firebase - .auth() - .createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS) - .then(successCb) - .catch(failureCb); + return firebase.auth().signInWithCredential(credential).then(successCb).catch(failureCb); + }); }); - it('it should error on create if password weak', function () { - const email = 'testy@testy.com'; - const pass = '123'; - - const successCb = () => Promise.reject(new Error('Did not error.')); + describe('createUserWithEmailAndPassword()', function () { + it('it should create a user with an email and password', function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = newUserCredential => { + const newUser = newUserCredential.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + newUser.should.equal(firebase.auth().currentUser); + const { additionalUserInfo } = newUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(true); + + return newUser.delete(); + }; - const failureCb = error => { - error.code.should.equal('auth/weak-password'); - // cannot test this message - it's different on the web client than ios/android return - // error.message.should.containEql('The given password is invalid.'); - return Promise.resolve(); - }; + return firebase.auth().createUserWithEmailAndPassword(email, pass).then(successCb); + }); - return firebase - .auth() - .createUserWithEmailAndPassword(email, pass) - .then(successCb) - .catch(failureCb); - }); - }); + it('it should error on create with invalid email', function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}${random}.com.boop.shoop`; + const pass = random; - describe('fetchSignInMethodsForEmail()', function () { - it('it should return password provider for an email address', function () { - return new Promise((resolve, reject) => { - const successCb = providers => { - providers.should.be.a.Array(); - providers.should.containEql('password'); - resolve(); - }; + const successCb = () => Promise.reject(new Error('Did not error.')); - const failureCb = () => { - reject(new Error('Should not have an error.')); + const failureCb = error => { + error.code.should.equal('auth/invalid-email'); + error.message.should.containEql('The email address is badly formatted.'); + return Promise.resolve(); }; return firebase .auth() - .fetchSignInMethodsForEmail(TEST_EMAIL) + .createUserWithEmailAndPassword(email, pass) .then(successCb) .catch(failureCb); }); - }); - it('it should return an empty array for a not found email', function () { - return new Promise((resolve, reject) => { - const successCb = providers => { - providers.should.be.a.Array(); - providers.should.be.empty(); - resolve(); - }; + it('it should error on create if email in use', function () { + const successCb = () => Promise.reject(new Error('Did not error.')); - const failureCb = () => { - reject(new Error('Should not have an error.')); + const failureCb = error => { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.containEql( + 'The email address is already in use by another account.', + ); + return Promise.resolve(); }; return firebase .auth() - .fetchSignInMethodsForEmail('test@i-do-not-exist.com') + .createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS) .then(successCb) .catch(failureCb); }); - }); - it('it should return an error for a bad email address', function () { - return new Promise((resolve, reject) => { - const successCb = () => { - reject(new Error('Should not have successfully resolved.')); - }; + it('it should error on create if password weak', function () { + const email = 'testy@testy.com'; + const pass = '123'; + + const successCb = () => Promise.reject(new Error('Did not error.')); const failureCb = error => { - error.code.should.equal('auth/invalid-email'); - error.message.should.containEql('The email address is badly formatted.'); - resolve(); + error.code.should.equal('auth/weak-password'); + // cannot test this message - it's different on the web client than ios/android return + // error.message.should.containEql('The given password is invalid.'); + return Promise.resolve(); }; return firebase .auth() - .fetchSignInMethodsForEmail('foobar') + .createUserWithEmailAndPassword(email, pass) .then(successCb) .catch(failureCb); }); }); - }); - describe('signOut()', function () { - it('it should reject signOut if no currentUser', function () { - return new Promise((resolve, reject) => { - if (firebase.auth().currentUser) { - return reject( - new Error(`A user is currently signed in. ${firebase.auth().currentUser.uid}`), - ); - } + describe('fetchSignInMethodsForEmail()', function () { + it('it should return password provider for an email address', function () { + return new Promise((resolve, reject) => { + const successCb = providers => { + providers.should.be.a.Array(); + providers.should.containEql('password'); + resolve(); + }; + + const failureCb = () => { + reject(new Error('Should not have an error.')); + }; + + return firebase + .auth() + .fetchSignInMethodsForEmail(TEST_EMAIL) + .then(successCb) + .catch(failureCb); + }); + }); - const successCb = () => { - reject(new Error('No signOut error returned')); - }; + it('it should return an empty array for a not found email', function () { + return new Promise((resolve, reject) => { + const successCb = providers => { + providers.should.be.a.Array(); + providers.should.be.empty(); + resolve(); + }; + + const failureCb = () => { + reject(new Error('Should not have an error.')); + }; + + return firebase + .auth() + .fetchSignInMethodsForEmail('test@i-do-not-exist.com') + .then(successCb) + .catch(failureCb); + }); + }); - const failureCb = error => { - error.code.should.equal('auth/no-current-user'); - error.message.should.containEql('No user currently signed in.'); - resolve(); - }; + it('it should return an error for a bad email address', function () { + return new Promise((resolve, reject) => { + const successCb = () => { + reject(new Error('Should not have successfully resolved.')); + }; + + const failureCb = error => { + error.code.should.equal('auth/invalid-email'); + error.message.should.containEql('The email address is badly formatted.'); + resolve(); + }; + + return firebase + .auth() + .fetchSignInMethodsForEmail('foobar') + .then(successCb) + .catch(failureCb); + }); + }); + }); - return firebase.auth().signOut().then(successCb).catch(failureCb); + describe('signOut()', function () { + it('it should reject signOut if no currentUser', function () { + return new Promise((resolve, reject) => { + if (firebase.auth().currentUser) { + return reject( + new Error(`A user is currently signed in. ${firebase.auth().currentUser.uid}`), + ); + } + + const successCb = () => { + reject(new Error('No signOut error returned')); + }; + + const failureCb = error => { + error.code.should.equal('auth/no-current-user'); + error.message.should.containEql('No user currently signed in.'); + resolve(); + }; + + return firebase.auth().signOut().then(successCb).catch(failureCb); + }); }); }); - }); - describe('delete()', function () { - it('should delete a user', function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - - const successCb = authResult => { - const newUser = authResult.user; - newUser.uid.should.be.a.String(); - newUser.email.should.equal(email.toLowerCase()); - newUser.emailVerified.should.equal(false); - newUser.isAnonymous.should.equal(false); - newUser.providerId.should.equal('firebase'); - return firebase.auth().currentUser.delete(); - }; - - return firebase.auth().createUserWithEmailAndPassword(email, pass).then(successCb); + describe('delete()', function () { + it('should delete a user', function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = authResult => { + const newUser = authResult.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + return firebase.auth().currentUser.delete(); + }; + + return firebase.auth().createUserWithEmailAndPassword(email, pass).then(successCb); + }); }); - }); - describe('languageCode', function () { - it('it should change the language code', async function () { - await firebase.auth().setLanguageCode('en'); + describe('languageCode', function () { + it('it should change the language code', async function () { + await firebase.auth().setLanguageCode('en'); - if (firebase.auth().languageCode !== 'en') { - throw new Error('Expected language code to be "en".'); - } - await firebase.auth().setLanguageCode('fr'); + if (firebase.auth().languageCode !== 'en') { + throw new Error('Expected language code to be "en".'); + } + await firebase.auth().setLanguageCode('fr'); - if (firebase.auth().languageCode !== 'fr') { - throw new Error('Expected language code to be "fr".'); - } - // expect no error - await firebase.auth().setLanguageCode(null); - - try { - await firebase.auth().setLanguageCode(123); - return Promise.reject('It did not error'); - } catch (e) { - e.message.should.containEql("expected 'languageCode' to be a string or null value"); - } + if (firebase.auth().languageCode !== 'fr') { + throw new Error('Expected language code to be "fr".'); + } + // expect no error + await firebase.auth().setLanguageCode(null); - await firebase.auth().setLanguageCode('en'); - }); - }); + try { + await firebase.auth().setLanguageCode(123); + return Promise.reject('It did not error'); + } catch (e) { + e.message.should.containEql("expected 'languageCode' to be a string or null value"); + } - describe('getRedirectResult()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().getRedirectResult(); - }).should.throw( - 'firebase.auth().getRedirectResult() is unsupported by the native Firebase SDKs.', - ); + await firebase.auth().setLanguageCode('en'); + }); }); - }); - describe('setPersistence()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().setPersistence(); - }).should.throw( - 'firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.', - ); + describe('getRedirectResult()', function () { + it('should throw an unsupported error', function () { + (() => { + firebase.auth().getRedirectResult(); + }).should.throw( + 'firebase.auth().getRedirectResult() is unsupported by the native Firebase SDKs.', + ); + }); }); - }); - describe('signInWithPopup', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithPopup(); - }).should.throw( - 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', - ); + describe('setPersistence()', function () { + it('should throw an unsupported error', function () { + (() => { + firebase.auth().setPersistence(); + }).should.throw( + 'firebase.auth().setPersistence() is unsupported by the native Firebase SDKs.', + ); + }); }); - }); - describe('sendPasswordResetEmail()', function () { - it('should not error', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - - try { - await firebase.auth().sendPasswordResetEmail(email); - } catch (error) { - throw new Error('sendPasswordResetEmail() caused an error', error); - } finally { - await firebase.auth().currentUser.delete(); - } + describe('signInWithPopup', function () { + it('should throw an unsupported error', function () { + (() => { + firebase.auth().signInWithPopup(); + }).should.throw( + 'firebase.auth().signInWithPopup() is unsupported by the native Firebase SDKs.', + ); + }); }); - it('should verify with valid code', async function () { - // FIXME Fails on android against auth emulator with: - // com.google.firebase.FirebaseException: An internal error has occurred. - if (device.getPlatform() === 'ios') { - const random = Utils.randString(12, '#a'); + describe('sendPasswordResetEmail()', function () { + it('should not error', async function () { + const random = Utils.randString(12, '#aA'); const email = `${random}@${random}.com`; - const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); - userCredential.user.emailVerified.should.equal(false); - firebase.auth().currentUser.email.should.equal(email); - firebase.auth().currentUser.emailVerified.should.equal(false); + await firebase.auth().createUserWithEmailAndPassword(email, random); try { await firebase.auth().sendPasswordResetEmail(email); - const { oobCode } = await getLastOob(email); - await firebase.auth().verifyPasswordResetCode(oobCode); } catch (error) { throw new Error('sendPasswordResetEmail() caused an error', error); } finally { await firebase.auth().currentUser.delete(); } - } - }); - - it('should fail to verify with invalid code', async function () { - const random = Utils.randString(12, '#a'); - const email = `${random}@${random}.com`; - const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); - userCredential.user.emailVerified.should.equal(false); - firebase.auth().currentUser.email.should.equal(email); - firebase.auth().currentUser.emailVerified.should.equal(false); - - try { - await firebase.auth().sendPasswordResetEmail(email); - const { oobCode } = await getLastOob(email); - await firebase.auth().verifyPasswordResetCode(oobCode + 'badcode'); - throw new Error('Invalid code should throw an error'); - } catch (error) { - error.message.should.containEql('[auth/invalid-action-code]'); - } finally { - await firebase.auth().currentUser.delete(); - } - }); + }); - it('should change password correctly OOB', async function () { - const random = Utils.randString(12, '#a'); - const email = `${random}@${random}.com`; - const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); - userCredential.user.emailVerified.should.equal(false); - firebase.auth().currentUser.email.should.equal(email); - firebase.auth().currentUser.emailVerified.should.equal(false); - - try { - await firebase.auth().sendPasswordResetEmail(email); - const { oobCode } = await getLastOob(email); - await resetPassword(oobCode, 'testNewPassword'); - await firebase.auth().signOut(); - await Utils.sleep(50); - await firebase.auth().signInWithEmailAndPassword(email, 'testNewPassword'); - } catch (error) { - throw new Error('sendPasswordResetEmail() caused an error', error); - } finally { - await firebase.auth().currentUser.delete(); - } + it('should verify with valid code', async function () { + // FIXME Fails on android against auth emulator with: + // com.google.firebase.FirebaseException: An internal error has occurred. + if (device.getPlatform() === 'ios') { + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const userCredential = await firebase + .auth() + .createUserWithEmailAndPassword(email, random); + userCredential.user.emailVerified.should.equal(false); + firebase.auth().currentUser.email.should.equal(email); + firebase.auth().currentUser.emailVerified.should.equal(false); + + try { + await firebase.auth().sendPasswordResetEmail(email); + const { oobCode } = await getLastOob(email); + await firebase.auth().verifyPasswordResetCode(oobCode); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await firebase.auth().currentUser.delete(); + } + } + }); + + it('should fail to verify with invalid code', async function () { + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); + userCredential.user.emailVerified.should.equal(false); + firebase.auth().currentUser.email.should.equal(email); + firebase.auth().currentUser.emailVerified.should.equal(false); + + try { + await firebase.auth().sendPasswordResetEmail(email); + const { oobCode } = await getLastOob(email); + await firebase.auth().verifyPasswordResetCode(oobCode + 'badcode'); + throw new Error('Invalid code should throw an error'); + } catch (error) { + error.message.should.containEql('[auth/invalid-action-code]'); + } finally { + await firebase.auth().currentUser.delete(); + } + }); + + it('should change password correctly OOB', async function () { + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); + userCredential.user.emailVerified.should.equal(false); + firebase.auth().currentUser.email.should.equal(email); + firebase.auth().currentUser.emailVerified.should.equal(false); + + try { + await firebase.auth().sendPasswordResetEmail(email); + const { oobCode } = await getLastOob(email); + await resetPassword(oobCode, 'testNewPassword'); + await firebase.auth().signOut(); + await Utils.sleep(50); + await firebase.auth().signInWithEmailAndPassword(email, 'testNewPassword'); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await firebase.auth().currentUser.delete(); + } + }); + + it('should change password correctly via API', async function () { + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); + userCredential.user.emailVerified.should.equal(false); + firebase.auth().currentUser.email.should.equal(email); + firebase.auth().currentUser.emailVerified.should.equal(false); + + try { + await firebase.auth().sendPasswordResetEmail(email); + const { oobCode } = await getLastOob(email); + await firebase.auth().confirmPasswordReset(oobCode, 'testNewPassword'); + await firebase.auth().signOut(); + await Utils.sleep(50); + await firebase.auth().signInWithEmailAndPassword(email, 'testNewPassword'); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await firebase.auth().currentUser.delete(); + } + }); }); - it('should change password correctly via API', async function () { - const random = Utils.randString(12, '#a'); - const email = `${random}@${random}.com`; - const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); - userCredential.user.emailVerified.should.equal(false); - firebase.auth().currentUser.email.should.equal(email); - firebase.auth().currentUser.emailVerified.should.equal(false); - - try { - await firebase.auth().sendPasswordResetEmail(email); - const { oobCode } = await getLastOob(email); - await firebase.auth().confirmPasswordReset(oobCode, 'testNewPassword'); - await firebase.auth().signOut(); - await Utils.sleep(50); - await firebase.auth().signInWithEmailAndPassword(email, 'testNewPassword'); - } catch (error) { - throw new Error('sendPasswordResetEmail() caused an error', error); - } finally { - await firebase.auth().currentUser.delete(); - } + describe('signInWithRedirect()', function () { + it('should throw an unsupported error', function () { + (() => { + firebase.auth().signInWithRedirect(); + }).should.throw( + 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', + ); + }); }); - }); - describe('signInWithRedirect()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().signInWithRedirect(); - }).should.throw( - 'firebase.auth().signInWithRedirect() is unsupported by the native Firebase SDKs.', - ); + describe('useDeviceLanguage()', function () { + it('should throw an unsupported error', function () { + (() => { + firebase.auth().useDeviceLanguage(); + }).should.throw( + 'firebase.auth().useDeviceLanguage() is unsupported by the native Firebase SDKs.', + ); + }); + }); + + describe('useUserAccessGroup()', function () { + // Android simply does Promise.resolve, that is sufficient for this test multi-platform + it('should return "null" when accessing a group that exists', async function () { + const successfulKeychain = await firebase + .auth() + .useUserAccessGroup('YYX2P3XVJ7.com.invertase.testing'); // iOS signing team is YYX2P3XVJ7 + + should.not.exist(successfulKeychain); + + //clean up + const resetKeychain = await firebase.auth().useUserAccessGroup(null); + + should.not.exist(resetKeychain); + }); + + it('should throw when requesting an inaccessible group', async function () { + // Android will never throw, so this test is iOS only + if (device.getPlatform() === 'ios') { + try { + await firebase.auth().useUserAccessGroup('there.is.no.way.this.group.exists'); + throw new Error('Should have thrown an error for inaccessible group'); + } catch (e) { + e.message.should.containEql('auth/keychain-error'); + } + } + }); + }); + + describe('setTenantId()', function () { + it('should return null if tenantId unset', function () { + should.not.exist(firebase.auth().tenantId); + }); + + // multi-tenant is not supported by the firebase auth emulator, and requires a valid multi-tenant tenantid + // After setting this, next user creation will result in internal error on emulator, or auth/invalid-tenant-id live + // it('should return tenantId correctly after setting', async function () { + // await firebase.auth().setTenantId('testTenantId'); + // firebase.auth().tenantId.should.equal('testTenantId'); + // }); + // it('user should have tenant after setting tenantId', async function () { + // await firebase.auth().setTenantId('userTestTenantId'); + // firebase.auth().tenantId.should.equal('userTestTenantId'); + // const random = Utils.randString(12, '#a'); + // const email = `${random}@${random}.com`; + // const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); + // userCredential.user.tenantId.should.equal('userTestTenantId'); + // }); }); }); - describe('useDeviceLanguage()', function () { - it('should throw an unsupported error', function () { - (() => { - firebase.auth().useDeviceLanguage(); - }).should.throw( - 'firebase.auth().useDeviceLanguage() is unsupported by the native Firebase SDKs.', + describe('modular', function () { + before(async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + await clearAllUsers(); + await createUserWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + const disabledUserCredential = await createUserWithEmailAndPassword( + defaultAuth, + DISABLED_EMAIL, + DISABLED_PASS, ); + await disableUser(disabledUserCredential.user.uid); }); - }); - describe('useUserAccessGroup()', function () { - // Android simply does Promise.resolve, that is sufficient for this test multi-platform - it('should return "null" when accessing a group that exists', async function () { - const successfulKeychain = await firebase - .auth() - .useUserAccessGroup('YYX2P3XVJ7.com.invertase.testing'); // iOS signing team is YYX2P3XVJ7 + beforeEach(async function () { + const { signOut, getAuth } = authModular; + + const defaultAuth = getAuth(firebase.app()); + if (defaultAuth.currentUser) { + await signOut(defaultAuth); + await Utils.sleep(50); + } + }); + + describe('namespace', function () { + it('accessible from firebase.app()', function () { + const { getAuth } = authModular; - should.not.exist(successfulKeychain); + const app = firebase.app(); + const auth = getAuth(app); + should.exist(auth); + auth.app.should.equal(app); + }); + + // removing as pending if module.options.hasMultiAppSupport = true + it('supports multiple apps', async function () { + const { getAuth } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + defaultAuth.app.name.should.equal('[DEFAULT]'); - //clean up - const resetKeychain = await firebase.auth().useUserAccessGroup(null); + const secondaryApp = firebase.app('secondaryFromNative'); + const secondaryAuth = getAuth(secondaryApp); + secondaryAuth.app.name.should.equal('secondaryFromNative'); - should.not.exist(resetKeychain); + secondaryApp.auth().app.name.should.equal('secondaryFromNative'); + }); + }); + describe('applyActionCode()', function () { + // Needs a different setup to work against the auth emulator + xit('works as expected', async function () { + const { applyActionCode, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + await applyActionCode(defaultAuth, 'fooby shooby dooby').then($ => $); + }); + it('errors on invalid code', async function () { + const { applyActionCode, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + try { + await applyActionCode(defaultAuth, 'fooby shooby dooby').then($ => $); + } catch (e) { + e.message.should.containEql('code is invalid'); + } + }); }); - it('should throw when requesting an inaccessible group', async function () { - // Android will never throw, so this test is iOS only - if (device.getPlatform() === 'ios') { + describe('checkActionCode()', function () { + it('errors on invalid code', async function () { + const { checkActionCode, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); try { - await firebase.auth().useUserAccessGroup('there.is.no.way.this.group.exists'); - throw new Error('Should have thrown an error for inaccessible group'); + await checkActionCode(defaultAuth, 'fooby shooby dooby'); } catch (e) { - e.message.should.containEql('auth/keychain-error'); + e.message.should.containEql('code is invalid'); } - } + }); }); - }); - describe('setTenantId()', function () { - it('should return null if tenantId unset', function () { - should.not.exist(firebase.auth().tenantId); + describe('reload()', function () { + it('Meta data returns as expected with anonymous sign in', async function () { + const { signInAnonymously, signOut, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(50); + const firstUser = defaultAuth.currentUser; + await firstUser.reload(); + + await signOut(defaultAuth); + + await signInAnonymously(defaultAuth); + await Utils.sleep(50); + const secondUser = defaultAuth.currentUser; + await secondUser.reload(); + + firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); + }); + + it('Meta data returns as expected with email and password sign in', async function () { + const { createUserWithEmailAndPassword, signOut, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#aA'); + const email1 = `${random}@${random}.com`; + const pass = random; + + await createUserWithEmailAndPassword(defaultAuth, email1, pass); + const firstUser = defaultAuth.currentUser; + await firstUser.reload(); + + await signOut(defaultAuth); + + const anotherRandom = Utils.randString(12, '#aA'); + const email2 = `${anotherRandom}@${anotherRandom}.com`; + + await createUserWithEmailAndPassword(defaultAuth, email2, pass); + const secondUser = defaultAuth.currentUser; + await secondUser.reload(); + + firstUser.metadata.creationTime.should.not.equal(secondUser.metadata.creationTime); + }); + }); + + describe('confirmPasswordReset()', function () { + it('errors on invalid code via API', async function () { + const { confirmPasswordReset, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + await confirmPasswordReset(defaultAuth, 'fooby shooby dooby', 'passwordthing'); + } catch (e) { + e.message.should.containEql('code is invalid'); + } + }); + }); + + describe('signInWithCustomToken()', function () { + // Needs a different setup when running against the emulator + xit('signs in with an admin SDK created custom auth token', async function () { + const { signInWithEmailAndPassword, signOut, signInWithCustomToken, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return currentUser; + }; + + const user = await signInWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS).then( + successCb, + ); + + const IdToken = await defaultAuth.currentUser.getIdToken(); + + await signOut(defaultAuth); + await Utils.sleep(50); + + const token = await new TestAdminApi(IdToken).auth().createCustomToken(user.uid, {}); + + await signInWithCustomToken(defaultAuth, token); + + defaultAuth.currentUser.email.should.equal(TEST_EMAIL); + }); + }); + + describe('onAuthStateChanged()', function () { + it('calls callback with the current user and when auth state changes', async function () { + const { signInAnonymously, signOut, onAuthStateChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(50); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = onAuthStateChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(defaultAuth.currentUser); + callback.should.be.calledOnce(); + + // Sign out + await signOut(defaultAuth); + + // Assertions + await Utils.sleep(50); + + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Tear down + unsubscribe(); + }); + + it('accepts observer instead of callback as well', async function () { + const { signInAnonymously, signOut, onAuthStateChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(200); + + // Test + const observer = { + next(user) { + // Test this access + this.onNext(); + this.user = user; + }, + }; + + let unsubscribe; + await new Promise(resolve => { + observer.onNext = resolve; + unsubscribe = onAuthStateChanged(defaultAuth, observer); + }); + should.exist(observer.user); + + // Sign out + await signOut(defaultAuth); + + // Assertions + await Utils.sleep(50); + + should.not.exist(observer.user); + + // Tear down + unsubscribe(); + }); + + it('stops listening when unsubscribed', async function () { + const { signInAnonymously, signOut, onAuthStateChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(200); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = onAuthStateChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(defaultAuth.currentUser); + callback.should.be.calledOnce(); + + // Sign out + await signOut(defaultAuth); + await Utils.sleep(50); + + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Unsubscribe + unsubscribe(); + + // Sign back in + await signInAnonymously(defaultAuth); + + // Assertions + callback.should.be.calledTwice(); + + // Tear down + await signOut(defaultAuth); + await Utils.sleep(50); + }); + + it('returns the same user with multiple subscribers #1815', async function () { + const { signInAnonymously, signOut, onAuthStateChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const callback = sinon.spy(); + + let unsubscribe1; + let unsubscribe2; + let unsubscribe3; + + await new Promise(resolve => { + unsubscribe1 = onAuthStateChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + await new Promise(resolve => { + unsubscribe2 = onAuthStateChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + await new Promise(resolve => { + unsubscribe3 = onAuthStateChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledThrice(); + callback.should.be.calledWith(null); + + await signInAnonymously(defaultAuth); + await Utils.sleep(800); + + unsubscribe1(); + unsubscribe2(); + unsubscribe3(); + + callback.should.be.callCount(6); + + const uid = callback.getCall(3).args[0].uid; + + callback.getCall(4).args[0].uid.should.eql(uid); + callback.getCall(5).args[0].uid.should.eql(uid); + + await signOut(defaultAuth); + await Utils.sleep(50); + }); + }); + + describe('onIdTokenChanged()', function () { + it('calls callback with the current user and when auth state changes', async function () { + const { signInAnonymously, signOut, onIdTokenChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(200); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = onIdTokenChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(defaultAuth.currentUser); + callback.should.be.calledOnce(); + + // Sign out + await signOut(defaultAuth); + await Utils.sleep(50); + + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Tear down + unsubscribe(); + }); + + it('stops listening when unsubscribed', async function () { + const { signInAnonymously, signOut, onIdTokenChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + await signInAnonymously(defaultAuth); + await Utils.sleep(200); + + // Test + const callback = sinon.spy(); + + let unsubscribe; + await new Promise(resolve => { + unsubscribe = onIdTokenChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + }); + + callback.should.be.calledWith(defaultAuth.currentUser); + callback.should.be.calledOnce(); + + // Sign out + await signOut(defaultAuth); + await Utils.sleep(50); + + // Assertions + callback.should.be.calledWith(null); + callback.should.be.calledTwice(); + + // Unsubscribe + unsubscribe(); + + // Sign back in + await signInAnonymously(defaultAuth); + + // Assertions + callback.should.be.calledTwice(); + + // Tear down + await signOut(defaultAuth); + await Utils.sleep(50); + }); + + it('listens to a null user when auth result is not defined', async function () { + const { onIdTokenChanged, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + let unsubscribe; + + const callback = sinon.spy(); + + await new Promise(resolve => { + unsubscribe = onIdTokenChanged(defaultAuth, user => { + callback(user); + resolve(); + }); + + unsubscribe(); + }); + }); + }); + + describe('signInAnonymously()', function () { + it('it should sign in anonymously', async function () { + const { signInAnonymously, signOut, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + + return signOut(defaultAuth); + }; + + const currentUserCredential = await signInAnonymously(defaultAuth); + await successCb(currentUserCredential); + }); }); - // multi-tenant is not supported by the firebase auth emulator, and requires a valid multi-tenant tenantid - // After setting this, next user creation will result in internal error on emulator, or auth/invalid-tenant-id live - // it('should return tenantId correctly after setting', async function () { - // await firebase.auth().setTenantId('testTenantId'); - // firebase.auth().tenantId.should.equal('testTenantId'); - // }); - // it('user should have tenant after setting tenantId', async function () { - // await firebase.auth().setTenantId('userTestTenantId'); - // firebase.auth().tenantId.should.equal('userTestTenantId'); - // const random = Utils.randString(12, '#a'); - // const email = `${random}@${random}.com`; - // const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); - // userCredential.user.tenantId.should.equal('userTestTenantId'); - // }); + describe('signInWithEmailAndPassword()', function () { + it('it should login with email and password', async function () { + const { signInWithEmailAndPassword, signOut, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return signOut(defaultAuth); + }; + + const currentUserCredential = await signInWithEmailAndPassword( + defaultAuth, + TEST_EMAIL, + TEST_PASS, + ); + await successCb(currentUserCredential); + }); + + it('it should error on login if user is disabled', async function () { + const { signInWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.containEql( + 'The user account has been disabled by an administrator.', + ); + return Promise.resolve(); + }; + + try { + await signInWithEmailAndPassword(defaultAuth, DISABLED_EMAIL, DISABLED_PASS); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + + it('it should error on login if password incorrect', async function () { + const { signInWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.containEql( + 'The password is invalid or the user does not have a password.', + ); + return Promise.resolve(); + }; + + try { + await signInWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS + '666'); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + + it('it should error on login if user not found', async function () { + const { signInWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.containEql( + 'There is no user record corresponding to this identifier. The user may have been deleted.', + ); + return Promise.resolve(); + }; + + try { + await signInWithEmailAndPassword(defaultAuth, email, pass); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + }); + + describe('signInWithCredential()', function () { + it('it should login with email and password', async function () { + const { signInWithCredential, getAuth, signOut } = authModular; + const defaultAuth = getAuth(firebase.app()); + const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); + + const successCb = currentUserCredential => { + const currentUser = currentUserCredential.user; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(getAuth().currentUser); + + const { additionalUserInfo } = currentUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(false); + + return signOut(defaultAuth); + }; + + const userCredential = await signInWithCredential(defaultAuth, credential); + await successCb(userCredential); + }); + + it('it should error on login if user is disabled', async function () { + const { signInWithCredential, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + const credential = firebase.auth.EmailAuthProvider.credential( + DISABLED_EMAIL, + DISABLED_PASS, + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-disabled'); + error.message.should.containEql( + 'The user account has been disabled by an administrator.', + ); + return Promise.resolve(); + }; + + try { + await signInWithCredential(defaultAuth, credential); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + + it('it should error on login if password incorrect', async function () { + const { signInWithCredential, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + const credential = firebase.auth.EmailAuthProvider.credential( + TEST_EMAIL, + TEST_PASS + '666', + ); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/wrong-password'); + error.message.should.containEql( + 'The password is invalid or the user does not have a password.', + ); + return Promise.resolve(); + }; + + try { + await signInWithCredential(defaultAuth, credential); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + + it('it should error on login if user not found', async function () { + const { signInWithCredential, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + + const successCb = () => Promise.reject(new Error('Did not error.')); + + const failureCb = error => { + error.code.should.equal('auth/user-not-found'); + error.message.should.containEql( + 'There is no user record corresponding to this identifier. The user may have been deleted.', + ); + return Promise.resolve(); + }; + + try { + await signInWithCredential(defaultAuth, credential); + await successCb(); + } catch (error) { + await failureCb(error); + } + }); + + describe('createUserWithEmailAndPassword()', function () { + it('it should create a user with an email and password', async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + try { + const newUserCredential = await createUserWithEmailAndPassword( + defaultAuth, + email, + pass, + ); + const newUser = newUserCredential.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + newUser.should.equal(defaultAuth.currentUser); + const { additionalUserInfo } = newUserCredential; + additionalUserInfo.should.be.an.Object(); + additionalUserInfo.isNewUser.should.equal(true); + + await newUser.delete(); + } catch (error) { + throw error; + } + }); + + it('it should error on create with invalid email', async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const random = Utils.randString(12, '#aA'); + const email = `${random}${random}.com.boop.shoop`; + const pass = random; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, pass); + throw new Error('Did not error.'); + } catch (error) { + error.code.should.equal('auth/invalid-email'); + error.message.should.containEql('The email address is badly formatted.'); + } + }); + + it('it should error on create if email in use', async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + await createUserWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + throw new Error('Did not error.'); + } catch (error) { + error.code.should.equal('auth/email-already-in-use'); + error.message.should.containEql( + 'The email address is already in use by another account.', + ); + } + }); + + it('it should error on create if password weak', async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const email = 'testy@testy.com'; + const pass = '123'; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, pass); + throw new Error('Did not error.'); + } catch (error) { + error.code.should.equal('auth/weak-password'); + // cannot test this message - it's different on the web client than ios/android return + // error.message.should.containEql('The given password is invalid.'); + } + }); + }); + + describe('fetchSignInMethodsForEmail()', function () { + it('it should return password provider for an email address', async function () { + const { fetchSignInMethodsForEmail, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + const providers = await fetchSignInMethodsForEmail(defaultAuth, TEST_EMAIL); + providers.should.be.a.Array(); + providers.should.containEql('password'); + } catch (error) { + throw new Error('Should not have an error.'); + } + }); + + it('it should return an empty array for a not found email', async function () { + const { fetchSignInMethodsForEmail, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + const providers = await fetchSignInMethodsForEmail( + defaultAuth, + 'test@i-do-not-exist.com', + ); + providers.should.be.a.Array(); + providers.should.be.empty(); + } catch (error) { + throw new Error('Should not have an error.'); + } + }); + + it('it should return an error for a bad email address', async function () { + const { fetchSignInMethodsForEmail, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + await fetchSignInMethodsForEmail(defaultAuth, 'foobar'); + throw new Error('Should not have successfully resolved.'); + } catch (error) { + error.code.should.equal('auth/invalid-email'); + error.message.should.containEql('The email address is badly formatted.'); + } + }); + }); + + describe('signOut()', function () { + it('it should reject signOut if no currentUser', async function () { + const { signOut, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + if (defaultAuth.currentUser) { + throw new Error(`A user is currently signed in. ${defaultAuth.currentUser.uid}`); + } + + try { + await signOut(defaultAuth); + throw new Error('No signOut error returned'); + } catch (error) { + error.code.should.equal('auth/no-current-user'); + error.message.should.containEql('No user currently signed in.'); + } + }); + }); + + describe('delete()', function () { + it('should delete a user', async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + try { + const authResult = await createUserWithEmailAndPassword(defaultAuth, email, pass); + const newUser = authResult.user; + newUser.uid.should.be.a.String(); + newUser.email.should.equal(email.toLowerCase()); + newUser.emailVerified.should.equal(false); + newUser.isAnonymous.should.equal(false); + newUser.providerId.should.equal('firebase'); + await defaultAuth.currentUser.delete(); + } catch (error) { + throw error; + } + }); + }); + + describe('languageCode', function () { + it('it should change the language code', async function () { + const { getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + defaultAuth.languageCode = 'en'; + + if (defaultAuth.languageCode !== 'en') { + throw new Error('Expected language code to be "en".'); + } + defaultAuth.languageCode = 'fr'; + + if (defaultAuth.languageCode !== 'fr') { + throw new Error('Expected language code to be "fr".'); + } + // expect no error + defaultAuth.languageCode = null; + + try { + defaultAuth.languageCode = 123; + throw new Error('It did not error'); + } catch (e) { + e.message.should.containEql("expected 'languageCode' to be a string or null value"); + } + + defaultAuth.languageCode = 'en'; + }); + }); + + describe('getRedirectResult()', function () { + it('should throw an unsupported error', function () { + const { getRedirectResult, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + getRedirectResult(defaultAuth); + } catch (error) { + error.message.should.containEql( + 'getRedirectResult is unsupported by the native Firebase SDKs.', + ); + } + }); + }); + + describe('setPersistence()', function () { + it('should throw an unsupported error', function () { + const { setPersistence, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + setPersistence(defaultAuth); + } catch (error) { + error.message.should.containEql( + 'setPersistence is unsupported by the native Firebase SDKs.', + ); + } + }); + }); + + describe('signInWithPopup', function () { + it('should throw an unsupported error', function () { + const { signInWithPopup, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + signInWithPopup(defaultAuth); + } catch (error) { + error.message.should.containEql( + 'signInWithPopup is unsupported by the native Firebase SDKs.', + ); + } + }); + }); + + describe('sendPasswordResetEmail()', function () { + it('should not error', async function () { + const { createUserWithEmailAndPassword, sendPasswordResetEmail, getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, random); + await sendPasswordResetEmail(defaultAuth, email); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await defaultAuth.currentUser.delete(); + } + }); + + it('should verify with valid code', async function () { + // FIXME Fails on android against auth emulator with: + // com.google.firebase.FirebaseException: An internal error has occurred. + if (device.getPlatform() === 'ios') { + const { + createUserWithEmailAndPassword, + sendPasswordResetEmail, + getAuth, + verifyPasswordResetCode, + } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, random); + await sendPasswordResetEmail(defaultAuth, email); + const { oobCode } = await getLastOob(email); + await verifyPasswordResetCode(defaultAuth, oobCode); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await defaultAuth.currentUser.delete(); + } + } + }); + + it('should fail to verify with invalid code', async function () { + const { + createUserWithEmailAndPassword, + sendPasswordResetEmail, + getAuth, + verifyPasswordResetCode, + } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, random); + await sendPasswordResetEmail(defaultAuth, email); + const { oobCode } = await getLastOob(email); + await verifyPasswordResetCode(defaultAuth, oobCode + 'badcode'); + throw new Error('Invalid code should throw an error'); + } catch (error) { + error.message.should.containEql('[auth/invalid-action-code]'); + } finally { + await defaultAuth.currentUser.delete(); + } + }); + + it('should change password correctly OOB', async function () { + const { + createUserWithEmailAndPassword, + sendPasswordResetEmail, + getAuth, + signOut, + signInWithEmailAndPassword, + } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, random); + await sendPasswordResetEmail(defaultAuth, email); + const { oobCode } = await getLastOob(email); + await resetPassword(oobCode, 'testNewPassword'); + await signOut(defaultAuth); + await Utils.sleep(50); + await signInWithEmailAndPassword(defaultAuth, email, 'testNewPassword'); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await defaultAuth.currentUser.delete(); + } + }); + + it('should change password correctly via API', async function () { + const { + createUserWithEmailAndPassword, + sendPasswordResetEmail, + getAuth, + confirmPasswordReset, + signOut, + signInWithEmailAndPassword, + } = authModular; + const defaultAuth = getAuth(firebase.app()); + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + + try { + await createUserWithEmailAndPassword(defaultAuth, email, random); + await sendPasswordResetEmail(defaultAuth, email); + const { oobCode } = await getLastOob(email); + await confirmPasswordReset(defaultAuth, oobCode, 'testNewPassword'); + await signOut(defaultAuth); + await Utils.sleep(50); + await signInWithEmailAndPassword(defaultAuth, email, 'testNewPassword'); + } catch (error) { + throw new Error('sendPasswordResetEmail() caused an error', error); + } finally { + await defaultAuth.currentUser.delete(); + } + }); + }); + + describe('signInWithRedirect()', function () { + it('should throw an unsupported error', function () { + const { getAuth, signInWithRedirect } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + signInWithRedirect(defaultAuth); + } catch (error) { + error.message.should.containEql( + 'signInWithRedirect is unsupported by the native Firebase SDKs.', + ); + } + }); + }); + + describe('useDeviceLanguage()', function () { + it('should throw an unsupported error', function () { + const { getAuth, useDeviceLanguage } = authModular; + const defaultAuth = getAuth(firebase.app()); + + try { + useDeviceLanguage(defaultAuth); + } catch (error) { + error.message.should.containEql( + 'useDeviceLanguage is unsupported by the native Firebase SDKs', + ); + } + }); + }); + + describe('useUserAccessGroup()', function () { + // Android simply does Promise.resolve, that is sufficient for this test multi-platform + it('should return "null" when accessing a group that exists', async function () { + const { getAuth, useUserAccessGroup } = authModular; + const defaultAuth = getAuth(firebase.app()); + + const successfulKeychain = await useUserAccessGroup( + defaultAuth, + 'YYX2P3XVJ7.com.invertase.testing', + ); // iOS signing team is YYX2P3XVJ7 + + should.not.exist(successfulKeychain); + + //clean up + const resetKeychain = await useUserAccessGroup(defaultAuth, null); + + should.not.exist(resetKeychain); + }); + + it('should throw when requesting an inaccessible group', async function () { + const { getAuth, useUserAccessGroup } = authModular; + const defaultAuth = getAuth(firebase.app()); + + // Android will never throw, so this test is iOS only + if (device.getPlatform() === 'ios') { + try { + await useUserAccessGroup(defaultAuth, 'there.is.no.way.this.group.exists'); + throw new Error('Should have thrown an error for inaccessible group'); + } catch (e) { + e.message.should.containEql('auth/keychain-error'); + } + } + }); + }); + + describe('setTenantId()', function () { + it('should return null if tenantId unset', function () { + const { getAuth } = authModular; + const defaultAuth = getAuth(firebase.app()); + + should.not.exist(defaultAuth.tenantId); + }); + + // multi-tenant is not supported by the firebase auth emulator, and requires a valid multi-tenant tenantid + // After setting this, next user creation will result in internal error on emulator, or auth/invalid-tenant-id live + // it('should return tenantId correctly after setting', async function () { + // await firebase.auth().setTenantId('testTenantId'); + // firebase.auth().tenantId.should.equal('testTenantId'); + // }); + // it('user should have tenant after setting tenantId', async function () { + // await firebase.auth().setTenantId('userTestTenantId'); + // firebase.auth().tenantId.should.equal('userTestTenantId'); + // const random = Utils.randString(12, '#a'); + // const email = `${random}@${random}.com`; + // const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, random); + // userCredential.user.tenantId.should.equal('userTestTenantId'); + // }); + }); + }); }); }); diff --git a/packages/auth/e2e/emailLink.e2e.js b/packages/auth/e2e/emailLink.e2e.js index 812c5d37e4..5c8503a1fc 100644 --- a/packages/auth/e2e/emailLink.e2e.js +++ b/packages/auth/e2e/emailLink.e2e.js @@ -2,17 +2,22 @@ const { getLastOob, signInUser } = require('./helpers'); describe('auth() -> emailLink Provider', function () { beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); + const { getAuth, signOut } = authModular; + + const auth = getAuth(); + if (auth.currentUser) { + await signOut(auth); await Utils.sleep(50); } }); describe('sendSignInLinkToEmail', function () { it('should send email', async function () { + const { getAuth, sendSignInLinkToEmail } = authModular; + + const auth = getAuth(); const random = Utils.randString(12, '#aA'); const email = `${random}@${random}.com`; - // const email = 'MANUAL TEST EMAIL HERE'; const actionCodeSettings = { url: 'http://localhost:1337/authLinkFoo?bar=1234', handleCodeInApp: true, @@ -25,10 +30,13 @@ describe('auth() -> emailLink Provider', function () { minimumVersion: '12', }, }; - await firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings); + await sendSignInLinkToEmail(auth, email, actionCodeSettings); }); it('sign in via email works', async function () { + const { getAuth, sendSignInLinkToEmail } = authModular; + + const auth = getAuth(); const random = Utils.randString(12, '#aa'); const email = `${random}@${random}.com`; const continueUrl = 'http://localhost:1337/authLinkFoo?bar=' + random; @@ -44,7 +52,7 @@ describe('auth() -> emailLink Provider', function () { minimumVersion: '12', }, }; - await firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings); + await sendSignInLinkToEmail(auth, email, actionCodeSettings); const oobInfo = await getLastOob(email); oobInfo.oobLink.should.containEql(encodeURIComponent(continueUrl)); const signInResponse = await signInUser(oobInfo.oobLink); @@ -53,39 +61,46 @@ describe('auth() -> emailLink Provider', function () { }); xit('should send email with defaults', async function () { + const { getAuth, sendSignInLinkToEmail } = authModular; + + const auth = getAuth(); const random = Utils.randString(12, '#aA'); const email = `${random}@${random}.com`; - await firebase.auth().sendSignInLinkToEmail(email); + await sendSignInLinkToEmail(auth, email); }); }); describe('isSignInWithEmailLink', function () { it('should return true/false', async function () { + const { getAuth, isSignInWithEmailLink } = authModular; + + const auth = getAuth(); const emailLink1 = 'https://www.example.com/action?mode=signIn&oobCode=oobCode'; const emailLink2 = 'https://www.example.com/action?mode=verifyEmail&oobCode=oobCode'; const emailLink3 = 'https://www.example.com/action?mode=signIn'; const emailLink4 = 'https://x59dg.app.goo.gl/?link=https://rnfirebase-b9ad4.firebaseapp.com/__/auth/action?apiKey%3Dfoo%26mode%3DsignIn%26oobCode%3Dbar'; - should.equal(true, firebase.auth().isSignInWithEmailLink(emailLink1)); - should.equal(false, firebase.auth().isSignInWithEmailLink(emailLink2)); - should.equal(false, firebase.auth().isSignInWithEmailLink(emailLink3)); - should.equal(true, firebase.auth().isSignInWithEmailLink(emailLink4)); + should.equal(true, isSignInWithEmailLink(auth, emailLink1)); + should.equal(false, isSignInWithEmailLink(auth, emailLink2)); + should.equal(false, isSignInWithEmailLink(auth, emailLink3)); + should.equal(true, isSignInWithEmailLink(auth, emailLink4)); }); }); // FOR MANUAL TESTING ONLY xdescribe('signInWithEmailLink', function () { it('should signIn', async function () { + const auth = getAuth(); const email = 'MANUAL TEST EMAIL HERE'; const emailLink = 'MANUAL TEST CODE HERE'; - const userCredential = await firebase.auth().signInWithEmailLink(email, emailLink); + const userCredential = await signInWithEmailLink(auth, email, emailLink); userCredential.user.email.should.equal(email); - await await firebase.auth().signOut(); + await signOut(auth); }); }); }); diff --git a/packages/auth/e2e/multiFactor.e2e.js b/packages/auth/e2e/multiFactor.e2e.js index 17d74211d4..74f1fd0fcd 100644 --- a/packages/auth/e2e/multiFactor.e2e.js +++ b/packages/auth/e2e/multiFactor.e2e.js @@ -10,309 +10,885 @@ const { const TEST_EMAIL = 'test@example.com'; const TEST_PASS = 'test1234'; -describe('multi-factor', function () { - beforeEach(async function () { - await clearAllUsers(); - await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); - await Utils.sleep(50); - } - }); - it('has no multi-factor information if not enrolled', async function () { - await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - const { multiFactor } = firebase.auth().currentUser; - multiFactor.should.be.an.Object(); - multiFactor.enrolledFactors.should.be.an.Array(); - multiFactor.enrolledFactors.length.should.equal(0); - return Promise.resolve(); - }); - - describe('sign-in', function () { - it('requires multi-factor auth when enrolled', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); +describe('multi-factor modular', function () { + describe('firebase v8 compatibility', function () { + beforeEach(async function () { + await clearAllUsers(); + await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); } - const { phoneNumber, email, password } = await createUserWithMultiFactor(); + }); + it('has no multi-factor information if not enrolled', async function () { + await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + const { multiFactor } = firebase.auth().currentUser; + multiFactor.should.be.an.Object(); + multiFactor.enrolledFactors.should.be.an.Array(); + multiFactor.enrolledFactors.length.should.equal(0); + return Promise.resolve(); + }); - try { - await firebase.auth().signInWithEmailAndPassword(email, password); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - const resolver = firebase.auth().getMultiFactorResolver(e); - resolver.should.be.an.Object(); - resolver.hints.should.be.an.Array(); - resolver.hints.length.should.equal(1); - resolver.session.should.be.a.String(); + describe('sign-in', function () { + it('requires multi-factor auth when enrolled', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + const { phoneNumber, email, password } = await createUserWithMultiFactor(); - const verificationId = await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); - verificationId.should.be.a.String(); + try { + await firebase.auth().signInWithEmailAndPassword(email, password); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + const resolver = firebase.auth().getMultiFactorResolver(e); + resolver.should.be.an.Object(); + resolver.hints.should.be.an.Array(); + resolver.hints.length.should.equal(1); + resolver.session.should.be.a.String(); + + const verificationId = await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); + verificationId.should.be.a.String(); - let verificationCode = await getLastSmsCode(phoneNumber); - if (verificationCode == null) { - // iOS simulator uses a masked phone number - const maskedNumber = '+********' + phoneNumber.substring(phoneNumber.length - 4); - verificationCode = await getLastSmsCode(maskedNumber); + let verificationCode = await getLastSmsCode(phoneNumber); + if (verificationCode == null) { + // iOS simulator uses a masked phone number + const maskedNumber = '+********' + phoneNumber.substring(phoneNumber.length - 4); + verificationCode = await getLastSmsCode(maskedNumber); + } + const credential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + verificationCode, + ); + const multiFactorAssertion = + firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + return resolver + .resolveSignIn(multiFactorAssertion) + .then(userCreds => { + userCreds.should.be.an.Object(); + userCreds.user.should.be.an.Object(); + userCreds.user.email.should.equal('verified@example.com'); + userCreds.user.multiFactor.should.be.an.Object(); + userCreds.user.multiFactor.enrolledFactors.length.should.equal(1); + return Promise.resolve(); + }) + .catch(e => { + return Promise.reject(e); + }); } - const credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, - verificationCode, + + return Promise.reject( + new Error('Multi-factor users need to handle an exception on sign-in'), ); - const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); - return resolver - .resolveSignIn(multiFactorAssertion) - .then(userCreds => { - userCreds.should.be.an.Object(); - userCreds.user.should.be.an.Object(); - userCreds.user.email.should.equal('verified@example.com'); - userCreds.user.multiFactor.should.be.an.Object(); - userCreds.user.multiFactor.enrolledFactors.length.should.equal(1); - return Promise.resolve(); - }) - .catch(e => { - return Promise.reject(e); - }); - } + }); + it('reports an error when providing an invalid sms code', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } - return Promise.reject(new Error('Multi-factor users need to handle an exception on sign-in')); - }); - it('reports an error when providing an invalid sms code', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + const { phoneNumber, email, password } = await createUserWithMultiFactor(); - const { phoneNumber, email, password } = await createUserWithMultiFactor(); + try { + await firebase.auth().signInWithEmailAndPassword(email, password); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + const resolver = firebase.auth().getMultiFactorResolver(e); + const verificationId = await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); - try { - await firebase.auth().signInWithEmailAndPassword(email, password); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - const resolver = firebase.auth().getMultiFactorResolver(e); - const verificationId = await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); + const credential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + 'incorrect', + ); + const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + try { + await resolver.resolveSignIn(assertion); + } catch (e) { + e.message + .toLocaleLowerCase() + .should.containEql('[auth/invalid-verification-code]'.toLocaleLowerCase()); + + const verificationId = await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); + const verificationCode = await getLastSmsCode(phoneNumber); + const credential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + verificationCode, + ); + const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + await resolver.resolveSignIn(assertion); + firebase.auth().currentUser.email.should.equal(email); + return Promise.resolve(); + } + } + return Promise.reject(); + }); + it('reports an error when providing an invalid verification code', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + const { phoneNumber, email, password } = await createUserWithMultiFactor(); - const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, 'incorrect'); - const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); try { - await resolver.resolveSignIn(assertion); + await firebase.auth().signInWithEmailAndPassword(email, password); } catch (e) { - e.message - .toLocaleLowerCase() - .should.containEql('[auth/invalid-verification-code]'.toLocaleLowerCase()); - - const verificationId = await firebase + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + const resolver = firebase.auth().getMultiFactorResolver(e); + await firebase .auth() .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); const verificationCode = await getLastSmsCode(phoneNumber); + const credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, + 'incorrect', verificationCode, ); const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); - await resolver.resolveSignIn(assertion); - firebase.auth().currentUser.email.should.equal(email); - return Promise.resolve(); - } - } - return Promise.reject(); - }); - it('reports an error when providing an invalid verification code', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } - const { phoneNumber, email, password } = await createUserWithMultiFactor(); + try { + await resolver.resolveSignIn(assertion); + } catch (e) { + e.message.should.equal( + '[auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.', + ); - try { - await firebase.auth().signInWithEmailAndPassword(email, password); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - const resolver = firebase.auth().getMultiFactorResolver(e); - await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); - const verificationCode = await getLastSmsCode(phoneNumber); + return Promise.resolve(); + } + } + return Promise.reject(); + }); + it('reports an error when using an unknown factor', async function () { + const { email, password } = await createUserWithMultiFactor(); - const credential = firebase.auth.PhoneAuthProvider.credential( - 'incorrect', - verificationCode, - ); - const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); try { - await resolver.resolveSignIn(assertion); + await firebase.auth().signInWithEmailAndPassword(email, password); } catch (e) { e.message.should.equal( - '[auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.', + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', ); + const resolver = firebase.auth().getMultiFactorResolver(e); + const unknownFactor = { + uid: 'notknown', + }; + try { + await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(unknownFactor, resolver.session); + } catch (e) { + e.code.should.equal('auth/multi-factor-info-not-found'); + e.message.should.equal( + '[auth/multi-factor-info-not-found] The user does not have a second factor matching the identifier provided.', + ); - return Promise.resolve(); + return Promise.resolve(); + } } - } - return Promise.reject(); + return Promise.reject(); + }); }); - it('reports an error when using an unknown factor', async function () { - const { email, password } = await createUserWithMultiFactor(); - - try { - await firebase.auth().signInWithEmailAndPassword(email, password); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - const resolver = firebase.auth().getMultiFactorResolver(e); - const unknownFactor = { - uid: 'notknown', - }; + + describe('enroll', function () { + it("can't enroll an existing user without verified email", async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + try { + const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); + const session = await multiFactorUser.getSession(); await firebase .auth() - .verifyPhoneNumberWithMultiFactorInfo(unknownFactor, resolver.session); + .verifyPhoneNumberForMultiFactor({ phoneNumber: getRandomPhoneNumber(), session }); } catch (e) { - e.code.should.equal('auth/multi-factor-info-not-found'); e.message.should.equal( - '[auth/multi-factor-info-not-found] The user does not have a second factor matching the identifier provided.', + '[auth/unverified-email] This operation requires a verified email.', ); - + e.code.should.equal('auth/unverified-email'); return Promise.resolve(); } - } - return Promise.reject(); - }); - }); - describe('enroll', function () { - it("can't enroll an existing user without verified email", async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + return Promise.reject(new Error('Should throw error for unverified user.')); + }); - await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + it('can enroll new factor', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } - try { - const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); - const session = await multiFactorUser.getSession(); - await firebase - .auth() - .verifyPhoneNumberForMultiFactor({ phoneNumber: getRandomPhoneNumber(), session }); - } catch (e) { - e.message.should.equal('[auth/unverified-email] This operation requires a verified email.'); - e.code.should.equal('auth/unverified-email'); + try { + await createVerifiedUser('verified@example.com', 'test123'); + const phoneNumber = getRandomPhoneNumber(); + + should.deepEqual(firebase.auth().currentUser.multiFactor.enrolledFactors, []); + const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); + + const session = await multiFactorUser.getSession(); + + const verificationId = await firebase + .auth() + .verifyPhoneNumberForMultiFactor({ phoneNumber, session }); + const verificationCode = await getLastSmsCode(phoneNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); + await multiFactorUser.enroll(multiFactorAssertion, 'Hint displayName'); + + const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; + enrolledFactors.length.should.equal(1); + enrolledFactors[0].displayName.should.equal('Hint displayName'); + enrolledFactors[0].factorId.should.equal('phone'); + enrolledFactors[0].uid.should.be.a.String(); + enrolledFactors[0].enrollmentTime.should.be.a.String(); + } catch (e) { + return Promise.reject(e); + } return Promise.resolve(); - } + }); + it('can enroll new factor without display name', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } - return Promise.reject(new Error('Should throw error for unverified user.')); - }); + try { + await createVerifiedUser('verified@example.com', 'test123'); + const phoneNumber = getRandomPhoneNumber(); - it('can enroll new factor', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + should.deepEqual(firebase.auth().currentUser.multiFactor.enrolledFactors, []); + const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); - try { - await createVerifiedUser('verified@example.com', 'test123'); - const phoneNumber = getRandomPhoneNumber(); + const session = await multiFactorUser.getSession(); - should.deepEqual(firebase.auth().currentUser.multiFactor.enrolledFactors, []); + const verificationId = await firebase + .auth() + .verifyPhoneNumberForMultiFactor({ phoneNumber, session }); + const verificationCode = await getLastSmsCode(phoneNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); + await multiFactorUser.enroll(multiFactorAssertion); + + const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; + enrolledFactors.length.should.equal(1); + should.equal(enrolledFactors[0].displayName, null); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }); + it('can enroll multiple factors', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { email, password, phoneNumber } = await createUserWithMultiFactor(); + await signInUserWithMultiFactor(email, password, phoneNumber); + + const anotherNumber = getRandomPhoneNumber(); const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); const session = await multiFactorUser.getSession(); - const verificationId = await firebase .auth() - .verifyPhoneNumberForMultiFactor({ phoneNumber, session }); - const verificationCode = await getLastSmsCode(phoneNumber); + .verifyPhoneNumberForMultiFactor({ phoneNumber: anotherNumber, session }); + const verificationCode = await getLastSmsCode(anotherNumber); const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); - await multiFactorUser.enroll(multiFactorAssertion, 'Hint displayName'); + const displayName = 'Another displayName'; + await multiFactorUser.enroll(multiFactorAssertion, displayName); const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; - enrolledFactors.length.should.equal(1); - enrolledFactors[0].displayName.should.equal('Hint displayName'); - enrolledFactors[0].factorId.should.equal('phone'); - enrolledFactors[0].uid.should.be.a.String(); - enrolledFactors[0].enrollmentTime.should.be.a.String(); - } catch (e) { - return Promise.reject(e); - } - return Promise.resolve(); - }); - it('can enroll new factor without display name', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + enrolledFactors.length.should.equal(2); + const matchingFactor = enrolledFactors.find(factor => factor.displayName === displayName); + matchingFactor.should.be.an.Object(); + matchingFactor.uid.should.be.a.String(); + matchingFactor.enrollmentTime.should.be.a.String(); + matchingFactor.factorId.should.equal('phone'); - try { - await createVerifiedUser('verified@example.com', 'test123'); - const phoneNumber = getRandomPhoneNumber(); - - should.deepEqual(firebase.auth().currentUser.multiFactor.enrolledFactors, []); + return Promise.resolve(); + }); + it('can not enroll the same factor twice', async function () { + this.skip(); + // This test should probably be implemented but doesn't work: + // Every time the same phone number requests a verification code, + // the emulator endpoint does not return a code, even though the emulator log + // prints a code. + // See https://github.com/firebase/firebase-tools/issues/4290#issuecomment-1281260335 + /* + await clearAllUsers(); + const { email, password, phoneNumber } = await createUserWithMultiFactor(); + await signInUserWithMultiFactor(email, password, phoneNumber); const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); - const session = await multiFactorUser.getSession(); const verificationId = await firebase .auth() .verifyPhoneNumberForMultiFactor({ phoneNumber, session }); const verificationCode = await getLastSmsCode(phoneNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); - await multiFactorUser.enroll(multiFactorAssertion); + const displayName = 'Another displayName'; + try { + await multiFactorUser.enroll(multiFactorAssertion, displayName); + } catch (e) { + console.error(e); + return Promise.resolve(); + } + return Promise.reject(); + */ + }); - const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; - enrolledFactors.length.should.equal(1); - should.equal(enrolledFactors[0].displayName, null); - } catch (e) { - return Promise.reject(e); - } - return Promise.resolve(); + it('throws an error for wrong verification id', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { phoneNumber, email, password } = await createUserWithMultiFactor(); + + // GIVEN a MultiFactorResolver + let resolver = null; + try { + await firebase.auth().signInWithEmailAndPassword(email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = firebase.auth().getMultiFactorResolver(e); + } + await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); + + // AND I request a verification code + const verificationCode = await getLastSmsCode(phoneNumber); + // AND I use an incorrect verificationId + const credential = firebase.auth.PhoneAuthProvider.credential( + 'wrongVerificationId', + verificationCode, + ); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + + try { + // WHEN I try to resolve the sign-in + await resolver.resolveSignIn(multiFactorAssertion); + } catch (e) { + // THEN an error message is thrown + e.message.should.equal( + '[auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.', + ); + return Promise.resolve(); + } + return Promise.reject(); + }); + it('throws an error for unknown sessions', async function () { + const { email, password } = await createUserWithMultiFactor(); + let resolver = null; + try { + await firebase.auth().signInWithEmailAndPassword(email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = firebase.auth().getMultiFactorResolver(e); + } + + try { + await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], 'unknown-session'); + } catch (e) { + // THEN an error message is thrown + e.message.should.equal( + '[auth/invalid-multi-factor-session] No resolver for session found. Is the session id correct?', + ); + return Promise.resolve(); + } + return Promise.reject(); + }); + it('throws an error for unknown verification code', async function () { + const { email, password } = await createUserWithMultiFactor(); + + // GIVEN a MultiFactorResolver + let resolver = null; + try { + await firebase.auth().signInWithEmailAndPassword(email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = firebase.auth().getMultiFactorResolver(e); + } + const verificationId = await firebase + .auth() + .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); + + // AND I use an incorrect verificationId + const credential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + 'wrong-verification-code', + ); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + + try { + // WHEN I try to resolve the sign-in + await resolver.resolveSignIn(multiFactorAssertion); + } catch (e) { + // THEN an error message is thrown + e.message + .toLocaleLowerCase() + .should.containEql('[auth/invalid-verification-code]'.toLocaleLowerCase()); + + return Promise.resolve(); + } + return Promise.reject(); + }); + + it('can not enroll with phone authentication (unsupported primary factor)', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + // GIVEN a user that only signs in with phone + const testPhone = getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + + // WHEN they attempt to enroll a second factor + const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); + const session = await multiFactorUser.getSession(); + try { + await firebase + .auth() + .verifyPhoneNumberForMultiFactor({ phoneNumber: '+1123123', session }); + } catch (e) { + e.message.should.equal( + '[auth/unsupported-first-factor] Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.', + ); + return Promise.resolve(); + } + return Promise.reject( + new Error('Enrolling a second factor when using phone authentication is not supported.'), + ); + }); + it('can not enroll when phone number is missing + sign', async function () { + await createVerifiedUser('verified@example.com', 'test123'); + const multiFactorUser = firebase.auth().multiFactor(firebase.auth().currentUser); + const session = await multiFactorUser.getSession(); + try { + await firebase.auth().verifyPhoneNumberForMultiFactor({ phoneNumber: '491575', session }); + } catch (e) { + e.code.should.equal('auth/invalid-phone-number'); + e.message.should.equal( + '[auth/invalid-phone-number] The format of the phone number provided is incorrect. Please enter the ' + + 'phone number in a format that can be parsed into E.164 format. E.164 ' + + 'phone numbers are written in the format [+][country code][subscriber ' + + 'number including area code].', + ); + return Promise.resolve(); + } + return Promise.reject(); + }); }); - it('can enroll multiple factors', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); + }); + + describe('modular', function () { + beforeEach(async function () { + const { createUserWithEmailAndPassword, getAuth, signOut } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + await clearAllUsers(); + await createUserWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + if (defaultAuth.currentUser) { + await signOut(defaultAuth); + await Utils.sleep(50); } + }); + it('has no multi-factor information if not enrolled', async function () { + const { signInWithEmailAndPassword, getAuth } = authModular; - const { email, password, phoneNumber } = await createUserWithMultiFactor(); - await signInUserWithMultiFactor(email, password, phoneNumber); - - const anotherNumber = getRandomPhoneNumber(); - const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); - - const session = await multiFactorUser.getSession(); - const verificationId = await firebase - .auth() - .verifyPhoneNumberForMultiFactor({ phoneNumber: anotherNumber, session }); - const verificationCode = await getLastSmsCode(anotherNumber); - const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); - const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); - const displayName = 'Another displayName'; - await multiFactorUser.enroll(multiFactorAssertion, displayName); - - const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; - enrolledFactors.length.should.equal(2); - const matchingFactor = enrolledFactors.find(factor => factor.displayName === displayName); - matchingFactor.should.be.an.Object(); - matchingFactor.uid.should.be.a.String(); - matchingFactor.enrollmentTime.should.be.a.String(); - matchingFactor.factorId.should.equal('phone'); + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + await signInWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + const multiFactorUser = defaultAuth.currentUser.multiFactor; + multiFactorUser.should.be.an.Object(); + multiFactorUser.enrolledFactors.should.be.an.Array(); + multiFactorUser.enrolledFactors.length.should.equal(0); return Promise.resolve(); }); - it('can not enroll the same factor twice', async function () { - this.skip(); - // This test should probably be implemented but doesn't work: - // Every time the same phone number requests a verification code, - // the emulator endpoint does not return a code, even though the emulator log - // prints a code. - // See https://github.com/firebase/firebase-tools/issues/4290#issuecomment-1281260335 - /* + + describe('sign-in', function () { + it('requires multi-factor auth when enrolled', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + const { phoneNumber, email, password } = await createUserWithMultiFactor(); + + const { signInWithEmailAndPassword, getAuth, getMultiFactorResolver } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + } catch (e) { + e.code.should.equal('auth/multi-factor-auth-required'); + const multiFactorResolver = getMultiFactorResolver(defaultAuth, e); + + multiFactorResolver.should.be.an.Object(); + multiFactorResolver.hints.should.be.an.Array(); + multiFactorResolver.hints.length.should.equal(1); + multiFactorResolver.session.should.be.a.String(); + + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + multiFactorHint: multiFactorResolver.hints[0], + session: multiFactorResolver.session, + }); + verificationId.should.be.a.String(); + + let verificationCode = await getLastSmsCode(phoneNumber); + if (verificationCode == null) { + // iOS simulator uses a masked phone number + const maskedNumber = '+********' + phoneNumber.substring(phoneNumber.length - 4); + verificationCode = await getLastSmsCode(maskedNumber); + } + const phoneAuthCredential = new firebase.auth.PhoneAuthProvider.credential( + verificationId, + verificationCode, + ); + const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + return multiFactorResolver + .resolveSignIn(assertion) + .then(userCredential => { + const { user } = userCredential; + user.should.be.an.Object(); + user.email.should.equal('verified@example.com'); + user.multiFactor.should.be.an.Object(); + user.multiFactor.enrolledFactors.length.should.equal(1); + return Promise.resolve(); + }) + .catch(e => { + return Promise.reject(e); + }); + } + + return Promise.reject( + new Error('Multi-factor users need to handle an exception on sign-in'), + ); + }); + + it('reports an error when providing an invalid sms code', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { phoneNumber, email, password } = await createUserWithMultiFactor(); + + const { + signInWithEmailAndPassword, + getAuth, + // authPhoneMultiFactor, + getMultiFactorResolver, + } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + } catch (e) { + e.code.should.equal('auth/multi-factor-auth-required'); + + const multiFactorResolver = getMultiFactorResolver(defaultAuth, e); + + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + multiFactorHint: multiFactorResolver.hints[0], + session: multiFactorResolver.session, + }); + + const phoneAuthCredential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + 'incorrect', + ); + const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + try { + await multiFactorResolver.resolveSignIn(assertion); + } catch (e) { + e.code.should.equal('auth/invalid-verification-code'); + + const newVerificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + multiFactorHint: multiFactorResolver.hints[0], + session: multiFactorResolver.session, + }); + const verificationCode = await getLastSmsCode(phoneNumber); + const newPhoneAuthCredential = firebase.auth.PhoneAuthProvider.credential( + newVerificationId, + verificationCode, + ); + const newAssertion = + firebase.auth.PhoneMultiFactorGenerator.assertion(newPhoneAuthCredential); + await multiFactorResolver.resolveSignIn(newAssertion); + const currentUser = defaultAuth.currentUser; + currentUser.email.should.equal(email); + return Promise.resolve(); + } + } + return Promise.reject(); + }); + + it('reports an error when providing an invalid verification code', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { phoneNumber, email, password } = await createUserWithMultiFactor(); + + const { signInWithEmailAndPassword, getAuth, getMultiFactorResolver } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + } catch (e) { + e.code.should.equal('auth/multi-factor-auth-required'); + + const multiFactorResolver = getMultiFactorResolver(defaultAuth, e); + + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + multiFactorHint: multiFactorResolver.hints[0], + session: multiFactorResolver.session, + }); + const verificationCode = await getLastSmsCode(phoneNumber); + + const phoneAuthCredential = firebase.auth.PhoneAuthProvider.credential( + 'incorrect', + verificationCode, + ); + const assertion = firebase.auth.PhoneMultiFactorGenerator.assertion(phoneAuthCredential); + try { + await multiFactorResolver.resolveSignIn(assertion); + } catch (e) { + e.code.should.equal('auth/invalid-verification-id'); + + return Promise.resolve(); + } + } + return Promise.reject(); + }); + + it('reports an error when using an unknown factor', async function () { + const { email, password } = await createUserWithMultiFactor(); + + const { signInWithEmailAndPassword, getAuth, getMultiFactorResolver } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + } catch (e) { + e.code.should.equal('auth/multi-factor-auth-required'); + + const multiFactorResolver = getMultiFactorResolver(defaultAuth, e); + + const unknownFactor = { + uid: 'notknown', + }; + try { + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + multiFactorHint: unknownFactor, + session: multiFactorResolver.session, + }); + } catch (e) { + e.code.should.equal('auth/multi-factor-info-not-found'); + e.message.should.equal( + '[auth/multi-factor-info-not-found] The user does not have a second factor matching the identifier provided.', + ); + + return Promise.resolve(); + } + } + return Promise.reject(); + }); + }); + + describe('enroll', function () { + it("can't enroll an existing user without verified email", async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { signInWithEmailAndPassword, getAuth, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + await signInWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + + try { + const multiFactorUser = await multiFactor(defaultAuth.currentUser); + const session = await multiFactorUser.getSession(); + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + phoneNumber: getRandomPhoneNumber(), + session, + }); + } catch (e) { + e.message.should.equal( + '[auth/unverified-email] This operation requires a verified email.', + ); + e.code.should.equal('auth/unverified-email'); + return Promise.resolve(); + } + + return Promise.reject(new Error('Should throw error for unverified user.')); + }); + + it('can enroll new factor', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getAuth, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await createVerifiedUser('verified@example.com', 'test123'); + const phoneNumber = getRandomPhoneNumber(); + + should.deepEqual(defaultAuth.currentUser.multiFactor.enrolledFactors, []); + const multiFactorUser = await multiFactor(defaultAuth.currentUser); + + const session = await multiFactorUser.getSession(); + + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + phoneNumber: phoneNumber, + session, + }); + const verificationCode = await getLastSmsCode(phoneNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); + await multiFactorUser.enroll(multiFactorAssertion, 'Hint displayName'); + + const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; + enrolledFactors.length.should.equal(1); + enrolledFactors[0].displayName.should.equal('Hint displayName'); + enrolledFactors[0].factorId.should.equal('phone'); + enrolledFactors[0].uid.should.be.a.String(); + enrolledFactors[0].enrollmentTime.should.be.a.String(); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }); + it('can enroll new factor without display name', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getAuth, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await createVerifiedUser('verified@example.com', 'test123'); + const phoneNumber = getRandomPhoneNumber(); + + should.deepEqual(defaultAuth.currentUser.multiFactor.enrolledFactors, []); + const multiFactorUser = await multiFactor(defaultAuth.currentUser); + + const session = await multiFactorUser.getSession(); + + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + phoneNumber: phoneNumber, + session: session, + }); + const verificationCode = await getLastSmsCode(phoneNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); + await multiFactorUser.enroll(multiFactorAssertion); + + const enrolledFactors = defaultAuth.currentUser.multiFactor.enrolledFactors; + enrolledFactors.length.should.equal(1); + should.equal(enrolledFactors[0].displayName, null); + } catch (e) { + return Promise.reject(e); + } + return Promise.resolve(); + }); + it('can enroll multiple factors', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getAuth, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const { email, password, phoneNumber } = await createUserWithMultiFactor(); + await signInUserWithMultiFactor(email, password, phoneNumber); + + const anotherNumber = getRandomPhoneNumber(); + const multiFactorUser = await multiFactor(defaultAuth.currentUser); + + const session = await multiFactorUser.getSession(); + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + phoneNumber: anotherNumber, + session: session, + }); + const verificationCode = await getLastSmsCode(anotherNumber); + const cred = firebase.auth.PhoneAuthProvider.credential(verificationId, verificationCode); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(cred); + const displayName = 'Another displayName'; + await multiFactorUser.enroll(multiFactorAssertion, displayName); + + const enrolledFactors = firebase.auth().currentUser.multiFactor.enrolledFactors; + enrolledFactors.length.should.equal(2); + const matchingFactor = enrolledFactors.find(factor => factor.displayName === displayName); + matchingFactor.should.be.an.Object(); + matchingFactor.uid.should.be.a.String(); + matchingFactor.enrollmentTime.should.be.a.String(); + matchingFactor.factorId.should.equal('phone'); + + return Promise.resolve(); + }); + it('can not enroll the same factor twice', async function () { + this.skip(); + // This test should probably be implemented but doesn't work: + // Every time the same phone number requests a verification code, + // the emulator endpoint does not return a code, even though the emulator log + // prints a code. + // See https://github.com/firebase/firebase-tools/issues/4290#issuecomment-1281260335 + /* await clearAllUsers(); const { email, password, phoneNumber } = await createUserWithMultiFactor(); await signInUserWithMultiFactor(email, password, phoneNumber); @@ -335,159 +911,196 @@ describe('multi-factor', function () { } return Promise.reject(); */ - }); + }); - it('throws an error for wrong verification id', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + it('throws an error for wrong verification id', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } - const { phoneNumber, email, password } = await createUserWithMultiFactor(); + const { getAuth, signInWithEmailAndPassword, getMultiFactorResolver } = authModular; - // GIVEN a MultiFactorResolver - let resolver = null; - try { - await firebase.auth().signInWithEmailAndPassword(email, password); - return Promise.reject(); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - resolver = firebase.auth().getMultiFactorResolver(e); - } - await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); - - // AND I request a verification code - const verificationCode = await getLastSmsCode(phoneNumber); - // AND I use an incorrect verificationId - const credential = firebase.auth.PhoneAuthProvider.credential( - 'wrongVerificationId', - verificationCode, - ); - const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); - - try { - // WHEN I try to resolve the sign-in - await resolver.resolveSignIn(multiFactorAssertion); - } catch (e) { - // THEN an error message is thrown - e.message.should.equal( - '[auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.', + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const { phoneNumber, email, password } = await createUserWithMultiFactor(); + + // GIVEN a MultiFactorResolver + let resolver = null; + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = getMultiFactorResolver(defaultAuth, e); + } + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + multiFactorHint: resolver.hints[0], + session: resolver.session, + }); + + // AND I request a verification code + const verificationCode = await getLastSmsCode(phoneNumber); + // AND I use an incorrect verificationId + const credential = firebase.auth.PhoneAuthProvider.credential( + 'wrongVerificationId', + verificationCode, ); - return Promise.resolve(); - } - return Promise.reject(); - }); - it('throws an error for unknown sessions', async function () { - const { email, password } = await createUserWithMultiFactor(); - let resolver = null; - try { - await firebase.auth().signInWithEmailAndPassword(email, password); + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + + try { + // WHEN I try to resolve the sign-in + await resolver.resolveSignIn(multiFactorAssertion); + } catch (e) { + // THEN an error message is thrown + e.message.should.equal( + '[auth/invalid-verification-id] The verification ID used to create the phone auth credential is invalid.', + ); + return Promise.resolve(); + } return Promise.reject(); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - resolver = firebase.auth().getMultiFactorResolver(e); - } + }); + it('throws an error for unknown sessions', async function () { + const { getAuth, signInWithEmailAndPassword, getMultiFactorResolver } = authModular; - try { - await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], 'unknown-session'); - } catch (e) { - // THEN an error message is thrown - e.message.should.equal( - '[auth/invalid-multi-factor-session] No resolver for session found. Is the session id correct?', - ); - return Promise.resolve(); - } - return Promise.reject(); - }); - it('throws an error for unknown verification code', async function () { - const { email, password } = await createUserWithMultiFactor(); + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); - // GIVEN a MultiFactorResolver - let resolver = null; - try { - await firebase.auth().signInWithEmailAndPassword(email, password); + const { email, password } = await createUserWithMultiFactor(); + let resolver = null; + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = getMultiFactorResolver(defaultAuth, e); + } + + try { + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + multiFactorHint: resolver.hints[0], + session: 'unknown-session', + }); + } catch (e) { + // THEN an error message is thrown + e.message.should.equal( + '[auth/invalid-multi-factor-session] No resolver for session found. Is the session id correct?', + ); + return Promise.resolve(); + } return Promise.reject(); - } catch (e) { - e.message.should.equal( - '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', - ); - resolver = firebase.auth().getMultiFactorResolver(e); - } - const verificationId = await firebase - .auth() - .verifyPhoneNumberWithMultiFactorInfo(resolver.hints[0], resolver.session); - - // AND I use an incorrect verificationId - const credential = firebase.auth.PhoneAuthProvider.credential( - verificationId, - 'wrong-verification-code', - ); - const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); - - try { - // WHEN I try to resolve the sign-in - await resolver.resolveSignIn(multiFactorAssertion); - } catch (e) { - // THEN an error message is thrown - e.message - .toLocaleLowerCase() - .should.containEql('[auth/invalid-verification-code]'.toLocaleLowerCase()); + }); + it('throws an error for unknown verification code', async function () { + const { getAuth, signInWithEmailAndPassword, getMultiFactorResolver } = authModular; - return Promise.resolve(); - } - return Promise.reject(); - }); + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); - it('can not enroll with phone authentication (unsupported primary factor)', async function () { - if (device.getPlatform() === 'ios') { - this.skip(); - } + const { email, password } = await createUserWithMultiFactor(); - // GIVEN a user that only signs in with phone - const testPhone = getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - const lastSmsCode = await getLastSmsCode(testPhone); - await confirmResult.confirm(lastSmsCode); - - // WHEN they attempt to enroll a second factor - const multiFactorUser = await firebase.auth().multiFactor(firebase.auth().currentUser); - const session = await multiFactorUser.getSession(); - try { - await firebase.auth().verifyPhoneNumberForMultiFactor({ phoneNumber: '+1123123', session }); - } catch (e) { - e.message.should.equal( - '[auth/unsupported-first-factor] Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.', + // GIVEN a MultiFactorResolver + let resolver = null; + try { + await signInWithEmailAndPassword(defaultAuth, email, password); + return Promise.reject(); + } catch (e) { + e.message.should.equal( + '[auth/multi-factor-auth-required] Please complete a second factor challenge to finish signing into this account.', + ); + resolver = getMultiFactorResolver(defaultAuth, e); + } + const verificationId = await new firebase.auth.PhoneAuthProvider( + defaultAuth, + ).verifyPhoneNumber({ + multiFactorHint: resolver.hints[0], + session: resolver.session, + }); + + // AND I use an incorrect verificationId + const credential = firebase.auth.PhoneAuthProvider.credential( + verificationId, + 'wrong-verification-code', ); - return Promise.resolve(); - } - return Promise.reject( - new Error('Enrolling a second factor when using phone authentication is not supported.'), - ); - }); - it('can not enroll when phone number is missing + sign', async function () { - await createVerifiedUser('verified@example.com', 'test123'); - const multiFactorUser = firebase.auth().multiFactor(firebase.auth().currentUser); - const session = await multiFactorUser.getSession(); - try { - await firebase.auth().verifyPhoneNumberForMultiFactor({ phoneNumber: '491575', session }); - } catch (e) { - e.code.should.equal('auth/invalid-phone-number'); - e.message.should.equal( - '[auth/invalid-phone-number] The format of the phone number provided is incorrect. Please enter the ' + - 'phone number in a format that can be parsed into E.164 format. E.164 ' + - 'phone numbers are written in the format [+][country code][subscriber ' + - 'number including area code].', + const multiFactorAssertion = firebase.auth.PhoneMultiFactorGenerator.assertion(credential); + + try { + // WHEN I try to resolve the sign-in + await resolver.resolveSignIn(multiFactorAssertion); + } catch (e) { + // THEN an error message is thrown + e.message + .toLocaleLowerCase() + .should.containEql('[auth/invalid-verification-code]'.toLocaleLowerCase()); + + return Promise.resolve(); + } + return Promise.reject(); + }); + + it('can not enroll with phone authentication (unsupported primary factor)', async function () { + if (device.getPlatform() === 'ios') { + this.skip(); + } + + const { getAuth, signInWithPhoneNumber, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + // GIVEN a user that only signs in with phone + const testPhone = getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(defaultAuth, testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + + // WHEN they attempt to enroll a second factor + const multiFactorUser = await multiFactor(defaultAuth.currentUser); + const session = await multiFactorUser.getSession(); + try { + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + phoneNumber: '+1123123', + session, + }); + } catch (e) { + e.message.should.equal( + '[auth/unsupported-first-factor] Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor.', + ); + return Promise.resolve(); + } + return Promise.reject( + new Error('Enrolling a second factor when using phone authentication is not supported.'), ); - return Promise.resolve(); - } - return Promise.reject(); + }); + it('can not enroll when phone number is missing + sign', async function () { + const { getAuth, multiFactor } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + await createVerifiedUser('verified@example.com', 'test123'); + const multiFactorUser = multiFactor(defaultAuth.currentUser); + const session = await multiFactorUser.getSession(); + try { + await new firebase.auth.PhoneAuthProvider(defaultAuth).verifyPhoneNumber({ + phoneNumber: '491575', + session, + }); + } catch (e) { + e.code.should.equal('auth/invalid-phone-number'); + e.message.should.equal( + '[auth/invalid-phone-number] The format of the phone number provided is incorrect. Please enter the ' + + 'phone number in a format that can be parsed into E.164 format. E.164 ' + + 'phone numbers are written in the format [+][country code][subscriber ' + + 'number including area code].', + ); + return Promise.resolve(); + } + return Promise.reject(); + }); }); }); }); diff --git a/packages/auth/e2e/phone.e2e.js b/packages/auth/e2e/phone.e2e.js index 518af8243e..2e6f7ec622 100644 --- a/packages/auth/e2e/phone.e2e.js +++ b/packages/auth/e2e/phone.e2e.js @@ -4,175 +4,409 @@ const { clearAllUsers, getLastSmsCode, getRandomPhoneNumber } = require('./helpers'); describe('auth() => Phone', function () { - before(async function () { - try { - await clearAllUsers(); - } catch (e) { - throw e; - } - firebase.auth().settings.appVerificationDisabledForTesting = true; - await Utils.sleep(50); - }); - - beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); + describe('firebase v8 compatibility', function () { + before(async function () { + try { + await clearAllUsers(); + } catch (e) { + throw e; + } + firebase.auth().settings.appVerificationDisabledForTesting = true; await Utils.sleep(50); - } - }); - - describe('signInWithPhoneNumber', function () { - it('signs in with a valid code', async function () { - const testPhone = getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - confirmResult.verificationId.should.be.a.String(); - should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); - confirmResult.confirm.should.be.a.Function(); - const lastSmsCode = await getLastSmsCode(testPhone); - const userCredential = await confirmResult.confirm(lastSmsCode); - userCredential.user.should.be.instanceOf(jet.require('packages/auth/lib/User')); - - // Broken check, phone number is undefined - // userCredential.user.phoneNumber.should.equal(TEST_PHONE_A); }); - it('errors on invalid code', async function () { - const testPhone = getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - confirmResult.verificationId.should.be.a.String(); - should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); - confirmResult.confirm.should.be.a.Function(); - // Get the last SMS code just to make absolutely sure we don't accidentally use it - const lastSmsCode = await getLastSmsCode(testPhone); - await confirmResult - .confirm(lastSmsCode === '000000' ? '111111' : '000000') - .should.be.rejected(); - // TODO test error code and message - - // If you don't consume the valid code, then it sticks around - await confirmResult.confirm(lastSmsCode); + beforeEach(async function () { + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); + } }); - }); - describe('verifyPhoneNumber', function () { - it('successfully verifies', async function () { - const testPhone = getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - const lastSmsCode = await getLastSmsCode(testPhone); - await confirmResult.confirm(lastSmsCode); - await firebase.auth().verifyPhoneNumber(testPhone, false, false); - }); + describe('signInWithPhoneNumber', function () { + it('signs in with a valid code', async function () { + const testPhone = getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + confirmResult.verificationId.should.be.a.String(); + should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); + confirmResult.confirm.should.be.a.Function(); + const lastSmsCode = await getLastSmsCode(testPhone); + const userCredential = await confirmResult.confirm(lastSmsCode); + userCredential.user.should.be.instanceOf(jet.require('packages/auth/lib/User')); + + // Broken check, phone number is undefined + // userCredential.user.phoneNumber.should.equal(TEST_PHONE_A); + }); - it('uses the autoVerifyTimeout when a non boolean autoVerifyTimeoutOrForceResend is provided', async function () { - const testPhone = getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - const lastSmsCode = await getLastSmsCode(testPhone); - await confirmResult.confirm(lastSmsCode); - await firebase.auth().verifyPhoneNumber(testPhone, 0, false); + it('errors on invalid code', async function () { + const testPhone = getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + confirmResult.verificationId.should.be.a.String(); + should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); + confirmResult.confirm.should.be.a.Function(); + // Get the last SMS code just to make absolutely sure we don't accidentally use it + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult + .confirm(lastSmsCode === '000000' ? '111111' : '000000') + .should.be.rejected(); + // TODO test error code and message + + // If you don't consume the valid code, then it sticks around + await confirmResult.confirm(lastSmsCode); + }); }); - it('throws an error with an invalid on event', async function () { - const testPhone = getRandomPhoneNumber(); - try { + describe('verifyPhoneNumber', function () { + it('successfully verifies', async function () { + const testPhone = getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + await firebase.auth().verifyPhoneNumber(testPhone, false, false); + }); + + it('uses the autoVerifyTimeout when a non boolean autoVerifyTimeoutOrForceResend is provided', async function () { + const testPhone = getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + await firebase.auth().verifyPhoneNumber(testPhone, 0, false); + }); + + it('throws an error with an invalid on event', async function () { + const testPhone = getRandomPhoneNumber(); + try { + await firebase + .auth() + .verifyPhoneNumber(testPhone) + .on('example', () => {}); + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql( + "firebase.auth.PhoneAuthListener.on(*, _, _, _) 'event' must equal 'state_changed'.", + ); + return Promise.resolve(); + } + }); + + it('throws an error with an invalid observer event', async function () { + const testPhone = getRandomPhoneNumber(); + try { + await firebase + .auth() + .verifyPhoneNumber(testPhone) + .on('state_changed', null, null, () => {}); + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql( + "firebase.auth.PhoneAuthListener.on(_, *, _, _) 'observer' must be a function.", + ); + return Promise.resolve(); + } + }); + + it('successfully runs verification complete handler', async function () { + const testPhone = getRandomPhoneNumber(); + const thenCb = sinon.spy(); + await firebase.auth().verifyPhoneNumber(testPhone).then(thenCb); + thenCb.should.be.calledOnce(); + const successAuthSnapshot = thenCb.args[0][0]; + if (device.getPlatform() === 'ios') { + successAuthSnapshot.state.should.equal('sent'); + } else { + successAuthSnapshot.state.should.equal('timeout'); + } + }); + + it('successfully runs and calls success callback', async function () { + const testPhone = getRandomPhoneNumber(); + const successCb = sinon.spy(); + const observerCb = sinon.spy(); + const errorCb = sinon.spy(); + await firebase .auth() .verifyPhoneNumber(testPhone) - .on('example', () => {}); + .on('state_changed', observerCb, errorCb, successCb); - return Promise.reject(new Error('Did not throw Error.')); - } catch (e) { - e.message.should.containEql( - "firebase.auth.PhoneAuthListener.on(*, _, _, _) 'event' must equal 'state_changed'.", - ); - return Promise.resolve(); - } - }); + await Utils.spyToBeCalledOnceAsync(successCb); + errorCb.should.be.callCount(0); + successCb.should.be.calledOnce(); + let observerAuthSnapshot = observerCb.args[0][0]; + const successAuthSnapshot = successCb.args[0][0]; + successAuthSnapshot.verificationId.should.be.a.String(); + if (device.getPlatform() === 'ios') { + observerCb.should.be.calledOnce(); + successAuthSnapshot.state.should.equal('sent'); + } else { + // android waits for SMS auto retrieval which does not work on an emulator + // it gets a sent and a timeout message on observer, just the timeout on success + observerCb.should.be.calledTwice(); + observerAuthSnapshot = observerCb.args[1][0]; + successAuthSnapshot.state.should.equal('timeout'); + } + JSON.stringify(successAuthSnapshot).should.equal(JSON.stringify(observerAuthSnapshot)); + }); + + // TODO determine why this is not stable on the emulator, is it also not stable on real device? + xit('successfully runs and calls error callback', async function () { + const successCb = sinon.spy(); + const observerCb = sinon.spy(); + const errorCb = sinon.spy(); - it('throws an error with an invalid observer event', async function () { - const testPhone = getRandomPhoneNumber(); - try { await firebase .auth() - .verifyPhoneNumber(testPhone) - .on('state_changed', null, null, () => {}); + .verifyPhoneNumber('notaphonenumber') + .on('state_changed', observerCb, errorCb, successCb); - return Promise.reject(new Error('Did not throw Error.')); - } catch (e) { - e.message.should.containEql( - "firebase.auth.PhoneAuthListener.on(_, *, _, _) 'observer' must be a function.", - ); - return Promise.resolve(); - } + await Utils.spyToBeCalledOnceAsync(errorCb); + errorCb.should.be.calledOnce(); + observerCb.should.be.calledOnce(); + // const observerEvent = observerCb.args[0][0]; + successCb.should.be.callCount(0); + // const errorEvent = errorCb.args[0][0]; + // errorEvent.error.should.containEql('auth/invalid-phone-number'); + // JSON.stringify(errorEvent).should.equal(JSON.stringify(observerEvent)); + }); + + it('catches an error and emits an error event', async function () { + const catchCb = sinon.spy(); + await firebase.auth().verifyPhoneNumber('badphonenumber').catch(catchCb); + catchCb.should.be.calledOnce(); + }); }); + }); + + describe('modular', function () { + before(async function () { + const { getAuth } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); - it('successfully runs verification complete handler', async function () { - const testPhone = getRandomPhoneNumber(); - const thenCb = sinon.spy(); - await firebase.auth().verifyPhoneNumber(testPhone).then(thenCb); - thenCb.should.be.calledOnce(); - const successAuthSnapshot = thenCb.args[0][0]; - if (device.getPlatform() === 'ios') { - successAuthSnapshot.state.should.equal('sent'); - } else { - successAuthSnapshot.state.should.equal('timeout'); + try { + await clearAllUsers(); + } catch (e) { + throw e; } + defaultAuth.settings.appVerificationDisabledForTesting = true; + await Utils.sleep(50); }); - it('successfully runs and calls success callback', async function () { - const testPhone = getRandomPhoneNumber(); - const successCb = sinon.spy(); - const observerCb = sinon.spy(); - const errorCb = sinon.spy(); - - await firebase - .auth() - .verifyPhoneNumber(testPhone) - .on('state_changed', observerCb, errorCb, successCb); - - await Utils.spyToBeCalledOnceAsync(successCb); - errorCb.should.be.callCount(0); - successCb.should.be.calledOnce(); - let observerAuthSnapshot = observerCb.args[0][0]; - const successAuthSnapshot = successCb.args[0][0]; - successAuthSnapshot.verificationId.should.be.a.String(); - if (device.getPlatform() === 'ios') { - observerCb.should.be.calledOnce(); - successAuthSnapshot.state.should.equal('sent'); - } else { - // android waits for SMS auto retrieval which does not work on an emulator - // it gets a sent and a timeout message on observer, just the timeout on success - observerCb.should.be.calledTwice(); - observerAuthSnapshot = observerCb.args[1][0]; - successAuthSnapshot.state.should.equal('timeout'); + beforeEach(async function () { + const { getAuth, signOut } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + if (defaultAuth.currentUser) { + await signOut(defaultAuth); + await Utils.sleep(50); } - JSON.stringify(successAuthSnapshot).should.equal(JSON.stringify(observerAuthSnapshot)); }); - // TODO determine why this is not stable on the emulator, is it also not stable on real device? - xit('successfully runs and calls error callback', async function () { - const successCb = sinon.spy(); - const observerCb = sinon.spy(); - const errorCb = sinon.spy(); - - await firebase - .auth() - .verifyPhoneNumber('notaphonenumber') - .on('state_changed', observerCb, errorCb, successCb); - - await Utils.spyToBeCalledOnceAsync(errorCb); - errorCb.should.be.calledOnce(); - observerCb.should.be.calledOnce(); - // const observerEvent = observerCb.args[0][0]; - successCb.should.be.callCount(0); - // const errorEvent = errorCb.args[0][0]; - // errorEvent.error.should.containEql('auth/invalid-phone-number'); - // JSON.stringify(errorEvent).should.equal(JSON.stringify(observerEvent)); + describe('signInWithPhoneNumber', function () { + it('signs in with a valid code', async function () { + const { getAuth, signInWithPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(defaultAuth, testPhone); + confirmResult.verificationId.should.be.a.String(); + should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); + confirmResult.confirm.should.be.a.Function(); + const lastSmsCode = await getLastSmsCode(testPhone); + const userCredential = await confirmResult.confirm(lastSmsCode); + userCredential.user.should.be.instanceOf(jet.require('packages/auth/lib/User')); + + // Broken check, phone number is undefined + // userCredential.user.phoneNumber.should.equal(TEST_PHONE_A); + }); + + it('errors on invalid code', async function () { + const { getAuth, signInWithPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(defaultAuth, testPhone); + confirmResult.verificationId.should.be.a.String(); + should.ok(confirmResult.verificationId.length, 'verificationId string should not be empty'); + confirmResult.confirm.should.be.a.Function(); + // Get the last SMS code just to make absolutely sure we don't accidentally use it + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult + .confirm(lastSmsCode === '000000' ? '111111' : '000000') + .should.be.rejected(); + // TODO test error code and message + + // If you don't consume the valid code, then it sticks around + await confirmResult.confirm(lastSmsCode); + }); }); - it('catches an error and emits an error event', async function () { - const catchCb = sinon.spy(); - await firebase.auth().verifyPhoneNumber('badphonenumber').catch(catchCb); - catchCb.should.be.calledOnce(); + // Note: verifyPhoneNumber is not available in the Firebase v9 API. The following tests need to be updated to use the new API. + + describe('verifyPhoneNumber', function () { + it('successfully verifies', async function () { + const { getAuth, signInWithPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(defaultAuth, testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + await firebase.auth().verifyPhoneNumber(testPhone, false, false); + }); + + it('uses the autoVerifyTimeout when a non boolean autoVerifyTimeoutOrForceResend is provided', async function () { + const { getAuth, signInWithPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + const testPhone = getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(defaultAuth, testPhone); + const lastSmsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(lastSmsCode); + await firebase.auth().verifyPhoneNumber(testPhone, 0, false); + }); + + it('throws an error with an invalid on event', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const auth = getAuth(); + const testPhone = getRandomPhoneNumber(); + + try { + await verifyPhoneNumber(auth, testPhone).on('example', () => {}); + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql( + "firebase.auth.PhoneAuthListener.on(*, _, _, _) 'event' must equal 'state_changed'.", + ); + return Promise.resolve(); + } + }); + + it('throws an error with an invalid observer event', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + try { + await verifyPhoneNumber(defaultAuth, testPhone).on('state_changed', null, null, () => {}); + + return Promise.reject(new Error('Did not throw Error.')); + } catch (e) { + e.message.should.containEql( + "firebase.auth.PhoneAuthListener.on(_, *, _, _) 'observer' must be a function.", + ); + return Promise.resolve(); + } + }); + + it('successfully runs verification complete handler', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + const thenCb = sinon.spy(); + await verifyPhoneNumber(defaultAuth, testPhone).then(thenCb); + thenCb.should.be.calledOnce(); + const successAuthSnapshot = thenCb.args[0][0]; + if (device.getPlatform() === 'ios') { + successAuthSnapshot.state.should.equal('sent'); + } else { + successAuthSnapshot.state.should.equal('timeout'); + } + }); + + it('successfully runs and calls success callback', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const testPhone = getRandomPhoneNumber(); + const successCb = sinon.spy(); + const observerCb = sinon.spy(); + const errorCb = sinon.spy(); + + await verifyPhoneNumber(defaultAuth, testPhone).on( + 'state_changed', + observerCb, + errorCb, + successCb, + ); + + await Utils.spyToBeCalledOnceAsync(successCb); + errorCb.should.be.callCount(0); + successCb.should.be.calledOnce(); + let observerAuthSnapshot = observerCb.args[0][0]; + const successAuthSnapshot = successCb.args[0][0]; + successAuthSnapshot.verificationId.should.be.a.String(); + if (device.getPlatform() === 'ios') { + observerCb.should.be.calledOnce(); + successAuthSnapshot.state.should.equal('sent'); + } else { + // android waits for SMS auto retrieval which does not work on an emulator + // it gets a sent and a timeout message on observer, just the timeout on success + observerCb.should.be.calledTwice(); + observerAuthSnapshot = observerCb.args[1][0]; + successAuthSnapshot.state.should.equal('timeout'); + } + JSON.stringify(successAuthSnapshot).should.equal(JSON.stringify(observerAuthSnapshot)); + }); + + // TODO determine why this is not stable on the emulator, is it also not stable on real device? + xit('successfully runs and calls error callback', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const successCb = sinon.spy(); + const observerCb = sinon.spy(); + const errorCb = sinon.spy(); + + await verifyPhoneNumber(defaultAuth, 'notaphonenumber').on( + 'state_changed', + observerCb, + errorCb, + successCb, + ); + + await Utils.spyToBeCalledOnceAsync(errorCb); + errorCb.should.be.calledOnce(); + observerCb.should.be.calledOnce(); + // const observerEvent = observerCb.args[0][0]; + successCb.should.be.callCount(0); + // const errorEvent = errorCb.args[0][0]; + // errorEvent.error.should.containEql('auth/invalid-phone-number'); + // JSON.stringify(errorEvent).should.equal(JSON.stringify(observerEvent)); + }); + + it('catches an error and emits an error event', async function () { + const { getAuth, verifyPhoneNumber } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + const catchCb = sinon.spy(); + await verifyPhoneNumber(defaultAuth, 'badphonenumber').catch(catchCb); + catchCb.should.be.calledOnce(); + }); }); }); }); diff --git a/packages/auth/e2e/provider.e2e.js b/packages/auth/e2e/provider.e2e.js index 1475eb5675..13bf1e53df 100644 --- a/packages/auth/e2e/provider.e2e.js +++ b/packages/auth/e2e/provider.e2e.js @@ -1,256 +1,563 @@ describe('auth() -> Providers', function () { - beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); - await Utils.sleep(50); - } - }); + describe('firebase v8 compatibility', function () { + beforeEach(async function () { + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); + } + }); - describe('EmailAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.EmailAuthProvider()).should.throw( - '`new EmailAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('EmailAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.EmailAuthProvider()).should.throw( + '`new EmailAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); }); - }); - describe('credential', function () { - it('should return a credential object', function () { - const email = 'email@email.com'; - const password = 'password'; - const credential = firebase.auth.EmailAuthProvider.credential(email, password); - credential.providerId.should.equal('password'); - credential.token.should.equal(email); - credential.secret.should.equal(password); + describe('credential', function () { + it('should return a credential object', function () { + const email = 'email@email.com'; + const password = 'password'; + const credential = firebase.auth.EmailAuthProvider.credential(email, password); + credential.providerId.should.equal('password'); + credential.token.should.equal(email); + credential.secret.should.equal(password); + }); }); - }); - describe('credentialWithLink', function () { - it('should return a credential object', function () { - const email = 'email@email.com'; - const link = 'link'; - const credential = firebase.auth.EmailAuthProvider.credentialWithLink(email, link); - credential.providerId.should.equal('emailLink'); - credential.token.should.equal(email); - credential.secret.should.equal(link); + describe('credentialWithLink', function () { + it('should return a credential object', function () { + const email = 'email@email.com'; + const link = 'link'; + const credential = firebase.auth.EmailAuthProvider.credentialWithLink(email, link); + credential.providerId.should.equal('emailLink'); + credential.token.should.equal(email); + credential.secret.should.equal(link); + }); }); - }); - describe('EMAIL_PASSWORD_SIGN_IN_METHOD', function () { - it('should return password', function () { - firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD.should.equal('password'); + describe('EMAIL_PASSWORD_SIGN_IN_METHOD', function () { + it('should return password', function () { + firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD.should.equal('password'); + }); }); - }); - describe('EMAIL_LINK_SIGN_IN_METHOD', function () { - it('should return emailLink', function () { - firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD.should.equal('emailLink'); + describe('EMAIL_LINK_SIGN_IN_METHOD', function () { + it('should return emailLink', function () { + firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD.should.equal('emailLink'); + }); }); - }); - describe('PROVIDER_ID', function () { - it('should return password', function () { - firebase.auth.EmailAuthProvider.PROVIDER_ID.should.equal('password'); + describe('PROVIDER_ID', function () { + it('should return password', function () { + firebase.auth.EmailAuthProvider.PROVIDER_ID.should.equal('password'); + }); }); }); - }); - describe('FacebookAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.FacebookAuthProvider()).should.throw( - '`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('FacebookAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.FacebookAuthProvider()).should.throw( + '`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); }); - }); - describe('credential', function () { - it('should return a credential object', function () { - const token = '123456'; - const credential = firebase.auth.FacebookAuthProvider.credential(token); - credential.providerId.should.equal('facebook.com'); - credential.token.should.equal(token); - credential.secret.should.equal(''); + describe('credential', function () { + it('should return a credential object', function () { + const token = '123456'; + const credential = firebase.auth.FacebookAuthProvider.credential(token); + credential.providerId.should.equal('facebook.com'); + credential.token.should.equal(token); + credential.secret.should.equal(''); + }); }); - }); - describe('credentialLimitedLogin', function () { - it('should return a credential object', function () { - const token = '123456'; - const nonce = '654321'; - const credential = firebase.auth.FacebookAuthProvider.credential(token, nonce); - credential.providerId.should.equal('facebook.com'); - credential.token.should.equal(token); - credential.secret.should.equal(nonce); + describe('credentialLimitedLogin', function () { + it('should return a credential object', function () { + const token = '123456'; + const nonce = '654321'; + const credential = firebase.auth.FacebookAuthProvider.credential(token, nonce); + credential.providerId.should.equal('facebook.com'); + credential.token.should.equal(token); + credential.secret.should.equal(nonce); + }); }); - }); - describe('PROVIDER_ID', function () { - it('should return facebook.com', function () { - firebase.auth.FacebookAuthProvider.PROVIDER_ID.should.equal('facebook.com'); + describe('PROVIDER_ID', function () { + it('should return facebook.com', function () { + firebase.auth.FacebookAuthProvider.PROVIDER_ID.should.equal('facebook.com'); + }); }); }); - }); - describe('GithubAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.GithubAuthProvider()).should.throw( - '`new GithubAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('GithubAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.GithubAuthProvider()).should.throw( + '`new GithubAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); }); - }); - describe('credential', function () { - it('should return a credential object', function () { - const token = '123456'; - const credential = firebase.auth.GithubAuthProvider.credential(token); - credential.providerId.should.equal('github.com'); - credential.token.should.equal(token); - credential.secret.should.equal(''); + describe('credential', function () { + it('should return a credential object', function () { + const token = '123456'; + const credential = firebase.auth.GithubAuthProvider.credential(token); + credential.providerId.should.equal('github.com'); + credential.token.should.equal(token); + credential.secret.should.equal(''); + }); }); - }); - describe('PROVIDER_ID', function () { - it('should return github.com', function () { - firebase.auth.GithubAuthProvider.PROVIDER_ID.should.equal('github.com'); + describe('PROVIDER_ID', function () { + it('should return github.com', function () { + firebase.auth.GithubAuthProvider.PROVIDER_ID.should.equal('github.com'); + }); }); }); - }); - describe('GoogleAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.GoogleAuthProvider()).should.throw( - '`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('GoogleAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.GoogleAuthProvider()).should.throw( + '`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); }); - }); - describe('credential', function () { - it('should return a credential object', function () { - const token = '123456'; - const secret = '654321'; - const credential = firebase.auth.GoogleAuthProvider.credential(token, secret); - credential.providerId.should.equal('google.com'); - credential.token.should.equal(token); - credential.secret.should.equal(secret); + describe('credential', function () { + it('should return a credential object', function () { + const token = '123456'; + const secret = '654321'; + const credential = firebase.auth.GoogleAuthProvider.credential(token, secret); + credential.providerId.should.equal('google.com'); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return google.com', function () { + firebase.auth.GoogleAuthProvider.PROVIDER_ID.should.equal('google.com'); + }); }); }); - describe('PROVIDER_ID', function () { - it('should return google.com', function () { - firebase.auth.GoogleAuthProvider.PROVIDER_ID.should.equal('google.com'); + describe('OAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.OAuthProvider()).should.throw( + '`new OAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const idToken = '123456'; + const accessToken = '654321'; + const credential = firebase.auth.OAuthProvider.credential(idToken, accessToken); + credential.providerId.should.equal('oauth'); + credential.token.should.equal(idToken); + credential.secret.should.equal(accessToken); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return oauth', function () { + firebase.auth.OAuthProvider.PROVIDER_ID.should.equal('oauth'); + }); }); }); - }); - describe('OAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.OAuthProvider()).should.throw( - '`new OAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('PhoneAuthProvider', function () { + describe('credential', function () { + it('should return a credential object', function () { + const verificationId = '123456'; + const code = '654321'; + const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code); + credential.providerId.should.equal('phone'); + credential.token.should.equal(verificationId); + credential.secret.should.equal(code); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return phone', function () { + firebase.auth.PhoneAuthProvider.PROVIDER_ID.should.equal('phone'); + }); }); }); - describe('credential', function () { - it('should return a credential object', function () { - const idToken = '123456'; - const accessToken = '654321'; - const credential = firebase.auth.OAuthProvider.credential(idToken, accessToken); - credential.providerId.should.equal('oauth'); - credential.token.should.equal(idToken); - credential.secret.should.equal(accessToken); + describe('TwitterAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.TwitterAuthProvider()).should.throw( + '`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const token = '123456'; + const secret = '654321'; + const credential = firebase.auth.TwitterAuthProvider.credential(token, secret); + credential.providerId.should.equal('twitter.com'); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return twitter.com', function () { + firebase.auth.TwitterAuthProvider.PROVIDER_ID.should.equal('twitter.com'); + }); }); }); - describe('PROVIDER_ID', function () { - it('should return oauth', function () { - firebase.auth.OAuthProvider.PROVIDER_ID.should.equal('oauth'); + describe('OIDCAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + (() => new firebase.auth.OIDCAuthProvider()).should.throw( + '`new OIDCAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const token = '123456'; + const secret = '654321'; + const providerSuffix = 'sample-provider'; + const credential = firebase.auth.OIDCAuthProvider.credential( + providerSuffix, + token, + secret, + ); + credential.providerId.should.equal('oidc.' + providerSuffix); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return oidc.', function () { + firebase.auth.OIDCAuthProvider.PROVIDER_ID.should.equal('oidc.'); + }); }); }); }); - describe('PhoneAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.PhoneAuthProvider()).should.throw( - '`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.', - ); - }); + describe('modular', function () { + beforeEach(async function () { + const { signOut, getAuth } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + if (defaultAuth.currentUser) { + await signOut(defaultAuth); + await Utils.sleep(50); + } }); - describe('credential', function () { - it('should return a credential object', function () { - const verificationId = '123456'; - const code = '654321'; - const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code); - credential.providerId.should.equal('phone'); - credential.token.should.equal(verificationId); - credential.secret.should.equal(code); + describe('EmailAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { EmailAuthProvider } = authModular; + + (() => new EmailAuthProvider()).should.throw( + '`new EmailAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { EmailAuthProvider } = authModular; + + const email = 'email@email.com'; + const password = 'password'; + const credential = EmailAuthProvider.credential(email, password); + credential.providerId.should.equal('password'); + credential.token.should.equal(email); + credential.secret.should.equal(password); + }); + }); + + describe('credentialWithLink', function () { + it('should return a credential object', function () { + const { EmailAuthProvider } = authModular; + + const email = 'email@email.com'; + const link = 'link'; + const credential = EmailAuthProvider.credentialWithLink(email, link); + credential.providerId.should.equal('emailLink'); + credential.token.should.equal(email); + credential.secret.should.equal(link); + }); + }); + + describe('EMAIL_PASSWORD_SIGN_IN_METHOD', function () { + it('should return password', function () { + const { EmailAuthProvider } = authModular; + + EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD.should.equal('password'); + }); + }); + + describe('EMAIL_LINK_SIGN_IN_METHOD', function () { + it('should return emailLink', function () { + const { EmailAuthProvider } = authModular; + + EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD.should.equal('emailLink'); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return password', function () { + const { EmailAuthProvider } = authModular; + + EmailAuthProvider.PROVIDER_ID.should.equal('password'); + }); }); }); - describe('PROVIDER_ID', function () { - it('should return phone', function () { - firebase.auth.PhoneAuthProvider.PROVIDER_ID.should.equal('phone'); + describe('FacebookAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { FacebookAuthProvider } = authModular; + + (() => new FacebookAuthProvider()).should.throw( + '`new FacebookAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { FacebookAuthProvider } = authModular; + + const token = '123456'; + const credential = FacebookAuthProvider.credential(token); + credential.providerId.should.equal('facebook.com'); + credential.token.should.equal(token); + credential.secret.should.equal(''); + }); + }); + + describe('credentialLimitedLogin', function () { + it('should return a credential object', function () { + const { FacebookAuthProvider } = authModular; + + const token = '123456'; + const nonce = '654321'; + const credential = FacebookAuthProvider.credential(token, nonce); + credential.providerId.should.equal('facebook.com'); + credential.token.should.equal(token); + credential.secret.should.equal(nonce); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return facebook.com', function () { + const { FacebookAuthProvider } = authModular; + + FacebookAuthProvider.PROVIDER_ID.should.equal('facebook.com'); + }); }); }); - }); - describe('TwitterAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.TwitterAuthProvider()).should.throw( - '`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('GithubAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { GithubAuthProvider } = authModular; + + (() => new GithubAuthProvider()).should.throw( + '`new GithubAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { GithubAuthProvider } = authModular; + + const token = '123456'; + const credential = GithubAuthProvider.credential(token); + credential.providerId.should.equal('github.com'); + credential.token.should.equal(token); + credential.secret.should.equal(''); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return github.com', function () { + const { GithubAuthProvider } = authModular; + + GithubAuthProvider.PROVIDER_ID.should.equal('github.com'); + }); }); }); - describe('credential', function () { - it('should return a credential object', function () { - const token = '123456'; - const secret = '654321'; - const credential = firebase.auth.TwitterAuthProvider.credential(token, secret); - credential.providerId.should.equal('twitter.com'); - credential.token.should.equal(token); - credential.secret.should.equal(secret); + describe('GoogleAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { GoogleAuthProvider } = authModular; + + (() => new GoogleAuthProvider()).should.throw( + '`new GoogleAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { GoogleAuthProvider } = authModular; + + const token = '123456'; + const secret = '654321'; + const credential = GoogleAuthProvider.credential(token, secret); + credential.providerId.should.equal('google.com'); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return google.com', function () { + const { GoogleAuthProvider } = authModular; + + GoogleAuthProvider.PROVIDER_ID.should.equal('google.com'); + }); }); }); - describe('PROVIDER_ID', function () { - it('should return twitter.com', function () { - firebase.auth.TwitterAuthProvider.PROVIDER_ID.should.equal('twitter.com'); + describe('OAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { OAuthProvider } = authModular; + + (() => new OAuthProvider()).should.throw( + '`new OAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { OAuthProvider } = authModular; + + const idToken = '123456'; + const accessToken = '654321'; + const credential = OAuthProvider.credential(idToken, accessToken); + credential.providerId.should.equal('oauth'); + credential.token.should.equal(idToken); + credential.secret.should.equal(accessToken); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return oauth', function () { + const { OAuthProvider } = authModular; + + OAuthProvider.PROVIDER_ID.should.equal('oauth'); + }); }); }); - }); - describe('OIDCAuthProvider', function () { - describe('constructor', function () { - it('should throw an unsupported error', function () { - (() => new firebase.auth.OIDCAuthProvider()).should.throw( - '`new OIDCAuthProvider()` is not supported on the native Firebase SDKs.', - ); + describe('PhoneAuthProvider', function () { + describe('credential', function () { + it('should return a credential object', function () { + const { PhoneAuthProvider } = authModular; + + const verificationId = '123456'; + const code = '654321'; + const credential = PhoneAuthProvider.credential(verificationId, code); + credential.providerId.should.equal('phone'); + credential.token.should.equal(verificationId); + credential.secret.should.equal(code); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return phone', function () { + const { PhoneAuthProvider } = authModular; + + PhoneAuthProvider.PROVIDER_ID.should.equal('phone'); + }); }); }); - describe('credential', function () { - it('should return a credential object', function () { - const token = '123456'; - const secret = '654321'; - const providerSuffix = 'sample-provider'; - const credential = firebase.auth.OIDCAuthProvider.credential(providerSuffix, token, secret); - credential.providerId.should.equal('oidc.' + providerSuffix); - credential.token.should.equal(token); - credential.secret.should.equal(secret); + describe('TwitterAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { TwitterAuthProvider } = authModular; + + (() => new TwitterAuthProvider()).should.throw( + '`new TwitterAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { TwitterAuthProvider } = authModular; + + const token = '123456'; + const secret = '654321'; + const credential = TwitterAuthProvider.credential(token, secret); + credential.providerId.should.equal('twitter.com'); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return twitter.com', function () { + const { TwitterAuthProvider } = authModular; + + TwitterAuthProvider.PROVIDER_ID.should.equal('twitter.com'); + }); }); }); - describe('PROVIDER_ID', function () { - it('should return oidc.', function () { - firebase.auth.OIDCAuthProvider.PROVIDER_ID.should.equal('oidc.'); + describe('OIDCAuthProvider', function () { + describe('constructor', function () { + it('should throw an unsupported error', function () { + const { OIDCAuthProvider } = authModular; + + (() => new OIDCAuthProvider()).should.throw( + '`new OIDCAuthProvider()` is not supported on the native Firebase SDKs.', + ); + }); + }); + + describe('credential', function () { + it('should return a credential object', function () { + const { OIDCAuthProvider } = authModular; + + const token = '123456'; + const secret = '654321'; + const providerSuffix = 'sample-provider'; + const credential = OIDCAuthProvider.credential(providerSuffix, token, secret); + credential.providerId.should.equal('oidc.' + providerSuffix); + credential.token.should.equal(token); + credential.secret.should.equal(secret); + }); + }); + + describe('PROVIDER_ID', function () { + it('should return oidc.', function () { + const { OIDCAuthProvider } = authModular; + + OIDCAuthProvider.PROVIDER_ID.should.equal('oidc.'); + }); }); }); }); diff --git a/packages/auth/e2e/rnReload.e2e.js b/packages/auth/e2e/rnReload.e2e.js index 2a8f40b15b..7c44ae5b08 100644 --- a/packages/auth/e2e/rnReload.e2e.js +++ b/packages/auth/e2e/rnReload.e2e.js @@ -4,86 +4,191 @@ const TEST_PASS = 'test1234'; const { clearAllUsers } = require('./helpers'); describe('auth()', function () { - before(async function () { - try { - await clearAllUsers(); - } catch (e) { - throw e; - } - try { - await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - } catch (e) { - // they may already exist, that's fine - } - }); + describe('firebase v8 compatibility', function () { + before(async function () { + try { + await clearAllUsers(); + } catch (e) { + throw e; + } + try { + await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + } catch (e) { + // they may already exist, that's fine + } + }); + + beforeEach(async function () { + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); + } + }); + + describe('firebase.auth().currentUser', function () { + it('exists after reload', async function () { + // Detox on iOS crashing app on reloads, documented + // https://github.com/wix/detox/blob/master/docs/APIRef.DeviceObjectAPI.md#devicereloadreactnative + if (device.getPlatform() === 'ios') { + this.skip(); + } + let currentUser; + // before reload + await firebase.auth().signInAnonymously(); + + ({ currentUser } = firebase.auth()); + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + // RELOAD + await device.reloadReactNative(); - beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); - await Utils.sleep(50); - } + // after reload + ({ currentUser } = firebase.auth()); + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + // test correct user is returned after signing + // in with a different user then reloading + await firebase.auth().signOut(); + + await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + + ({ currentUser } = firebase.auth()); + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + + // RELOAD + await device.reloadReactNative(); + + // after reload + ({ currentUser } = firebase.auth()); + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(firebase.auth().currentUser); + }).timeout(15000); + }); }); - describe('firebase.auth().currentUser', function () { - it('exists after reload', async function () { - // Detox on iOS crashing app on reloads, documented - // https://github.com/wix/detox/blob/master/docs/APIRef.DeviceObjectAPI.md#devicereloadreactnative - if (device.getPlatform() === 'ios') { - this.skip(); + describe('modular', function () { + before(async function () { + const { createUserWithEmailAndPassword, getAuth } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + try { + await clearAllUsers(); + } catch (e) { + throw e; + } + try { + await createUserWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + } catch (e) { + // they may already exist, that's fine } - let currentUser; - // before reload - await firebase.auth().signInAnonymously(); - - ({ currentUser } = firebase.auth()); - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - // RELOAD - await device.reloadReactNative(); - - // after reload - ({ currentUser } = firebase.auth()); - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - should.equal(currentUser.toJSON().email, null); - currentUser.isAnonymous.should.equal(true); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - // test correct user is returned after signing - // in with a different user then reloading - await firebase.auth().signOut(); - - await firebase.auth().signInWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - - ({ currentUser } = firebase.auth()); - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql(TEST_EMAIL); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - - // RELOAD - await device.reloadReactNative(); - - // after reload - ({ currentUser } = firebase.auth()); - currentUser.should.be.an.Object(); - currentUser.uid.should.be.a.String(); - currentUser.toJSON().should.be.an.Object(); - currentUser.toJSON().email.should.eql(TEST_EMAIL); - currentUser.isAnonymous.should.equal(false); - currentUser.providerId.should.equal('firebase'); - currentUser.should.equal(firebase.auth().currentUser); - }).timeout(15000); + }); + + beforeEach(async function () { + const { getAuth, signOut } = authModular; + + const defaultApp = firebase.app(); + const defaultAuth = getAuth(defaultApp); + + if (defaultAuth.currentUser) { + await signOut(defaultAuth); + await Utils.sleep(50); + } + }); + + describe('firebase.auth().currentUser', function () { + it('exists after reload', async function () { + let { getAuth, signOut, signInAnonymously, signInWithEmailAndPassword } = authModular; + + let defaultAuth = getAuth(firebase.app()); + + // Detox on iOS crashing app on reloads, documented + // https://github.com/wix/detox/blob/master/docs/APIRef.DeviceObjectAPI.md#devicereloadreactnative + if (device.getPlatform() === 'ios') { + this.skip(); + } + let currentUser; + // before reload + await signInAnonymously(defaultAuth); + + currentUser = defaultAuth.currentUser; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + // RELOAD + await device.reloadReactNative(); + // After reload, we have to fetch the exported methods and auth again... + ({ getAuth, signOut, signInAnonymously, signInWithEmailAndPassword } = authModular); + defaultAuth = getAuth(firebase.app()); + + // after reload + currentUser = defaultAuth.currentUser; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + should.equal(currentUser.toJSON().email, null); + currentUser.isAnonymous.should.equal(true); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + // test correct user is returned after signing + // in with a different user then reloading + await signOut(defaultAuth); + + await signInWithEmailAndPassword(defaultAuth, TEST_EMAIL, TEST_PASS); + + currentUser = defaultAuth.currentUser; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + + // RELOAD + await device.reloadReactNative(); + // defaultAuth = getAuth(firebase.app()); + + // after reload + currentUser = defaultAuth.currentUser; + currentUser.should.be.an.Object(); + currentUser.uid.should.be.a.String(); + currentUser.toJSON().should.be.an.Object(); + currentUser.toJSON().email.should.eql(TEST_EMAIL); + currentUser.isAnonymous.should.equal(false); + currentUser.providerId.should.equal('firebase'); + currentUser.should.equal(defaultAuth.currentUser); + }).timeout(15000); + }); }); }); diff --git a/packages/auth/e2e/user.e2e.js b/packages/auth/e2e/user.e2e.js index 9e1bcdcace..dbf9e823b1 100644 --- a/packages/auth/e2e/user.e2e.js +++ b/packages/auth/e2e/user.e2e.js @@ -10,954 +10,2107 @@ const { } = require('./helpers'); describe('auth().currentUser', function () { - before(async function () { - try { - await clearAllUsers(); - } catch (e) { - throw e; - } - firebase.auth().settings.appVerificationDisabledForTesting = true; - try { - await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); - } catch (e) { - // they may already exist, that's fine - } - }); + describe('firebase v8 compatibility', function () { + before(async function () { + try { + await clearAllUsers(); + } catch (e) { + throw e; + } + firebase.auth().settings.appVerificationDisabledForTesting = true; + try { + await firebase.auth().createUserWithEmailAndPassword(TEST_EMAIL, TEST_PASS); + } catch (e) { + // they may already exist, that's fine + } + }); + + beforeEach(async function () { + if (firebase.auth().currentUser) { + await firebase.auth().signOut(); + await Utils.sleep(50); + } + }); + + describe('getIdToken()', function () { + it('should return a token', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + + const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); + + // Test + const token = await user.getIdToken(); + + // Assertions + token.should.be.a.String(); + token.length.should.be.greaterThan(24); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); + + describe('getIdTokenResult()', function () { + it('should return a valid IdTokenResult Object', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + + const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); + + // Test + const tokenResult = await user.getIdTokenResult(); + + tokenResult.token.should.be.a.String(); + tokenResult.authTime.should.be.a.String(); + tokenResult.issuedAtTime.should.be.a.String(); + tokenResult.expirationTime.should.be.a.String(); + + new Date(tokenResult.authTime).toString().should.not.equal('Invalid Date'); + new Date(tokenResult.issuedAtTime).toString().should.not.equal('Invalid Date'); + new Date(tokenResult.expirationTime).toString().should.not.equal('Invalid Date'); + + tokenResult.claims.should.be.a.Object(); + tokenResult.claims.iat.should.be.a.Number(); + tokenResult.claims.exp.should.be.a.Number(); + tokenResult.claims.iss.should.be.a.String(); + + new Date(tokenResult.issuedAtTime).getTime().should.equal(tokenResult.claims.iat * 1000); + new Date(tokenResult.expirationTime).getTime().should.equal(tokenResult.claims.exp * 1000); + + tokenResult.signInProvider.should.equal('password'); + tokenResult.token.length.should.be.greaterThan(24); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); + + describe('linkWithCredential()', function () { + // hanging against auth emulator? + it('should link anonymous account <-> email account', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await firebase.auth().signInAnonymously(); + const { currentUser } = firebase.auth(); + + // Test + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + + const linkedUserCredential = await currentUser.linkWithCredential(credential); + + // Assertions + const linkedUser = linkedUserCredential.user; + linkedUser.should.be.an.Object(); + linkedUser.should.equal(firebase.auth().currentUser); + linkedUser.email.toLowerCase().should.equal(email.toLowerCase()); + linkedUser.isAnonymous.should.equal(false); + linkedUser.providerId.should.equal('firebase'); + linkedUser.providerData.should.be.an.Array(); + linkedUser.providerData.length.should.equal(1); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + + it('should error on link anon <-> email if email already exists', async function () { + await firebase.auth().signInAnonymously(); + const { currentUser } = firebase.auth(); + + // Test + try { + const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); + await currentUser.linkWithCredential(credential); + + // Clean up + await firebase.auth().signOut(); + + // Reject + return Promise.reject(new Error('Did not error on link')); + } catch (error) { + // Assertions + error.code.should.equal('auth/email-already-in-use'); + error.message.should.containEql( + 'The email address is already in use by another account.', + ); + + // Clean up + await firebase.auth().currentUser.delete(); + } + + return Promise.resolve(); + }); + }); + + describe('reauthenticateWithCredential()', function () { + it('should reauthenticate correctly', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await firebase.auth().createUserWithEmailAndPassword(email, pass); + + // Test + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + + await firebase.auth().currentUser.reauthenticateWithCredential(credential); + + // Assertions + const { currentUser } = firebase.auth(); + currentUser.email.should.equal(email.toLowerCase()); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); + + describe('reload()', function () { + it('should not error', async function () { + await firebase.auth().signInAnonymously(); + + try { + await firebase.auth().currentUser.reload(); + await firebase.auth().signOut(); + } catch (error) { + // Reject + await firebase.auth().signOut(); + return Promise.reject(new Error('reload() caused an error', error)); + } + + return Promise.resolve(); + }); + }); + + describe('sendEmailVerification()', function () { + it('should error if actionCodeSettings.url is not present', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({}); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.url is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({ url: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase + .auth() + .currentUser.sendEmailVerification({ url: 'string', dynamicLinkDomain: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.dynamicLinkDomain' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if handleCodeInApp is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase + .auth() + .currentUser.sendEmailVerification({ url: 'string', handleCodeInApp: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.handleCodeInApp' expected a boolean value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.iOS is not an object', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({ url: 'string', iOS: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase + .auth() + .currentUser.sendEmailVerification({ url: 'string', iOS: { bundleId: 123 } }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.iOS.bundleId' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android is not an object', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({ url: 'string', android: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.android' expected an object value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.packageName is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase + .auth() + .currentUser.sendEmailVerification({ url: 'string', android: { packageName: 123 } }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.packageName' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.installApp is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({ + url: 'string', + android: { packageName: 'packageName', installApp: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.installApp' expected a boolean value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.sendEmailVerification({ + url: 'string', + android: { packageName: 'packageName', minimumVersion: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.minimumVersion' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should not error', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + + try { + await firebase.auth().currentUser.sendEmailVerification(); + } catch (error) { + return Promise.reject(new Error('sendEmailVerification() caused an error', error)); + } finally { + await firebase.auth().currentUser.delete(); + } + + return Promise.resolve(); + }); + + it('should correctly report emailVerified status', async function () { + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + firebase.auth().currentUser.email.should.equal(email); + firebase.auth().currentUser.emailVerified.should.equal(false); + + try { + await firebase.auth().currentUser.sendEmailVerification(); + const { oobCode } = await getLastOob(email); + await verifyEmail(oobCode); + firebase.auth().currentUser.emailVerified.should.equal(false); + await firebase.auth().currentUser.reload(); + firebase.auth().currentUser.emailVerified.should.equal(true); + } catch (error) { + return Promise.reject(new Error('sendEmailVerification() caused an error', error)); + } finally { + await firebase.auth().currentUser.delete(); + } + + return Promise.resolve(); + }); + + it('should work with actionCodeSettings', async function () { + const actionCodeSettings = { + handleCodeInApp: true, + url: 'https://react-native-firebase-testing.firebaseapp.com/foo', + }; + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + + try { + await firebase.auth().currentUser.sendEmailVerification(actionCodeSettings); + } catch (error) { + return Promise.reject( + new Error('sendEmailVerification(actionCodeSettings) error' + error.message), + ); + } finally { + await firebase.auth().currentUser.delete(); + } + + return Promise.resolve(); + }); + + it('should throw an error within an invalid action code url', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await firebase.auth().createUserWithEmailAndPassword(email, random); + + (() => { + firebase.auth().currentUser.sendEmailVerification({ url: [] }); + }).should.throw( + "firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.url' expected a string value.", + ); + }); + }); + + describe('verifyBeforeUpdateEmail()', function () { + it('should error if newEmail is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(123); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'newEmail' expected a string value"); + } + await firebase.auth().currentUser.delete(); + }); + it('should error if actionCodeSettings.url is not present', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {}); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.url is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { url: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + dynamicLinkDomain: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.dynamicLinkDomain' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if handleCodeInApp is not a boolean', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + handleCodeInApp: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.handleCodeInApp' expected a boolean value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.iOS is not an object', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + iOS: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + iOS: { bundleId: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.iOS.bundleId' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android is not an object', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + android: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.android' expected an object value."); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.packageName is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + android: { packageName: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.packageName' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.installApp is not a boolean', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + android: { packageName: 'string', installApp: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.installApp' expected a boolean value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { + url: 'string', + android: { packageName: 'string', minimumVersion: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.minimumVersion' expected a string value.", + ); + } + await firebase.auth().currentUser.delete(); + }); + + // FIXME ios+android failing with an internal error against auth emulator + // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] + xit('should not error', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + + try { + await firebase.auth().createUserWithEmailAndPassword(email, random); + await firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail); + } catch (e) { + return Promise.reject("'verifyBeforeUpdateEmail()' did not work"); + } + await firebase.auth().currentUser.delete(); + }); + + // FIXME ios+android failing with an internal error against auth emulator + // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] + xit('should work with actionCodeSettings', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + const actionCodeSettings = { + url: 'https://react-native-firebase-testing.firebaseapp.com/foo', + }; + try { + await firebase.auth().createUserWithEmailAndPassword(email, random); + await firebase + .auth() + .currentUser.verifyBeforeUpdateEmail(updateEmail, actionCodeSettings); + await firebase.auth().currentUser.delete(); + } catch (error) { + try { + await firebase.auth().currentUser.delete(); + } catch (e) { + consle.log(e); + /* do nothing */ + } + + return Promise.reject( + "'verifyBeforeUpdateEmail()' with 'actionCodeSettings' did not work", + ); + } + return Promise.resolve(); + }); + }); + describe('unlink()', function () { + it('should unlink the email address', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await firebase.auth().signInAnonymously(); + const { currentUser } = firebase.auth(); + + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + await currentUser.linkWithCredential(credential); + + // Test + await currentUser.unlink(firebase.auth.EmailAuthProvider.PROVIDER_ID); + + // Assertions + const unlinkedUser = firebase.auth().currentUser; + unlinkedUser.providerData.should.be.an.Array(); + unlinkedUser.providerData.length.should.equal(0); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); + + describe('updateEmail()', function () { + it('should update the email address', async function () { + const random = Utils.randString(12, '#a'); + const random2 = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const email2 = `${random2}@${random2}.com`; + // Setup + await firebase.auth().createUserWithEmailAndPassword(email, random); + firebase.auth().currentUser.email.should.equal(email); + + // Update user email + await firebase.auth().currentUser.updateEmail(email2); + + // Assertions + firebase.auth().currentUser.email.should.equal(email2); + + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); + + describe('updatePhoneNumber()', function () { + it('should update the phone number', async function () { + const testPhone = await getRandomPhoneNumber(); + const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); + const smsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(smsCode); + + firebase.auth().currentUser.phoneNumber.should.equal(testPhone); + + const newPhone = await getRandomPhoneNumber(); + const newPhoneVerificationId = await new Promise((resolve, reject) => { + firebase + .auth() + .verifyPhoneNumber(newPhone) + .on('state_changed', phoneAuthSnapshot => { + if (phoneAuthSnapshot.error) { + reject(phoneAuthSnapshot.error); + } else { + resolve(phoneAuthSnapshot.verificationId); + } + }); + }); + + try { + const newSmsCode = await getLastSmsCode(newPhone); + const credential = await firebase.auth.PhoneAuthProvider.credential( + newPhoneVerificationId, + newSmsCode, + ); + + //Update with number? + await firebase + .auth() + .currentUser.updatePhoneNumber(credential) + .then($ => $); + } catch (e) { + throw e; + } + + firebase.auth().currentUser.phoneNumber.should.equal(newPhone); + }); + }); + + describe('updatePassword()', function () { + it('should update the password', async function () { + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + const pass2 = random2; - beforeEach(async function () { - if (firebase.auth().currentUser) { - await firebase.auth().signOut(); - await Utils.sleep(50); - } - }); + // Setup + await firebase.auth().createUserWithEmailAndPassword(email, pass); + + // Update user password + await firebase.auth().currentUser.updatePassword(pass2); - describe('getIdToken()', function () { - it('should return a token', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; + // Sign out + await firebase.auth().signOut(); - const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); + // Log in with the new password + await firebase.auth().signInWithEmailAndPassword(email, pass2); - // Test - const token = await user.getIdToken(); + // Assertions + firebase.auth().currentUser.should.be.an.Object(); + firebase.auth().currentUser.email.should.equal(email.toLowerCase()); - // Assertions - token.should.be.a.String(); - token.length.should.be.greaterThan(24); + // Clean up + await firebase.auth().currentUser.delete(); + }); - // Clean up - await firebase.auth().currentUser.delete(); + // Can only be run/reproduced locally with Firebase Auth rate limits lowered on the Firebase console. + xit('should throw too many requests when limit has been reached', async function () { + await Utils.sleep(10000); + try { + // Setup for creating new accounts + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const promises = []; + + // Create 10 accounts to force the error + [...Array(10).keys()].map($ => + promises.push( + new Promise(r => r(firebase.auth().createUserWithEmailAndPassword(email + $, pass))), + ), + ); + + await Promise.all(promises); + + return Promise.reject('Should have rejected'); + } catch (ex) { + ex.code.should.equal('auth/too-many-requests'); + return Promise.resolve(); + } + }); }); - }); - describe('getIdTokenResult()', function () { - it('should return a valid IdTokenResult Object', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; + describe('updateProfile()', function () { + it('should update the profile', async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + const displayName = random; + const photoURL = `http://${random}.com/${random}.jpg`; - const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); + // Setup + await firebase.auth().createUserWithEmailAndPassword(email, pass); - // Test - const tokenResult = await user.getIdTokenResult(); + // Update user profile + await firebase.auth().currentUser.updateProfile({ + displayName, + photoURL, + }); - tokenResult.token.should.be.a.String(); - tokenResult.authTime.should.be.a.String(); - tokenResult.issuedAtTime.should.be.a.String(); - tokenResult.expirationTime.should.be.a.String(); + // Assertions + const user = firebase.auth().currentUser; + user.should.be.an.Object(); + user.email.should.equal(email.toLowerCase()); + user.displayName.should.equal(displayName); + user.photoURL.should.equal(photoURL); - new Date(tokenResult.authTime).toString().should.not.equal('Invalid Date'); - new Date(tokenResult.issuedAtTime).toString().should.not.equal('Invalid Date'); - new Date(tokenResult.expirationTime).toString().should.not.equal('Invalid Date'); + // Clean up + await firebase.auth().currentUser.delete(); + }); - tokenResult.claims.should.be.a.Object(); - tokenResult.claims.iat.should.be.a.Number(); - tokenResult.claims.exp.should.be.a.Number(); - tokenResult.claims.iss.should.be.a.String(); + it('should return a valid profile when signing in anonymously', async function () { + // Setup + await firebase.auth().signInAnonymously(); + const { currentUser } = firebase.auth(); - new Date(tokenResult.issuedAtTime).getTime().should.equal(tokenResult.claims.iat * 1000); - new Date(tokenResult.expirationTime).getTime().should.equal(tokenResult.claims.exp * 1000); + // Assertions + currentUser.should.be.an.Object(); + should.equal(currentUser.email, null); + should.equal(currentUser.displayName, null); + should.equal(currentUser.emailVerified, false); + should.equal(currentUser.isAnonymous, true); + should.equal(currentUser.phoneNumber, null); + should.equal(currentUser.photoURL, null); + should.exist(currentUser.metadata.lastSignInTime); + should.exist(currentUser.metadata.creationTime); + should.deepEqual(currentUser.providerData, []); + should.exist(currentUser.providerId); + should.exist(currentUser.uid); - tokenResult.signInProvider.should.equal('password'); - tokenResult.token.length.should.be.greaterThan(24); + // Clean up + await firebase.auth().currentUser.delete(); + }); + }); - // Clean up - await firebase.auth().currentUser.delete(); + describe('linkWithPhoneNumber()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.linkWithPhoneNumber(); + }).should.throw( + 'firebase.auth.User.linkWithPhoneNumber() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); }); - }); - describe('linkWithCredential()', function () { - // hanging against auth emulator? - it('should link anonymous account <-> email account', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; + describe('linkWithPopup()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.linkWithPopup(); + }).should.throw( + 'firebase.auth.User.linkWithPopup() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); + }); - await firebase.auth().signInAnonymously(); - const { currentUser } = firebase.auth(); + describe('linkWithRedirect()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.linkWithRedirect(); + }).should.throw( + 'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); + }); - // Test - const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + describe('reauthenticateWithPhoneNumber()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.reauthenticateWithPhoneNumber(); + }).should.throw( + 'firebase.auth.User.reauthenticateWithPhoneNumber() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); + }); - const linkedUserCredential = await currentUser.linkWithCredential(credential); + describe('reauthenticateWithPopup()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.reauthenticateWithPopup(); + }).should.throw( + 'firebase.auth.User.reauthenticateWithPopup() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); + }); - // Assertions - const linkedUser = linkedUserCredential.user; - linkedUser.should.be.an.Object(); - linkedUser.should.equal(firebase.auth().currentUser); - linkedUser.email.toLowerCase().should.equal(email.toLowerCase()); - linkedUser.isAnonymous.should.equal(false); - linkedUser.providerId.should.equal('firebase'); - linkedUser.providerData.should.be.an.Array(); - linkedUser.providerData.length.should.equal(1); + describe('reauthenticateWithRedirect()', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => { + firebase.auth().currentUser.reauthenticateWithRedirect(); + }).should.throw( + 'firebase.auth.User.reauthenticateWithRedirect() is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); + }); - // Clean up - await firebase.auth().currentUser.delete(); + describe('refreshToken', function () { + it('should throw an unsupported error', async function () { + await firebase.auth().signInAnonymously(); + (() => firebase.auth().currentUser.refreshToken).should.throw( + 'firebase.auth.User.refreshToken is unsupported by the native Firebase SDKs.', + ); + await firebase.auth().signOut(); + }); }); - it('should error on link anon <-> email if email already exists', async function () { - await firebase.auth().signInAnonymously(); - const { currentUser } = firebase.auth(); + describe('user.metadata', function () { + it("should have the properties 'lastSignInTime' & 'creationTime' which are ISO strings", async function () { + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; - // Test - try { - const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); - await currentUser.linkWithCredential(credential); + const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); - // Clean up - await firebase.auth().signOut(); + const { metadata } = user; - // Reject - return Promise.reject(new Error('Did not error on link')); - } catch (error) { - // Assertions - error.code.should.equal('auth/email-already-in-use'); - error.message.should.containEql('The email address is already in use by another account.'); + should(metadata.lastSignInTime).be.a.String(); + should(metadata.creationTime).be.a.String(); + + new Date(metadata.lastSignInTime).getFullYear().should.equal(new Date().getFullYear()); + new Date(metadata.creationTime).getFullYear().should.equal(new Date().getFullYear()); - // Clean up await firebase.auth().currentUser.delete(); + }); + }); + }); + + describe('modular', function () { + before(async function () { + const { getAuth, createUserWithEmailAndPassword } = authModular; + const auth = getAuth(); + try { + await clearAllUsers(); + } catch (e) { + throw e; + } + auth.settings.appVerificationDisabledForTesting = true; + try { + await createUserWithEmailAndPassword(auth, TEST_EMAIL, TEST_PASS); + } catch (e) { + // they may already exist, that's fine } + }); + + beforeEach(async function () { + const { getAuth, signOut } = authModular; + const auth = getAuth(); - return Promise.resolve(); + if (auth.currentUser) { + await signOut(auth); + await Utils.sleep(50); + } }); - }); - describe('reauthenticateWithCredential()', function () { - it('should reauthenticate correctly', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; + describe('getIdToken()', function () { + it('should return a token', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, getIdToken } = authModular; + const auth = getAuth(); - await firebase.auth().createUserWithEmailAndPassword(email, pass); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; - // Test - const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + const { user } = await createUserWithEmailAndPassword(auth, email, random); - await firebase.auth().currentUser.reauthenticateWithCredential(credential); + // Test + const token = await getIdToken(user); - // Assertions - const { currentUser } = firebase.auth(); - currentUser.email.should.equal(email.toLowerCase()); + // Assertions + token.should.be.a.String(); + token.length.should.be.greaterThan(24); - // Clean up - await firebase.auth().currentUser.delete(); + // Clean up + await deleteUser(auth.currentUser); + }); }); - }); - describe('reload()', function () { - it('should not error', async function () { - await firebase.auth().signInAnonymously(); + describe('getIdTokenResult()', function () { + it('should return a valid IdTokenResult Object', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, getIdTokenResult } = + authModular; + const auth = getAuth(); - try { - await firebase.auth().currentUser.reload(); - await firebase.auth().signOut(); - } catch (error) { - // Reject - await firebase.auth().signOut(); - return Promise.reject(new Error('reload() caused an error', error)); - } + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; - return Promise.resolve(); - }); - }); + const { user } = await createUserWithEmailAndPassword(auth, email, random); - describe('sendEmailVerification()', function () { - it('should error if actionCodeSettings.url is not present', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({}); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.url' expected a string value."); - } - await firebase.auth().currentUser.delete(); - }); + // Test + const tokenResult = await getIdTokenResult(user); - it('should error if actionCodeSettings.url is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ url: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.url' expected a string value."); - } - await firebase.auth().currentUser.delete(); - }); + tokenResult.token.should.be.a.String(); + tokenResult.authTime.should.be.a.String(); + tokenResult.issuedAtTime.should.be.a.String(); + tokenResult.expirationTime.should.be.a.String(); - it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase - .auth() - .currentUser.sendEmailVerification({ url: 'string', dynamicLinkDomain: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.dynamicLinkDomain' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + new Date(tokenResult.authTime).toString().should.not.equal('Invalid Date'); + new Date(tokenResult.issuedAtTime).toString().should.not.equal('Invalid Date'); + new Date(tokenResult.expirationTime).toString().should.not.equal('Invalid Date'); - it('should error if handleCodeInApp is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ url: 'string', handleCodeInApp: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.handleCodeInApp' expected a boolean value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + tokenResult.claims.should.be.a.Object(); + tokenResult.claims.iat.should.be.a.Number(); + tokenResult.claims.exp.should.be.a.Number(); + tokenResult.claims.iss.should.be.a.String(); - it('should error if actionCodeSettings.iOS is not an object', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ url: 'string', iOS: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); - } - await firebase.auth().currentUser.delete(); - }); + new Date(tokenResult.issuedAtTime).getTime().should.equal(tokenResult.claims.iat * 1000); + new Date(tokenResult.expirationTime).getTime().should.equal(tokenResult.claims.exp * 1000); - it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase - .auth() - .currentUser.sendEmailVerification({ url: 'string', iOS: { bundleId: 123 } }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.iOS.bundleId' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + tokenResult.signInProvider.should.equal('password'); + tokenResult.token.length.should.be.greaterThan(24); - it('should error if actionCodeSettings.android is not an object', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ url: 'string', android: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.android' expected an object value."); - } - await firebase.auth().currentUser.delete(); + // Clean up + await deleteUser(auth.currentUser); + }); }); - it('should error if actionCodeSettings.android.packageName is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase - .auth() - .currentUser.sendEmailVerification({ url: 'string', android: { packageName: 123 } }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.packageName' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); + describe('linkWithCredential()', function () { + // hanging against auth emulator? + it('should link anonymous account <-> email account', async function () { + const { getAuth, signInAnonymously, deleteUser, linkWithCredential } = authModular; + const auth = getAuth(); + + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await signInAnonymously(auth); + const { currentUser } = auth; + + // Test + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + + const linkedUserCredential = await linkWithCredential(currentUser, credential); + + // Assertions + const linkedUser = linkedUserCredential.user; + linkedUser.should.be.an.Object(); + linkedUser.should.equal(auth.currentUser); + linkedUser.email.toLowerCase().should.equal(email.toLowerCase()); + linkedUser.isAnonymous.should.equal(false); + linkedUser.providerId.should.equal('firebase'); + linkedUser.providerData.should.be.an.Array(); + linkedUser.providerData.length.should.equal(1); + + // Clean up + await deleteUser(currentUser); + }); + + it('should error on link anon <-> email if email already exists', async function () { + const { getAuth, signInAnonymously, deleteUser, linkWithCredential } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + const { currentUser } = auth; + + // Test + try { + const credential = firebase.auth.EmailAuthProvider.credential(TEST_EMAIL, TEST_PASS); + await linkWithCredential(currentUser, credential); + + // Clean up + await deleteUser(currentUser); + + // Reject + return Promise.reject(new Error('Did not error on link')); + } catch (error) { + // Assertions + error.code.should.equal('auth/email-already-in-use'); + error.message.should.containEql( + 'The email address is already in use by another account.', + ); + + // Clean up + await deleteUser(currentUser); + } + + return Promise.resolve(); + }); }); - it('should error if actionCodeSettings.android.installApp is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ - url: 'string', - android: { packageName: 'packageName', installApp: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.installApp' expected a boolean value.", - ); - } - await firebase.auth().currentUser.delete(); + describe('reauthenticateWithCredential()', function () { + it('should reauthenticate correctly', async function () { + const { + getAuth, + createUserWithEmailAndPassword, + deleteUser, + reauthenticateWithCredential, + } = authModular; + const auth = getAuth(); + + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await createUserWithEmailAndPassword(auth, email, pass); + + // Test + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + + await reauthenticateWithCredential(auth.currentUser, credential); + + // Assertions + const { currentUser } = auth; + currentUser.email.should.equal(email.toLowerCase()); + + // Clean up + await deleteUser(currentUser); + }); }); - it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.sendEmailVerification({ - url: 'string', - android: { packageName: 'packageName', minimumVersion: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.minimumVersion' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); + describe('reload()', function () { + it('should not error', async function () { + const { getAuth, signInAnonymously, signOut, reload } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + + try { + await reload(auth.currentUser); + await signOut(auth); + } catch (error) { + // Reject + await signOut(auth); + return Promise.reject(new Error('reload() caused an error', error)); + } + + return Promise.resolve(); + }); }); - it('should not error', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); + describe('sendEmailVerification()', function () { + it('should error if actionCodeSettings.url is not present', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; - try { - await firebase.auth().currentUser.sendEmailVerification(); - } catch (error) { - return Promise.reject(new Error('sendEmailVerification() caused an error', error)); - } finally { - await firebase.auth().currentUser.delete(); - } + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, {}); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.url is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 'string', dynamicLinkDomain: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.dynamicLinkDomain' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should error if handleCodeInApp is not a boolean', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 'string', handleCodeInApp: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.handleCodeInApp' expected a boolean value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.iOS is not an object', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 'string', iOS: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 'string', iOS: { bundleId: 123 } }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.iOS.bundleId' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.android is not an object', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { url: 'string', android: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.android' expected an object value."); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.android.packageName is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { + url: 'string', + android: { packageName: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.packageName' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.android.installApp is not a boolean', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { + url: 'string', + android: { packageName: 'packageName', installApp: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.installApp' expected a boolean value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await sendEmailVerification(auth.currentUser, { + url: 'string', + android: { packageName: 'packageName', minimumVersion: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.minimumVersion' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); + + it('should not error', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + + try { + await sendEmailVerification(auth.currentUser); + } catch (error) { + return Promise.reject(new Error('sendEmailVerification() caused an error', error)); + } finally { + await deleteUser(auth.currentUser); + } + + return Promise.resolve(); + }); + + it('should correctly report emailVerified status', async function () { + const { + getAuth, + createUserWithEmailAndPassword, + deleteUser, + sendEmailVerification, + reload, + } = authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); + auth.currentUser.email.should.equal(email); + auth.currentUser.emailVerified.should.equal(false); + + try { + await sendEmailVerification(auth.currentUser); + const { oobCode } = await getLastOob(email); + await verifyEmail(oobCode); + auth.currentUser.emailVerified.should.equal(false); + await reload(auth.currentUser); + auth.currentUser.emailVerified.should.equal(true); + } catch (error) { + return Promise.reject(new Error('sendEmailVerification() caused an error', error)); + } finally { + await deleteUser(auth.currentUser); + } + + return Promise.resolve(); + }); + + it('should work with actionCodeSettings', async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser, sendEmailVerification } = + authModular; + const actionCodeSettings = { + handleCodeInApp: true, + url: 'https://react-native-firebase-testing.firebaseapp.com/foo', + }; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); - return Promise.resolve(); - }); + try { + await sendEmailVerification(auth.currentUser, actionCodeSettings); + } catch (error) { + return Promise.reject( + new Error('sendEmailVerification(actionCodeSettings) error' + error.message), + ); + } finally { + await deleteUser(auth.currentUser); + } - it('should correctly report emailVerified status', async function () { - const random = Utils.randString(12, '#a'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - firebase.auth().currentUser.email.should.equal(email); - firebase.auth().currentUser.emailVerified.should.equal(false); + return Promise.resolve(); + }); - try { - await firebase.auth().currentUser.sendEmailVerification(); - const { oobCode } = await getLastOob(email); - await verifyEmail(oobCode); - firebase.auth().currentUser.emailVerified.should.equal(false); - await firebase.auth().currentUser.reload(); - firebase.auth().currentUser.emailVerified.should.equal(true); - } catch (error) { - return Promise.reject(new Error('sendEmailVerification() caused an error', error)); - } finally { - await firebase.auth().currentUser.delete(); - } + it('should throw an error within an invalid action code url', async function () { + const { getAuth, createUserWithEmailAndPassword, sendEmailVerification } = authModular; - return Promise.resolve(); - }); + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + await createUserWithEmailAndPassword(auth, email, random); - it('should work with actionCodeSettings', async function () { - const actionCodeSettings = { - handleCodeInApp: true, - url: 'https://react-native-firebase-testing.firebaseapp.com/foo', - }; - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); + try { + await sendEmailVerification(auth.currentUser, { url: [] }); + } catch (error) { + error.message.should.containEql( + "firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.url' expected a string value.", + ); + } + }); + }); - try { - await firebase.auth().currentUser.sendEmailVerification(actionCodeSettings); - } catch (error) { - return Promise.reject( - new Error('sendEmailVerification(actionCodeSettings) error' + error.message), - ); - } finally { - await firebase.auth().currentUser.delete(); - } + describe('verifyBeforeUpdateEmail()', function () { + it('should error if newEmail is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; - return Promise.resolve(); - }); + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; - it('should throw an error within an invalid action code url', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, 123); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'newEmail' expected a string value"); + } + await deleteUser(auth.currentUser); + }); - (() => { - firebase.auth().currentUser.sendEmailVerification({ url: [] }); - }).should.throw( - "firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.url' expected a string value.", - ); - }); - }); + it('should error if actionCodeSettings.url is not present', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; - describe('verifyBeforeUpdateEmail()', function () { - it('should error if newEmail is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(123); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'newEmail' expected a string value"); - } - await firebase.auth().currentUser.delete(); - }); - it('should error if actionCodeSettings.url is not present', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, {}); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await deleteUser(auth.currentUser); + }); - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {}); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.url' expected a string value."); - } - await firebase.auth().currentUser.delete(); - }); + it('should error if actionCodeSettings.url is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; - it('should error if actionCodeSettings.url is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { url: 123 }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.url' expected a string value."); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { url: 123 }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.url' expected a string value."); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - dynamicLinkDomain: 123, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.dynamicLinkDomain' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + dynamicLinkDomain: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.dynamicLinkDomain' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); - it('should error if handleCodeInApp is not a boolean', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if handleCodeInApp is not a boolean', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - handleCodeInApp: 123, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.handleCodeInApp' expected a boolean value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + handleCodeInApp: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.handleCodeInApp' expected a boolean value.", + ); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.iOS is not an object', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.iOS is not an object', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - iOS: 123, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + iOS: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.iOS' expected an object value."); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.iOS.bundleId is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - iOS: { bundleId: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.iOS.bundleId' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + iOS: { bundleId: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.iOS.bundleId' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.android is not an object', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.android is not an object', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - android: 123, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql("'actionCodeSettings.android' expected an object value."); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + android: 123, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql("'actionCodeSettings.android' expected an object value."); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.android.packageName is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.android.packageName is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - android: { packageName: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.packageName' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + android: { packageName: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.packageName' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.android.installApp is not a boolean', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.android.installApp is not a boolean', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - android: { packageName: 'string', installApp: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.installApp' expected a boolean value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + android: { packageName: 'string', installApp: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.installApp' expected a boolean value.", + ); + } + await deleteUser(auth.currentUser); + }); - it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + it('should error if actionCodeSettings.android.minimumVersion is not a string', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - await firebase.auth().createUserWithEmailAndPassword(email, random); - try { - firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { - url: 'string', - android: { packageName: 'string', minimumVersion: 123 }, - }); - return Promise.reject(new Error('it did not error')); - } catch (error) { - error.message.should.containEql( - "'actionCodeSettings.android.minimumVersion' expected a string value.", - ); - } - await firebase.auth().currentUser.delete(); - }); + await createUserWithEmailAndPassword(auth, email, random); + try { + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, { + url: 'string', + android: { packageName: 'string', minimumVersion: 123 }, + }); + return Promise.reject(new Error('it did not error')); + } catch (error) { + error.message.should.containEql( + "'actionCodeSettings.android.minimumVersion' expected a string value.", + ); + } + await deleteUser(auth.currentUser); + }); - // FIXME ios+android failing with an internal error against auth emulator - // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] - xit('should not error', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; + // FIXME ios+android failing with an internal error against auth emulator + // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] + xit('should not error', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; - try { - await firebase.auth().createUserWithEmailAndPassword(email, random); - await firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail); - } catch (e) { - return Promise.reject("'verifyBeforeUpdateEmail()' did not work"); - } - await firebase.auth().currentUser.delete(); - }); - - // FIXME ios+android failing with an internal error against auth emulator - // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] - xit('should work with actionCodeSettings', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const updateEmail = `${random2}@${random2}.com`; - const actionCodeSettings = { - url: 'https://react-native-firebase-testing.firebaseapp.com/foo', - }; - try { - await firebase.auth().createUserWithEmailAndPassword(email, random); - await firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, actionCodeSettings); - await firebase.auth().currentUser.delete(); - } catch (error) { try { - await firebase.auth().currentUser.delete(); + await createUserWithEmailAndPassword(auth, email, random); + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail); } catch (e) { - consle.log(e); - /* do nothing */ + return Promise.reject("'verifyBeforeUpdateEmail()' did not work"); } + await deleteUser(auth.currentUser); + }); - return Promise.reject("'verifyBeforeUpdateEmail()' with 'actionCodeSettings' did not work"); - } - return Promise.resolve(); + // FIXME ios+android failing with an internal error against auth emulator + // com.google.firebase.FirebaseException: An internal error has occurred. [ VERIFY_AND_CHANGE_EMAIL ] + xit('should work with actionCodeSettings', async function () { + const { getAuth, createUserWithEmailAndPassword, verifyBeforeUpdateEmail, deleteUser } = + authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const updateEmail = `${random2}@${random2}.com`; + const actionCodeSettings = { + url: 'https://react-native-firebase-testing.firebaseapp.com/foo', + }; + try { + await createUserWithEmailAndPassword(auth, email, random); + await verifyBeforeUpdateEmail(auth.currentUser, updateEmail, actionCodeSettings); + await deleteUser(auth.currentUser); + } catch (error) { + try { + await deleteUser(auth.currentUser); + } catch (e) { + consle.log(e); + /* do nothing */ + } + + return Promise.reject( + "'verifyBeforeUpdateEmail()' with 'actionCodeSettings' did not work", + ); + } + return Promise.resolve(); + }); }); - }); - describe('unlink()', function () { - it('should unlink the email address', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - await firebase.auth().signInAnonymously(); - const { currentUser } = firebase.auth(); + describe('unlink()', function () { + it('should unlink the email address', async function () { + const { getAuth, signInAnonymously, linkWithCredential, unlink, deleteUser } = authModular; + const auth = getAuth(); - const credential = firebase.auth.EmailAuthProvider.credential(email, pass); - await currentUser.linkWithCredential(credential); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + await signInAnonymously(auth); - // Test - await currentUser.unlink(firebase.auth.EmailAuthProvider.PROVIDER_ID); + const credential = firebase.auth.EmailAuthProvider.credential(email, pass); + await linkWithCredential(auth.currentUser, credential); - // Assertions - const unlinkedUser = firebase.auth().currentUser; - unlinkedUser.providerData.should.be.an.Array(); - unlinkedUser.providerData.length.should.equal(0); + // Test + await unlink(auth.currentUser, firebase.auth.EmailAuthProvider.PROVIDER_ID); - // Clean up - await firebase.auth().currentUser.delete(); + // Assertions + const unlinkedUser = auth.currentUser; + unlinkedUser.providerData.should.be.an.Array(); + unlinkedUser.providerData.length.should.equal(0); + + // Clean up + await deleteUser(auth.currentUser); + }); }); - }); - describe('updateEmail()', function () { - it('should update the email address', async function () { - const random = Utils.randString(12, '#a'); - const random2 = Utils.randString(12, '#a'); - const email = `${random}@${random}.com`; - const email2 = `${random2}@${random2}.com`; - // Setup - await firebase.auth().createUserWithEmailAndPassword(email, random); - firebase.auth().currentUser.email.should.equal(email); + describe('updateEmail()', function () { + it('should update the email address', async function () { + const { getAuth, updateEmail, deleteUser, createUserWithEmailAndPassword } = authModular; + const auth = getAuth(); + + const random = Utils.randString(12, '#a'); + const random2 = Utils.randString(12, '#a'); + const email = `${random}@${random}.com`; + const email2 = `${random2}@${random2}.com`; + // Setup + await createUserWithEmailAndPassword(auth, email, random); + auth.currentUser.email.should.equal(email); - // Update user email - await firebase.auth().currentUser.updateEmail(email2); + // Update user email + await updateEmail(auth.currentUser, email2); - // Assertions - firebase.auth().currentUser.email.should.equal(email2); + // Assertions + auth.currentUser.email.should.equal(email2); - // Clean up - await firebase.auth().currentUser.delete(); + // Clean up + await deleteUser(auth.currentUser); + }); }); - }); - describe('updatePhoneNumber()', function () { - it('should update the phone number', async function () { - const testPhone = await getRandomPhoneNumber(); - const confirmResult = await firebase.auth().signInWithPhoneNumber(testPhone); - const smsCode = await getLastSmsCode(testPhone); - await confirmResult.confirm(smsCode); - - firebase.auth().currentUser.phoneNumber.should.equal(testPhone); - - const newPhone = await getRandomPhoneNumber(); - const newPhoneVerificationId = await new Promise((resolve, reject) => { - firebase - .auth() - .verifyPhoneNumber(newPhone) - .on('state_changed', phoneAuthSnapshot => { + describe('updatePhoneNumber()', function () { + it('should update the phone number', async function () { + const { getAuth, signInWithPhoneNumber, updatePhoneNumber, verifyPhoneNumber } = + authModular; + const auth = getAuth(); + + const testPhone = await getRandomPhoneNumber(); + const confirmResult = await signInWithPhoneNumber(auth, testPhone); + const smsCode = await getLastSmsCode(testPhone); + await confirmResult.confirm(smsCode); + + auth.currentUser.phoneNumber.should.equal(testPhone); + + const newPhone = await getRandomPhoneNumber(); + const newPhoneVerificationId = await new Promise((resolve, reject) => { + verifyPhoneNumber(auth, newPhone).on('state_changed', phoneAuthSnapshot => { if (phoneAuthSnapshot.error) { reject(phoneAuthSnapshot.error); } else { resolve(phoneAuthSnapshot.verificationId); } }); + }); + + try { + const newSmsCode = await getLastSmsCode(newPhone); + const credential = firebase.auth.PhoneAuthProvider.credential( + newPhoneVerificationId, + newSmsCode, + ); + + // Update with number? + await updatePhoneNumber(auth.currentUser, credential); + } catch (e) { + throw e; + } + + auth.currentUser.phoneNumber.should.equal(newPhone); }); + }); - try { - const newSmsCode = await getLastSmsCode(newPhone); - const credential = await firebase.auth.PhoneAuthProvider.credential( - newPhoneVerificationId, - newSmsCode, - ); + describe('updatePassword()', function () { + it('should update the password', async function () { + const { + getAuth, + createUserWithEmailAndPassword, + updatePassword, + signOut, + signInWithEmailAndPassword, + deleteUser, + } = authModular; + const auth = getAuth(); - //Update with number? - await firebase - .auth() - .currentUser.updatePhoneNumber(credential) - .then($ => $); - } catch (e) { - throw e; - } + const random = Utils.randString(12, '#aA'); + const random2 = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + const pass2 = random2; - firebase.auth().currentUser.phoneNumber.should.equal(newPhone); - }); - }); + // Setup + await createUserWithEmailAndPassword(auth, email, pass); - describe('updatePassword()', function () { - it('should update the password', async function () { - const random = Utils.randString(12, '#aA'); - const random2 = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - const pass2 = random2; + // Update user password + await updatePassword(auth.currentUser, pass2); - // Setup - await firebase.auth().createUserWithEmailAndPassword(email, pass); + // Sign out + await signOut(auth); - // Update user password - await firebase.auth().currentUser.updatePassword(pass2); + // Log in with the new password + await signInWithEmailAndPassword(auth, email, pass2); - // Sign out - await firebase.auth().signOut(); + // Assertions + auth.currentUser.should.be.an.Object(); + auth.currentUser.email.should.equal(email.toLowerCase()); - // Log in with the new password - await firebase.auth().signInWithEmailAndPassword(email, pass2); + // Clean up + await deleteUser(auth.currentUser); + }); - // Assertions - firebase.auth().currentUser.should.be.an.Object(); - firebase.auth().currentUser.email.should.equal(email.toLowerCase()); + // Can only be run/reproduced locally with Firebase Auth rate limits lowered on the Firebase console. + xit('should throw too many requests when limit has been reached', async function () { + const { getAuth, createUserWithEmailAndPassword } = authModular; + const auth = getAuth(); - // Clean up - await firebase.auth().currentUser.delete(); + await Utils.sleep(10000); + try { + // Setup for creating new accounts + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; + const pass = random; + + const promises = []; + + // Create 10 accounts to force the error + [...Array(10).keys()].map($ => + promises.push( + new Promise(r => r(createUserWithEmailAndPassword(auth, email + $, pass))), + ), + ); + + await Promise.all(promises); + + return Promise.reject('Should have rejected'); + } catch (ex) { + ex.code.should.equal('auth/too-many-requests'); + return Promise.resolve(); + } + }); }); - // Can only be run/reproduced locally with Firebase Auth rate limits lowered on the Firebase console. - xit('should throw too many requests when limit has been reached', async function () { - await Utils.sleep(10000); - try { - // Setup for creating new accounts + describe('updateProfile()', function () { + it('should update the profile', async function () { + const { getAuth, createUserWithEmailAndPassword, updateProfile, deleteUser } = authModular; + const auth = getAuth(); + const random = Utils.randString(12, '#aA'); const email = `${random}@${random}.com`; const pass = random; + const displayName = random; + const photoURL = `http://${random}.com/${random}.jpg`; - const promises = []; + // Setup + await createUserWithEmailAndPassword(auth, email, pass); - // Create 10 accounts to force the error - [...Array(10).keys()].map($ => - promises.push( - new Promise(r => r(firebase.auth().createUserWithEmailAndPassword(email + $, pass))), - ), - ); + // Update user profile + await updateProfile(auth.currentUser, { + displayName, + photoURL, + }); - await Promise.all(promises); + // Assertions + const user = auth.currentUser; + user.should.be.an.Object(); + user.email.should.equal(email.toLowerCase()); + user.displayName.should.equal(displayName); + user.photoURL.should.equal(photoURL); - return Promise.reject('Should have rejected'); - } catch (ex) { - ex.code.should.equal('auth/too-many-requests'); - return Promise.resolve(); - } - }); - }); + // Clean up + await deleteUser(auth.currentUser); + }); + + it('should return a valid profile when signing in anonymously', async function () { + const { getAuth, signInAnonymously, deleteUser } = authModular; + const auth = getAuth(); + + // Setup + await signInAnonymously(auth); + const currentUser = auth.currentUser; + + // Assertions + currentUser.should.be.an.Object(); + should.equal(currentUser.email, null); + should.equal(currentUser.displayName, null); + should.equal(currentUser.emailVerified, false); + should.equal(currentUser.isAnonymous, true); + should.equal(currentUser.phoneNumber, null); + should.equal(currentUser.photoURL, null); + should.exist(currentUser.metadata.lastSignInTime); + should.exist(currentUser.metadata.creationTime); + should.deepEqual(currentUser.providerData, []); + should.exist(currentUser.providerId); + should.exist(currentUser.uid); - describe('updateProfile()', function () { - it('should update the profile', async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; - const pass = random; - const displayName = random; - const photoURL = `http://${random}.com/${random}.jpg`; - - // Setup - await firebase.auth().createUserWithEmailAndPassword(email, pass); - - // Update user profile - await firebase.auth().currentUser.updateProfile({ - displayName, - photoURL, - }); - - // Assertions - const user = firebase.auth().currentUser; - user.should.be.an.Object(); - user.email.should.equal(email.toLowerCase()); - user.displayName.should.equal(displayName); - user.photoURL.should.equal(photoURL); - - // Clean up - await firebase.auth().currentUser.delete(); - }); - - it('should return a valid profile when signing in anonymously', async function () { - // Setup - await firebase.auth().signInAnonymously(); - const { currentUser } = firebase.auth(); - - // Assertions - currentUser.should.be.an.Object(); - should.equal(currentUser.email, null); - should.equal(currentUser.displayName, null); - should.equal(currentUser.emailVerified, false); - should.equal(currentUser.isAnonymous, true); - should.equal(currentUser.phoneNumber, null); - should.equal(currentUser.photoURL, null); - should.exist(currentUser.metadata.lastSignInTime); - should.exist(currentUser.metadata.creationTime); - should.deepEqual(currentUser.providerData, []); - should.exist(currentUser.providerId); - should.exist(currentUser.uid); - - // Clean up - await firebase.auth().currentUser.delete(); + // Clean up + await deleteUser(auth.currentUser); + }); }); - }); - describe('linkWithPhoneNumber()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.linkWithPhoneNumber(); - }).should.throw( - 'firebase.auth.User.linkWithPhoneNumber() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('linkWithPhoneNumber()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, linkWithPhoneNumber } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + + try { + await linkWithPhoneNumber(auth.currentUser); + } catch (e) { + e.message.should.containEql( + 'linkWithPhoneNumber is unsupported by the native Firebase SDKs', + ); + } + await signOut(auth); + }); }); - }); - describe('linkWithPopup()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.linkWithPopup(); - }).should.throw( - 'firebase.auth.User.linkWithPopup() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('linkWithPopup()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, linkWithPopup } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + try { + await linkWithPopup(auth.currentUser); + } catch (e) { + e.message.should.containEql('linkWithPopup is unsupported by the native Firebase SDKs'); + } + await signOut(auth); + }); }); - }); - describe('linkWithRedirect()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.linkWithRedirect(); - }).should.throw( - 'firebase.auth.User.linkWithRedirect() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('linkWithRedirect()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, linkWithRedirect } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + await signInAnonymously(auth); + try { + await linkWithRedirect(auth.currentUser); + } catch (e) { + e.message.should.containEql( + 'linkWithRedirect is unsupported by the native Firebase SDKs', + ); + } + await signOut(auth); + }); }); - }); - describe('reauthenticateWithPhoneNumber()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.reauthenticateWithPhoneNumber(); - }).should.throw( - 'firebase.auth.User.reauthenticateWithPhoneNumber() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('reauthenticateWithPhoneNumber()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, reauthenticateWithPhoneNumber } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + try { + await reauthenticateWithPhoneNumber(auth.currentUser); + } catch (e) { + e.message.should.containEql( + 'reauthenticateWithPhoneNumber is unsupported by the native Firebase SDKs', + ); + } + await signOut(auth); + }); }); - }); - describe('reauthenticateWithPopup()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.reauthenticateWithPopup(); - }).should.throw( - 'firebase.auth.User.reauthenticateWithPopup() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('reauthenticateWithPopup()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, reauthenticateWithPopup } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + try { + await reauthenticateWithPopup(auth.currentUser); + } catch (e) { + e.message.should.containEql( + 'reauthenticateWithPopup is unsupported by the native Firebase SDKs', + ); + } + await signOut(auth); + }); }); - }); - describe('reauthenticateWithRedirect()', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => { - firebase.auth().currentUser.reauthenticateWithRedirect(); - }).should.throw( - 'firebase.auth.User.reauthenticateWithRedirect() is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('reauthenticateWithRedirect()', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut, reauthenticateWithRedirect } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + try { + await reauthenticateWithRedirect(auth.currentUser); + } catch (e) { + e.message.should.containEql( + 'reauthenticateWithRedirect is unsupported by the native Firebase SDKs', + ); + } + await signOut(auth); + }); }); - }); - describe('refreshToken', function () { - it('should throw an unsupported error', async function () { - await firebase.auth().signInAnonymously(); - (() => firebase.auth().currentUser.refreshToken).should.throw( - 'firebase.auth.User.refreshToken is unsupported by the native Firebase SDKs.', - ); - await firebase.auth().signOut(); + describe('refreshToken', function () { + it('should throw an unsupported error', async function () { + const { getAuth, signInAnonymously, signOut } = authModular; + const auth = getAuth(); + + await signInAnonymously(auth); + (() => auth.currentUser.refreshToken).should.throw( + 'firebase.auth.User.refreshToken is unsupported by the native Firebase SDKs.', + ); + await signOut(auth); + }); }); - }); - describe('user.metadata', function () { - it("should have the properties 'lastSignInTime' & 'creationTime' which are ISO strings", async function () { - const random = Utils.randString(12, '#aA'); - const email = `${random}@${random}.com`; + describe('user.metadata', function () { + it("should have the properties 'lastSignInTime' & 'creationTime' which are ISO strings", async function () { + const { getAuth, createUserWithEmailAndPassword, deleteUser } = authModular; + const auth = getAuth(); - const { user } = await firebase.auth().createUserWithEmailAndPassword(email, random); + const random = Utils.randString(12, '#aA'); + const email = `${random}@${random}.com`; - const { metadata } = user; + const { user } = await createUserWithEmailAndPassword(auth, email, random); - should(metadata.lastSignInTime).be.a.String(); - should(metadata.creationTime).be.a.String(); + const { metadata } = user; - new Date(metadata.lastSignInTime).getFullYear().should.equal(new Date().getFullYear()); - new Date(metadata.creationTime).getFullYear().should.equal(new Date().getFullYear()); + should(metadata.lastSignInTime).be.a.String(); + should(metadata.creationTime).be.a.String(); - await firebase.auth().currentUser.delete(); + new Date(metadata.lastSignInTime).getFullYear().should.equal(new Date().getFullYear()); + new Date(metadata.creationTime).getFullYear().should.equal(new Date().getFullYear()); + + await deleteUser(auth.currentUser); + }); }); }); }); diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index 2540233a94..fd7cf89b80 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -18,17 +18,23 @@ import { isAndroid, isBoolean, - isString, isNull, + isString, isValidUrl, } from '@react-native-firebase/app/lib/common'; import { - createModuleNamespace, FirebaseModule, + createModuleNamespace, getFirebaseRoot, } from '@react-native-firebase/app/lib/internal'; import ConfirmationResult from './ConfirmationResult'; import PhoneAuthListener from './PhoneAuthListener'; +import PhoneMultiFactorGenerator from './PhoneMultiFactorGenerator'; +import Settings from './Settings'; +import User from './User'; +import { getMultiFactorResolver } from './getMultiFactorResolver'; +import { MultiFactorUser, multiFactor } from './multiFactor'; +import AppleAuthProvider from './providers/AppleAuthProvider'; import EmailAuthProvider from './providers/EmailAuthProvider'; import FacebookAuthProvider from './providers/FacebookAuthProvider'; import GithubAuthProvider from './providers/GithubAuthProvider'; @@ -36,14 +42,77 @@ import GoogleAuthProvider from './providers/GoogleAuthProvider'; import OAuthProvider from './providers/OAuthProvider'; import OIDCAuthProvider from './providers/OIDCAuthProvider'; import PhoneAuthProvider from './providers/PhoneAuthProvider'; -import PhoneMultiFactorGenerator from './PhoneMultiFactorGenerator'; import TwitterAuthProvider from './providers/TwitterAuthProvider'; -import AppleAuthProvider from './providers/AppleAuthProvider'; -import Settings from './Settings'; -import User from './User'; import version from './version'; -import { getMultiFactorResolver } from './getMultiFactorResolver'; -import { multiFactor, MultiFactorUser } from './multiFactor'; + +export { + applyActionCode, + beforeAuthStateChanged, + checkActionCode, + confirmPasswordReset, + connectAuthEmulator, + createUserWithEmailAndPassword, + deleteUser, + fetchSignInMethodsForEmail, + getAdditionalUserInfo, + getAuth, + getIdToken, + getIdTokenResult, + getMultiFactorResolver, + getRedirectResult, + initializeAuth, + isSignInWithEmailLink, + linkWithCredential, + linkWithPhoneNumber, + linkWithPopup, + linkWithRedirect, + multiFactor, + onAuthStateChanged, + onIdTokenChanged, + parseActionCodeURL, + reauthenticateWithCredential, + reauthenticateWithPhoneNumber, + reauthenticateWithPopup, + reauthenticateWithRedirect, + reload, + sendEmailVerification, + sendPasswordResetEmail, + sendSignInLinkToEmail, + setPersistence, + signInAnonymously, + signInWithCredential, + signInWithCustomToken, + signInWithEmailAndPassword, + signInWithEmailLink, + signInWithPhoneNumber, + signInWithPopup, + signInWithRedirect, + signOut, + unlink, + updateCurrentUser, + updateEmail, + updatePassword, + updatePhoneNumber, + updateProfile, + useDeviceLanguage, + useUserAccessGroup, + verifyBeforeUpdateEmail, + verifyPasswordResetCode, + verifyPhoneNumber, +} from './modular/index'; +// For modular imports +export { + AppleAuthProvider, + EmailAuthProvider, + PhoneAuthProvider, + GoogleAuthProvider, + GithubAuthProvider, + TwitterAuthProvider, + FacebookAuthProvider, + PhoneMultiFactorGenerator, + OAuthProvider, + OIDCAuthProvider, +}; const statics = { AppleAuthProvider, diff --git a/packages/auth/lib/modular/index.js b/packages/auth/lib/modular/index.js new file mode 100644 index 0000000000..eda21449fd --- /dev/null +++ b/packages/auth/lib/modular/index.js @@ -0,0 +1,467 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { isString } from '@react-native-firebase/app/lib/common'; +import { firebase } from '..'; + +/* + * Returns the Auth instance associated with the provided FirebaseApp. + */ +class Auth { + constructor(app) { + this.app = app ? firebase.app(app.name) : firebase.app(); + this._languageCode = this.app.auth().languageCode; + } + + get config() { + return this.app.auth().config; + } + + get currentUser() { + return this.app.auth().currentUser; + } + + get languageCode() { + return this._languageCode; + } + + set languageCode(code) { + if (code === null || isString(code)) { + this._languageCode = code; + this.app.auth().languageCode = code; + return; + } + throw new Error("expected 'languageCode' to be a string or null value"); + } + + get settings() { + return this.app.auth().settings; + } + + get tenantId() { + return this.app.auth().tenantId; + } +} + +/* + * Returns the Auth instance associated with the provided FirebaseApp. + * + * If no instance exists, initializes an Auth instance with platform-specific default dependencies. + */ +export function getAuth(app) { + return new Auth(app); +} + +function _getUnderlyingAuth(auth) { + return auth.app.auth(); +} + +/* + * This function allows more control over the Auth instance than getAuth(). + * + * getAuth uses platform-specific defaults to supply the Dependencies. + * In general, getAuth is the easiest way to initialize Auth and works for most use cases. + * Use initializeAuth if you need control over which persistence layer is used, or to minimize bundle size + * if you're not using either signInWithPopup or signInWithRedirect. + */ +export function initializeAuth(app, deps) { + return getAuth(app); +} + +/* + * Applies a verification code sent to the user by email or other out-of-band mechanism. + * + * Returns a promise that resolves when the code is applied successfully. + */ +export async function applyActionCode(auth, oobCode) { + const _auth = _getUnderlyingAuth(auth); + return _auth.applyActionCode(oobCode); +} + +/* + * Adds a blocking callback that runs before an auth state change sets a new user. + */ +export function beforeAuthStateChanged(auth, callback, onAbort) { + throw new Error('beforeAuthStateChanged is unsupported by the native Firebase SDKs'); +} + +/* + * Checks a verification code sent to the user by email or other out-of-band mechanism. + */ +export async function checkActionCode(auth, oobCode) { + const _auth = _getUnderlyingAuth(auth); + return _auth.checkActionCode(oobCode); +} + +/* + * Completes the password reset process, given a confirmation code and new password. + */ +export async function confirmPasswordReset(auth, oobCode, newPassword) { + const _auth = _getUnderlyingAuth(auth); + return _auth.confirmPasswordReset(oobCode, newPassword); +} + +/* + * Changes the Auth instance to communicate with the Firebase Auth Emulator, instead of production Firebase Auth services. + * + * This must be called synchronously immediately following the first call to initializeAuth(). Do not use with production credentials as emulator traffic is not encrypted. + */ +export function connectAuthEmulator(auth, url, options) { + const _auth = _getUnderlyingAuth(auth); + _auth.useEmulator(url, options); +} + +/* + * Creates a new user account associated with the specified email address and password. + */ +export async function createUserWithEmailAndPassword(auth, email, password) { + const _auth = _getUnderlyingAuth(auth); + return _auth.createUserWithEmailAndPassword(email, password); +} + +/* + * Gets the list of possible sign in methods for the given email address. + */ +export async function fetchSignInMethodsForEmail(auth, email) { + const _auth = _getUnderlyingAuth(auth); + return _auth.fetchSignInMethodsForEmail(email); +} + +/* + * Provides a MultiFactorResolver suitable for completion of a multi-factor flow. + */ +export function getMultiFactorResolver(auth, error) { + const _auth = _getUnderlyingAuth(auth); + return _auth.getMultiFactorResolver(error); +} + +/* + * Returns a UserCredential from the redirect-based sign-in flow. + */ +export async function getRedirectResult(auth, resolver) { + throw new Error('getRedirectResult is unsupported by the native Firebase SDKs'); +} + +/* + * Checks if an incoming link is a sign-in with email link suitable for signInWithEmailLink(). + */ +export function isSignInWithEmailLink(auth, emailLink) { + const _auth = _getUnderlyingAuth(auth); + return _auth.isSignInWithEmailLink(emailLink); +} + +/* + * Adds an observer for changes to the user's sign-in state. + */ +export function onAuthStateChanged(auth, nextOrObserver) { + const _auth = _getUnderlyingAuth(auth); + return _auth.onAuthStateChanged(nextOrObserver); +} + +/* + * Adds an observer for changes to the signed-in user's ID token. + */ +export function onIdTokenChanged(auth, nextOrObserver) { + const _auth = _getUnderlyingAuth(auth); + return _auth.onIdTokenChanged(nextOrObserver); +} + +/* + * Sends a password reset email to the given email address. + */ +export async function sendPasswordResetEmail(auth, email, actionCodeSettings) { + const _auth = _getUnderlyingAuth(auth); + return _auth.sendPasswordResetEmail(email, actionCodeSettings); +} + +/* + * Sends a sign-in email link to the user with the specified email. + */ +export async function sendSignInLinkToEmail(auth, email, actionCodeSettings) { + const _auth = _getUnderlyingAuth(auth); + return _auth.sendSignInLinkToEmail(email, actionCodeSettings); +} + +/* + * Changes the type of persistence on the Auth instance for the currently saved Auth session and applies this type of persistence for future sign-in requests, including sign-in with redirect requests. + */ +export async function setPersistence(auth, persistence) { + throw new Error('setPersistence is unsupported by the native Firebase SDKs'); +} + +/* + * Asynchronously signs in as an anonymous user. + */ +export async function signInAnonymously(auth) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInAnonymously(); +} + +/* + * Asynchronously signs in with the given credentials. + */ +export async function signInWithCredential(auth, credential) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInWithCredential(credential); +} + +/* + * Asynchronously signs in using a custom token. + */ +export async function signInWithCustomToken(auth, customToken) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInWithCustomToken(customToken); +} + +/* + * Asynchronously signs in using an email and password. + */ +export async function signInWithEmailAndPassword(auth, email, password) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInWithEmailAndPassword(email, password); +} + +/* + * Asynchronously signs in using an email and sign-in email link. + */ +export async function signInWithEmailLink(auth, email, emailLink) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInWithEmailLink(email, emailLink); +} + +/* + * Asynchronously signs in using a phone number. + */ +export async function signInWithPhoneNumber(auth, phoneNumber, appVerifier) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signInWithPhoneNumber(phoneNumber); +} + +/* + * Asynchronously signs in using a phone number. + */ +export function verifyPhoneNumber(auth, phoneNumber, autoVerifyTimeoutOrForceResend, forceResend) { + const _auth = _getUnderlyingAuth(auth); + return _auth.verifyPhoneNumber(phoneNumber, autoVerifyTimeoutOrForceResend, forceResend); +} + +/* +Authenticates a Firebase client using a popup-based OAuth authentication flow. +*/ +export async function signInWithPopup(auth, provider, resolver) { + throw new Error('signInWithPopup is unsupported by the native Firebase SDKs'); +} + +/* +Authenticates a Firebase client using a full-page redirect flow. +*/ +export async function signInWithRedirect(auth, provider, resolver) { + throw new Error('signInWithRedirect is unsupported by the native Firebase SDKs'); +} + +/* +Signs out the current user. +*/ +export async function signOut(auth) { + const _auth = _getUnderlyingAuth(auth); + return _auth.signOut(); +} + +/* +Asynchronously sets the provided user as Auth.currentUser on the Auth instance. +*/ +export async function updateCurrentUser(auth, user) { + throw new Error('updateCurrentUser is unsupported by the native Firebase SDKs'); +} + +/* +Sets the current language to the default device/browser preference. +*/ +export function useDeviceLanguage(auth) { + throw new Error('useDeviceLanguage is unsupported by the native Firebase SDKs'); +} + +/* + Sets the current language to the default device/browser preference. +*/ +export function useUserAccessGroup(auth, userAccessGroup) { + const _auth = _getUnderlyingAuth(auth); + return _auth.useUserAccessGroup(userAccessGroup); +} + +/* +Verifies the password reset code sent to the user by email or other out-of-band mechanism. +*/ +export async function verifyPasswordResetCode(auth, code) { + const _auth = _getUnderlyingAuth(auth); + return _auth.verifyPasswordResetCode(code); +} + +/* + * Parses the email action link string and returns an ActionCodeURL if the link is valid, otherwise returns null. + */ +export function parseActionCodeURL(link) { + throw new Error('parseActionCodeURL is unsupported by the native Firebase SDKs'); +} + +/* + * Deletes and signs out the user. + */ +export async function deleteUser(user) { + return user.delete(); +} + +/* + * Returns a JSON Web Token (JWT) used to identify the user to a Firebase service. + */ +export async function getIdToken(user, forceRefresh) { + return user.getIdToken(forceRefresh); +} + +/* + * Returns a deserialized JSON Web Token (JWT) used to identify the user to a Firebase service. + */ +export async function getIdTokenResult(user, forceRefresh) { + return user.getIdTokenResult(forceRefresh); +} + +/* + * Links the user account with the given credentials. + */ +export async function linkWithCredential(user, credential) { + return user.linkWithCredential(credential); +} + +/* + * Links the user account with the given phone number. + */ +export async function linkWithPhoneNumber(user, phoneNumber, appVerifier) { + throw new Error('linkWithPhoneNumber is unsupported by the native Firebase SDKs'); +} + +/* + * Links the authenticated provider to the user account using a pop-up based OAuth flow. + */ +export async function linkWithPopup(user, provider, resolver) { + throw new Error('linkWithPopup is unsupported by the native Firebase SDKs'); +} + +/* + * Links the OAuthProvider to the user account using a full-page redirect flow. + */ +export async function linkWithRedirect(user, provider, resolver) { + throw new Error('linkWithRedirect is unsupported by the native Firebase SDKs'); +} + +/* + * The MultiFactorUser corresponding to the user. + */ +export function multiFactor(user) { + return user._auth.multiFactor(user); +} + +/* + * Re-authenticates a user using a fresh credential. + */ +export async function reauthenticateWithCredential(user, credential) { + return user.reauthenticateWithCredential(credential); +} + +/* + * Re-authenticates a user using a fresh phone credential. + */ +export async function reauthenticateWithPhoneNumber(user, phoneNumber, appVerifier) { + throw new Error('reauthenticateWithPhoneNumber is unsupported by the native Firebase SDKs'); +} + +/* + * Reauthenticates the current user with the specified OAuthProvider using a pop-up based OAuth flow. + */ +export async function reauthenticateWithPopup(user, provider, resolver) { + throw new Error('reauthenticateWithPopup is unsupported by the native Firebase SDKs'); +} + +/* + * Reauthenticates the current user with the specified OAuthProvider using a full-page redirect flow. + */ +export async function reauthenticateWithRedirect(user, provider, resolver) { + throw new Error('reauthenticateWithRedirect is unsupported by the native Firebase SDKs'); +} + +/* + * Reloads user account data, if signed in. + */ +export async function reload(user) { + return user.reload(); +} + +/* + * Sends a verification email to a user. + */ +export async function sendEmailVerification(user, actionCodeSettings) { + return user.sendEmailVerification(actionCodeSettings); +} + +/* + * Unlinks a provider from a user account. + */ +export async function unlink(user, providerId) { + return user.unlink(providerId); +} + +/* + * Updates the user's email address. + */ +export async function updateEmail(user, newEmail) { + return user.updateEmail(newEmail); +} + +/* + * Updates the user's password. + */ +export async function updatePassword(user, newPassword) { + return user.updatePassword(newPassword); +} + +/* + * Updates the user's phone number. + */ +export async function updatePhoneNumber(user, credential) { + return user.updatePhoneNumber(credential); +} + +/* + * Updates a user's profile data. + */ +export async function updateProfile(user, { displayName, photoURL: photoUrl }) { + return user.updateProfile({ displayName, photoURL: photoUrl }); +} + +/* + * Sends a verification email to a new email address. + */ +export async function verifyBeforeUpdateEmail(user, newEmail, actionCodeSettings) { + return user.verifyBeforeUpdateEmail(newEmail, actionCodeSettings); +} + +/* + * Extracts provider specific AdditionalUserInfo for the given credential. + */ +export function getAdditionalUserInfo(userCredential) { + return userCredential.additionalUserInfo; +} diff --git a/packages/auth/lib/providers/PhoneAuthProvider.js b/packages/auth/lib/providers/PhoneAuthProvider.js index 898e4aa6ee..2fb238049c 100644 --- a/packages/auth/lib/providers/PhoneAuthProvider.js +++ b/packages/auth/lib/providers/PhoneAuthProvider.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* * Copyright (c) 2016-present Invertase Limited & Contributors * @@ -18,8 +19,11 @@ const providerId = 'phone'; export default class PhoneAuthProvider { - constructor() { - throw new Error('`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.'); + constructor(auth) { + if (auth === undefined) { + throw new Error('`new PhoneAuthProvider()` is not supported on the native Firebase SDKs.'); + } + this._auth = auth; } static get PROVIDER_ID() { @@ -33,4 +37,16 @@ export default class PhoneAuthProvider { providerId, }; } + + verifyPhoneNumber(phoneInfoOptions, appVerifier) { + if (phoneInfoOptions.multiFactorHint) { + return this._auth.app + .auth() + .verifyPhoneNumberWithMultiFactorInfo( + phoneInfoOptions.multiFactorHint, + phoneInfoOptions.session, + ); + } + return this._auth.app.auth().verifyPhoneNumberForMultiFactor(phoneInfoOptions); + } } diff --git a/tests/app.js b/tests/app.js index 317d73b363..517fd9f817 100644 --- a/tests/app.js +++ b/tests/app.js @@ -25,6 +25,7 @@ import * as appDistributionModular from '@react-native-firebase/app-distribution import NativeEventEmitter from '@react-native-firebase/app/lib/internal/RNFBNativeEventEmitter'; import '@react-native-firebase/app/lib/utils'; import '@react-native-firebase/auth'; +import * as authModular from '@react-native-firebase/auth'; import '@react-native-firebase/crashlytics'; import '@react-native-firebase/database'; import '@react-native-firebase/dynamic-links'; @@ -59,6 +60,7 @@ jet.exposeContextProperty('analyticsModular', analyticsModular); jet.exposeContextProperty('appDistributionModular', appDistributionModular); jet.exposeContextProperty('remoteConfigModular', remoteConfigModular); jet.exposeContextProperty('perfModular', perfModular); +jet.exposeContextProperty('authModular', authModular); jet.exposeContextProperty('appCheckModular', appCheckModular); jet.exposeContextProperty('messagingModular', messagingModular); jet.exposeContextProperty('storageModular', storageModular); diff --git a/tests/e2e/globals.js b/tests/e2e/globals.js index f52eba02dc..793859f561 100644 --- a/tests/e2e/globals.js +++ b/tests/e2e/globals.js @@ -83,6 +83,12 @@ Object.defineProperty(global, 'analyticsModular', { }, }); +Object.defineProperty(global, 'authModular', { + get() { + return jet.authModular; + }, +}); + Object.defineProperty(global, 'remoteConfigModular', { get() { return jet.remoteConfigModular;