Skip to content

Commit e2efd27

Browse files
authored
Add integration tests for phone, plus address some issues caught by them (#3478)
* Phone integration tests (plus some bugs that it caught) * Formatting * PR feedback * Formatting
1 parent ec2e8a0 commit e2efd27

File tree

3 files changed

+194
-3
lines changed

3 files changed

+194
-3
lines changed

packages-exp/auth-exp/index.browser.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export { indexedDBLocalPersistence } from './src/core/persistence/indexed_db';
3131
export {
3232
signInWithPhoneNumber,
3333
linkWithPhoneNumber,
34-
reauthenticateWithPhoneNumber
34+
reauthenticateWithPhoneNumber,
35+
updatePhoneNumber
3536
} from './src/core/strategies/phone';
3637
export {
3738
signInWithPopup,

packages-exp/auth-exp/src/platform_browser/recaptcha/recaptcha_verifier.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ export class RecaptchaVerifier
6161
private readonly parameters: Parameters = {
6262
...DEFAULT_PARAMS
6363
},
64-
auth?: Auth | null
64+
auth?: externs.Auth | null
6565
) {
66-
this.auth = auth || (initializeAuth() as Auth);
66+
this.auth = (auth || initializeAuth()) as Auth;
6767
this.appName = this.auth.name;
6868
this.isInvisible = this.parameters.size === 'invisible';
6969
const container =
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* @license
3+
* Copyright 2020 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect, use } from 'chai';
19+
import * as chaiAsPromised from 'chai-as-promised';
20+
21+
import {
22+
linkWithPhoneNumber,
23+
PhoneAuthProvider,
24+
reauthenticateWithPhoneNumber,
25+
RecaptchaVerifier,
26+
signInAnonymously,
27+
signInWithPhoneNumber,
28+
unlink,
29+
updatePhoneNumber
30+
} from '@firebase/auth-exp/index.browser';
31+
import {
32+
Auth,
33+
OperationType,
34+
ProviderId,
35+
UserCredential
36+
} from '@firebase/auth-types-exp';
37+
import { FirebaseError } from '@firebase/util';
38+
39+
import {
40+
cleanUpTestInstance,
41+
getTestInstance
42+
} from '../../helpers/integration/helpers';
43+
44+
use(chaiAsPromised);
45+
46+
// NOTE: These tests don't use a real phone number. In order to run these tests
47+
// you must whitelist the following phone numbers as "testing" numbers in the
48+
// auth console
49+
// https://console.firebase.google.com/u/0/project/_/authentication/providers
50+
// • +1 (555) 555-1000, SMS code 123456
51+
// • +1 (555) 555-2000, SMS code 654321
52+
53+
const PHONE_A = {
54+
number: '+15555551000',
55+
code: '123456'
56+
};
57+
58+
const PHONE_B = {
59+
number: '+15555552000',
60+
code: '654321'
61+
};
62+
63+
describe('Integration test: phone auth', () => {
64+
let auth: Auth;
65+
let verifier: RecaptchaVerifier;
66+
let fakeRecaptchaContainer: HTMLElement;
67+
68+
beforeEach(() => {
69+
auth = getTestInstance();
70+
fakeRecaptchaContainer = document.createElement('div');
71+
document.body.appendChild(fakeRecaptchaContainer);
72+
verifier = new RecaptchaVerifier(fakeRecaptchaContainer, undefined, auth);
73+
});
74+
75+
afterEach(async () => {
76+
await cleanUpTestInstance(auth);
77+
document.body.removeChild(fakeRecaptchaContainer);
78+
});
79+
80+
it('allows user to sign up', async () => {
81+
const cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
82+
const userCred = await cr.confirm(PHONE_A.code);
83+
84+
expect(auth.currentUser).to.eq(userCred.user);
85+
expect(userCred.operationType).to.eq(OperationType.SIGN_IN);
86+
87+
const user = userCred.user;
88+
expect(user.isAnonymous).to.be.false;
89+
expect(user.uid).to.be.a('string');
90+
expect(user.phoneNumber).to.eq(PHONE_A.number);
91+
});
92+
93+
it('anonymous users can link (and unlink) phone number', async () => {
94+
const { user } = await signInAnonymously(auth);
95+
const { uid: anonId } = user;
96+
97+
const cr = await linkWithPhoneNumber(user, PHONE_A.number, verifier);
98+
const linkResult = await cr.confirm(PHONE_A.code);
99+
expect(linkResult.operationType).to.eq(OperationType.LINK);
100+
expect(linkResult.user.uid).to.eq(user.uid);
101+
expect(linkResult.user.phoneNumber).to.eq(PHONE_A.number);
102+
103+
await unlink(user, ProviderId.PHONE);
104+
expect(auth.currentUser!.uid).to.eq(anonId);
105+
expect(auth.currentUser!.isAnonymous).to.be.true;
106+
expect(auth.currentUser!.phoneNumber).to.be.null;
107+
});
108+
109+
context('with already-created user', () => {
110+
let signUpCred: UserCredential;
111+
112+
function resetVerifier(): void {
113+
verifier.clear();
114+
verifier = new RecaptchaVerifier(fakeRecaptchaContainer, undefined, auth);
115+
}
116+
117+
beforeEach(async () => {
118+
const cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
119+
signUpCred = await cr.confirm(PHONE_A.code);
120+
resetVerifier();
121+
await auth.signOut();
122+
});
123+
124+
it('allows the user to sign in again', async () => {
125+
const cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
126+
const signInCred = await cr.confirm(PHONE_A.code);
127+
128+
expect(signInCred.user.uid).to.eq(signUpCred.user.uid);
129+
});
130+
131+
it('allows the user to update their phone number', async () => {
132+
let cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
133+
const { user } = await cr.confirm(PHONE_A.code);
134+
135+
resetVerifier();
136+
137+
const provider = new PhoneAuthProvider(auth);
138+
const verificationId = await provider.verifyPhoneNumber(
139+
PHONE_B.number,
140+
verifier
141+
);
142+
143+
await updatePhoneNumber(
144+
user,
145+
PhoneAuthProvider.credential(verificationId, PHONE_B.code)
146+
);
147+
expect(user.phoneNumber).to.eq(PHONE_B.number);
148+
149+
await auth.signOut();
150+
resetVerifier();
151+
152+
cr = await signInWithPhoneNumber(auth, PHONE_B.number, verifier);
153+
const { user: secondSignIn } = await cr.confirm(PHONE_B.code);
154+
expect(secondSignIn.uid).to.eq(user.uid);
155+
});
156+
157+
it('allows the user to reauthenticate with phone number', async () => {
158+
let cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
159+
const { user } = await cr.confirm(PHONE_A.code);
160+
const oldToken = user.refreshToken;
161+
162+
resetVerifier();
163+
164+
cr = await reauthenticateWithPhoneNumber(user, PHONE_A.number, verifier);
165+
await cr.confirm(PHONE_A.code);
166+
167+
expect(user.refreshToken).not.to.eq(oldToken);
168+
});
169+
170+
it('prevents reauthentication with wrong phone number', async () => {
171+
let cr = await signInWithPhoneNumber(auth, PHONE_A.number, verifier);
172+
const { user } = await cr.confirm(PHONE_A.code);
173+
174+
resetVerifier();
175+
176+
cr = await reauthenticateWithPhoneNumber(user, PHONE_B.number, verifier);
177+
await expect(cr.confirm(PHONE_B.code)).to.be.rejectedWith(
178+
FirebaseError,
179+
'auth/user-mismatch'
180+
);
181+
182+
// We need to manually delete PHONE_B number since a failed
183+
// reauthenticateWithPhoneNumber does not trigger a state change
184+
resetVerifier();
185+
cr = await signInWithPhoneNumber(auth, PHONE_B.number, verifier);
186+
const { user: otherUser } = await cr.confirm(PHONE_B.code);
187+
await otherUser.delete();
188+
});
189+
});
190+
});

0 commit comments

Comments
 (0)