Skip to content

Commit de58736

Browse files
committed
Add sendEmailVerification to auth-next (#2926)
* Add sendEmailVerification to auth-next * [AUTOMATED]: Prettier Code Styling * Add missing test * Remove GetOobCodeRequestType in favor of Operation
1 parent eddae49 commit de58736

File tree

7 files changed

+215
-25
lines changed

7 files changed

+215
-25
lines changed

packages-exp/auth-exp/src/api/authentication/email_and_password.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ import * as mockFetch from '../../../test/mock_fetch';
2525
import { ServerError } from '../errors';
2626
import {
2727
EmailSignInRequest,
28-
GetOobCodeRequestType,
2928
PasswordResetRequest,
3029
sendEmailVerification,
3130
sendPasswordResetEmail,
3231
sendSignInLinkToEmail,
3332
signInWithPassword,
3433
VerifyEmailRequest
3534
} from './email_and_password';
35+
import { Operation } from '../../model/action_code_info';
3636

3737
use(chaiAsPromised);
3838

@@ -91,7 +91,7 @@ describe('signInWithPassword', () => {
9191
describe('sendOobCode', () => {
9292
context('VERIFY_EMAIL', () => {
9393
const request: VerifyEmailRequest = {
94-
requestType: GetOobCodeRequestType.VERIFY_EMAIL,
94+
requestType: Operation.VERIFY_EMAIL,
9595
idToken: 'my-token'
9696
};
9797

@@ -140,7 +140,7 @@ describe('sendOobCode', () => {
140140

141141
context('PASSWORD_RESET', () => {
142142
const request: PasswordResetRequest = {
143-
requestType: GetOobCodeRequestType.PASSWORD_RESET,
143+
requestType: Operation.PASSWORD_RESET,
144144
145145
};
146146

@@ -191,7 +191,7 @@ describe('sendOobCode', () => {
191191

192192
context('EMAIL_SIGNIN', () => {
193193
const request: EmailSignInRequest = {
194-
requestType: GetOobCodeRequestType.EMAIL_SIGNIN,
194+
requestType: Operation.EMAIL_SIGNIN,
195195
196196
};
197197

packages-exp/auth-exp/src/api/authentication/email_and_password.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from '..';
2424
import { Auth } from '../../model/auth';
2525
import { IdToken, IdTokenResponse } from '../../model/id_token';
26+
import { Operation } from '../../model/action_code_info';
2627

2728
export interface SignInWithPasswordRequest {
2829
returnSecureToken?: boolean;
@@ -45,14 +46,7 @@ export async function signInWithPassword(
4546
>(auth, HttpMethod.POST, Endpoint.SIGN_IN_WITH_PASSWORD, request);
4647
}
4748

48-
export enum GetOobCodeRequestType {
49-
PASSWORD_RESET = 'PASSWORD_RESET',
50-
EMAIL_SIGNIN = 'EMAIL_SIGNIN',
51-
VERIFY_EMAIL = 'VERIFY_EMAIL',
52-
VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL'
53-
}
54-
55-
interface GetOobCodeRequest {
49+
export interface GetOobCodeRequest {
5650
email?: string; // Everything except VERIFY_AND_CHANGE_EMAIL
5751
continueUrl?: string;
5852
iosBundleId?: string;
@@ -67,19 +61,19 @@ interface GetOobCodeRequest {
6761
}
6862

6963
export interface VerifyEmailRequest extends GetOobCodeRequest {
70-
requestType: GetOobCodeRequestType.VERIFY_EMAIL;
64+
requestType: Operation.VERIFY_EMAIL;
7165
idToken: IdToken;
7266
}
7367

7468
export interface PasswordResetRequest extends GetOobCodeRequest {
75-
requestType: GetOobCodeRequestType.PASSWORD_RESET;
69+
requestType: Operation.PASSWORD_RESET;
7670
email: string;
7771
captchaResp?: string;
7872
userIp?: string;
7973
}
8074

8175
export interface EmailSignInRequest extends GetOobCodeRequest {
82-
requestType: GetOobCodeRequestType.EMAIL_SIGNIN;
76+
requestType: Operation.EMAIL_SIGNIN;
8377
email: string;
8478
}
8579

packages-exp/auth-exp/src/core/strategies/email.test.ts

+113-3
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@
1818
import { FirebaseError } from '@firebase/util';
1919
import { expect, use } from 'chai';
2020
import * as chaiAsPromised from 'chai-as-promised';
21-
import { SinonStub, stub } from 'sinon';
21+
import * as sinonChai from 'sinon-chai';
22+
import { SinonStub, stub, restore } from 'sinon';
2223
import { mockEndpoint } from '../../../test/api/helper';
23-
import { mockAuth } from '../../../test/mock_auth';
24+
import { mockAuth, testUser } from '../../../test/mock_auth';
2425
import * as mockFetch from '../../../test/mock_fetch';
2526
import { Endpoint } from '../../api';
2627
import { ServerError } from '../../api/errors';
2728
import { ProviderId } from '../providers';
2829
import * as location from '../util/location';
29-
import { fetchSignInMethodsForEmail } from './email';
30+
import { fetchSignInMethodsForEmail, sendEmailVerification } from './email';
31+
import { Operation } from '../../model/action_code_info';
3032

3133
use(chaiAsPromised);
34+
use(sinonChai);
3235

3336
describe('fetchSignInMethodsForEmail', () => {
3437
const email = '[email protected]';
@@ -94,3 +97,110 @@ describe('fetchSignInMethodsForEmail', () => {
9497
expect(mock.calls.length).to.eq(1);
9598
});
9699
});
100+
101+
describe('sendEmailVerification', () => {
102+
const email = '[email protected]';
103+
const user = testUser('my-user-uid', email);
104+
const idToken = 'id-token';
105+
let idTokenStub: SinonStub;
106+
let reloadStub: SinonStub;
107+
108+
beforeEach(() => {
109+
mockFetch.setUp();
110+
idTokenStub = stub(user, 'getIdToken');
111+
idTokenStub.callsFake(async () => idToken);
112+
reloadStub = stub(user, 'reload');
113+
});
114+
115+
afterEach(() => {
116+
mockFetch.tearDown();
117+
restore();
118+
});
119+
120+
it('should send the email verification', async () => {
121+
const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, {
122+
requestType: Operation.VERIFY_EMAIL,
123+
email
124+
});
125+
126+
await sendEmailVerification(mockAuth, user);
127+
128+
expect(reloadStub).to.not.have.been.called;
129+
expect(mock.calls[0].request).to.eql({
130+
requestType: Operation.VERIFY_EMAIL,
131+
idToken
132+
});
133+
});
134+
135+
it('should reload the user if the API returns a different email', async () => {
136+
const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, {
137+
requestType: Operation.VERIFY_EMAIL,
138+
139+
});
140+
141+
await sendEmailVerification(mockAuth, user);
142+
143+
expect(reloadStub).to.have.been.calledOnce;
144+
expect(mock.calls[0].request).to.eql({
145+
requestType: Operation.VERIFY_EMAIL,
146+
idToken
147+
});
148+
});
149+
150+
context('on iOS', () => {
151+
it('should pass action code parameters', async () => {
152+
const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, {
153+
requestType: Operation.VERIFY_EMAIL,
154+
email
155+
});
156+
await sendEmailVerification(mockAuth, user, {
157+
handleCodeInApp: true,
158+
iOS: {
159+
bundleId: 'my-bundle',
160+
appStoreId: 'my-appstore-id'
161+
},
162+
url: 'my-url',
163+
dynamicLinkDomain: 'fdl-domain'
164+
});
165+
166+
expect(mock.calls[0].request).to.eql({
167+
requestType: Operation.VERIFY_EMAIL,
168+
idToken,
169+
continueUrl: 'my-url',
170+
dynamicLinkDomain: 'fdl-domain',
171+
canHandleCodeInApp: true,
172+
iosBundleId: 'my-bundle',
173+
iosAppStoreId: 'my-appstore-id'
174+
});
175+
});
176+
});
177+
178+
context('on Android', () => {
179+
it('should pass action code parameters', async () => {
180+
const mock = mockEndpoint(Endpoint.SEND_OOB_CODE, {
181+
requestType: Operation.VERIFY_EMAIL,
182+
email
183+
});
184+
await sendEmailVerification(mockAuth, user, {
185+
handleCodeInApp: true,
186+
android: {
187+
installApp: false,
188+
minimumVersion: 'my-version',
189+
packageName: 'my-package'
190+
},
191+
url: 'my-url',
192+
dynamicLinkDomain: 'fdl-domain'
193+
});
194+
expect(mock.calls[0].request).to.eql({
195+
requestType: Operation.VERIFY_EMAIL,
196+
idToken,
197+
continueUrl: 'my-url',
198+
dynamicLinkDomain: 'fdl-domain',
199+
canHandleCodeInApp: true,
200+
androidInstallApp: false,
201+
androidMinimumVersionCode: 'my-version',
202+
androidPackageName: 'my-package'
203+
});
204+
});
205+
});
206+
});

packages-exp/auth-exp/src/core/strategies/email.ts

+26-4
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
import {
19-
createAuthUri,
20-
CreateAuthUriRequest
21-
} from '../../api/authentication/create_auth_uri';
18+
import { createAuthUri, CreateAuthUriRequest } from '../../api/authentication/create_auth_uri';
19+
import * as api from '../../api/authentication/email_and_password';
20+
import { Operation } from '../../model/action_code_info';
21+
import { ActionCodeSettings, setActionCodeSettingsOnRequest } from '../../model/action_code_settings';
2222
import { Auth } from '../../model/auth';
23+
import { User } from '../../model/user';
2324
import { getCurrentUrl, isHttpOrHttps } from '../util/location';
2425

2526
export async function fetchSignInMethodsForEmail(
@@ -39,3 +40,24 @@ export async function fetchSignInMethodsForEmail(
3940

4041
return signinMethods || [];
4142
}
43+
44+
export async function sendEmailVerification(
45+
auth: Auth,
46+
user: User,
47+
actionCodeSettings?: ActionCodeSettings
48+
): Promise<void> {
49+
const idToken = await user.getIdToken();
50+
const request: api.VerifyEmailRequest = {
51+
requestType: Operation.VERIFY_EMAIL,
52+
idToken
53+
};
54+
if (actionCodeSettings) {
55+
setActionCodeSettingsOnRequest(request, actionCodeSettings);
56+
}
57+
58+
const { email } = await api.sendEmailVerification(auth, request);
59+
60+
if (email !== user.email) {
61+
await user.reload();
62+
}
63+
}

packages-exp/auth-exp/src/model/action_code_info.d.ts renamed to packages-exp/auth-exp/src/model/action_code_info.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,14 @@ export enum Operation {
1919
PASSWORD_RESET = 'PASSWORD_RESET',
2020
RECOVER_EMAIL = 'RECOVER_EMAIL',
2121
EMAIL_SIGNIN = 'EMAIL_SIGNIN',
22-
VERIFY_EMAIL = 'VERIFY_EMAIL'
22+
VERIFY_EMAIL = 'VERIFY_EMAIL',
23+
VERIFY_AND_CHANGE_EMAIL = 'VERIFY_AND_CHANGE_EMAIL'
24+
}
25+
26+
export interface ActionCodeInfo {
27+
data: {
28+
email: string | null;
29+
fromEmail: string | null;
30+
};
31+
operation: string;
2332
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 { GetOobCodeRequest } from '../api/authentication/email_and_password';
19+
20+
export interface ActionCodeSettings {
21+
android?: {
22+
installApp?: boolean;
23+
minimumVersion?: string;
24+
packageName: string;
25+
};
26+
handleCodeInApp?: boolean;
27+
iOS?: {
28+
bundleId: string;
29+
appStoreId: string;
30+
};
31+
url: string;
32+
dynamicLinkDomain?: string;
33+
}
34+
35+
export function setActionCodeSettingsOnRequest(
36+
request: GetOobCodeRequest,
37+
actionCodeSettings: ActionCodeSettings
38+
): void {
39+
request.continueUrl = actionCodeSettings.url;
40+
request.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain;
41+
request.canHandleCodeInApp = actionCodeSettings.handleCodeInApp;
42+
43+
if (actionCodeSettings.iOS) {
44+
request.iosBundleId = actionCodeSettings.iOS.bundleId;
45+
request.iosAppStoreId = actionCodeSettings.iOS.appStoreId;
46+
}
47+
48+
if (actionCodeSettings.android) {
49+
request.androidInstallApp = actionCodeSettings.android.installApp;
50+
request.androidMinimumVersionCode =
51+
actionCodeSettings.android.minimumVersion;
52+
request.androidPackageName = actionCodeSettings.android.packageName;
53+
}
54+
}

packages-exp/auth-exp/test/mock_auth.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ export const mockAuth: Auth = {
3434
}
3535
};
3636

37-
export function testUser(uid: string): User {
37+
export function testUser(uid: string, email?: string): User {
3838
return new UserImpl({
3939
uid,
4040
auth: mockAuth,
41-
stsTokenManager: new StsTokenManager()
41+
stsTokenManager: new StsTokenManager(),
42+
email
4243
});
4344
}

0 commit comments

Comments
 (0)