Skip to content

Commit b5365c7

Browse files
sam-gcavolkovi
authored andcommitted
Add reauthenticateWithCredential, reauthenticateWithPhoneNumber (#3225)
1 parent 2592c8e commit b5365c7

File tree

15 files changed

+262
-64
lines changed

15 files changed

+262
-64
lines changed

packages-exp/auth-exp/demo/src/index.js

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ import {
4040
signInWithEmailAndPassword,
4141
sendSignInLinkToEmail,
4242
sendPasswordResetEmail,
43+
EmailAuthProvider,
4344
verifyPasswordResetCode,
4445
confirmPasswordReset,
4546
linkWithCredential,
47+
reauthenticateWithCredential,
4648
unlink
4749
} from '@firebase/auth-exp';
4850

@@ -373,17 +375,14 @@ function onLinkWithEmailLink() {
373375
* Re-authenticate a user with email link credential.
374376
*/
375377
function onReauthenticateWithEmailLink() {
376-
alertNotImplemented();
377-
// var email = $('#link-with-email-link-email').val();
378-
// var link = $('#link-with-email-link-link').val() || undefined;
379-
// var credential = firebase.auth.EmailAuthProvider
380-
// .credentialWithLink(email, link);
381-
// activeUser().reauthenticateWithCredential(credential)
382-
// .then(function(result) {
383-
// logAdditionalUserInfo(result);
384-
// refreshUserData();
385-
// alertSuccess('User reauthenticated!');
386-
// }, onAuthError);
378+
var email = $('#link-with-email-link-email').val();
379+
var link = $('#link-with-email-link-link').val() || undefined;
380+
var credential = EmailAuthProvider.credentialWithLink(email, link);
381+
reauthenticateWithCredential(activeUser(), credential).then(function(result) {
382+
logAdditionalUserInfo(result);
383+
refreshUserData();
384+
alertSuccess('User reauthenticated!');
385+
}, onAuthError);
387386
}
388387

389388
/**
@@ -558,13 +557,11 @@ function onReauthConfirmPhoneVerification() {
558557
verificationId,
559558
verificationCode
560559
);
561-
activeUser()
562-
.reauthenticateWithCredential(credential)
563-
.then(function(result) {
564-
logAdditionalUserInfo(result);
565-
refreshUserData();
566-
alertSuccess('User reauthenticated!');
567-
}, onAuthError);
560+
reauthenticateWithCredential(activeUser(), credential).then(function(result) {
561+
logAdditionalUserInfo(result);
562+
refreshUserData();
563+
alertSuccess('User reauthenticated!');
564+
}, onAuthError);
568565
}
569566

570567
/**

packages-exp/auth-exp/src/core/credentials/anonymous.test.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,18 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19-
import * as mockFetch from '../../../test/mock_fetch';
2018
import { expect, use } from 'chai';
2119
import * as chaiAsPromised from 'chai-as-promised';
22-
import { testAuth } from '../../../test/mock_auth';
23-
import { Auth } from '../../model/auth';
24-
import { AnonymousCredential } from './anonymous';
20+
21+
import { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
22+
2523
import { mockEndpoint } from '../../../test/api/helper';
24+
import { testAuth } from '../../../test/mock_auth';
25+
import * as mockFetch from '../../../test/mock_fetch';
2626
import { Endpoint } from '../../api';
2727
import { APIUserInfo } from '../../api/account_management/account';
28+
import { Auth } from '../../model/auth';
29+
import { AnonymousCredential } from './anonymous';
2830

2931
use(chaiAsPromised);
3032

@@ -84,9 +86,9 @@ describe('core/credentials/anonymous', () => {
8486
});
8587
});
8688

87-
describe('#_matchIdTokenWithUid', () => {
89+
describe('#_getReauthenticationResolver', () => {
8890
it('throws', () => {
89-
expect(() => credential._matchIdTokenWithUid(auth, 'other-uid')).to.throw(
91+
expect(() => credential._getReauthenticationResolver(auth)).to.throw(
9092
Error
9193
);
9294
});

packages-exp/auth-exp/src/core/credentials/anonymous.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
*/
1717

1818
import { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19+
1920
import { signUp } from '../../api/authentication/sign_up';
2021
import { Auth } from '../../model/auth';
2122
import { IdTokenResponse } from '../../model/id_token';
2223
import { debugFail } from '../util/assert';
23-
import { AuthCredential } from '.';
24+
import { AuthCredential } from './';
2425

2526
export class AnonymousCredential implements AuthCredential {
2627
providerId = ProviderId.ANONYMOUS;
@@ -44,7 +45,7 @@ export class AnonymousCredential implements AuthCredential {
4445
debugFail("Can't link to an anonymous credential");
4546
}
4647

47-
_matchIdTokenWithUid(_auth: Auth, _uid: string): Promise<never> {
48+
_getReauthenticationResolver(_auth: Auth): Promise<never> {
4849
debugFail('Method not implemented.');
4950
}
5051
}

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
1918
import { expect, use } from 'chai';
2019
import * as chaiAsPromised from 'chai-as-promised';
20+
21+
import { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
22+
23+
import { mockEndpoint } from '../../../test/api/helper';
2124
import { testAuth } from '../../../test/mock_auth';
22-
import { Auth } from '../../model/auth';
23-
import { EmailAuthProvider } from '../providers/email';
24-
import { EmailAuthCredential } from './email';
2525
import * as mockFetch from '../../../test/mock_fetch';
26-
import { mockEndpoint } from '../../../test/api/helper';
2726
import { Endpoint } from '../../api';
2827
import { APIUserInfo } from '../../api/account_management/account';
28+
import { Auth } from '../../model/auth';
29+
import { EmailAuthProvider } from '../providers/email';
30+
import { EmailAuthCredential } from './email';
2931

3032
use(chaiAsPromised);
3133

@@ -95,11 +97,11 @@ describe('core/credentials/email', () => {
9597
});
9698
});
9799

98-
describe('#_matchIdTokenWithUid', () => {
100+
describe('#_getReauthenticationResolver', () => {
99101
it('throws', () => {
100-
expect(() =>
101-
credential._matchIdTokenWithUid(auth, 'other-uid')
102-
).to.throw(Error);
102+
expect(() => credential._getReauthenticationResolver(auth)).to.throw(
103+
Error
104+
);
103105
});
104106
});
105107
});
@@ -160,9 +162,9 @@ describe('core/credentials/email', () => {
160162

161163
describe('#_matchIdTokenWithUid', () => {
162164
it('throws', () => {
163-
expect(() =>
164-
credential._matchIdTokenWithUid(auth, 'other-uid')
165-
).to.throw(Error);
165+
expect(() => credential._getReauthenticationResolver(auth)).to.throw(
166+
Error
167+
);
166168
});
167169
});
168170
});

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
*/
1717

1818
import * as externs from '@firebase/auth-types-exp';
19+
1920
import { signInWithPassword } from '../../api/authentication/email_and_password';
2021
import { signInWithEmailLink } from '../../api/authentication/email_link';
2122
import { Auth } from '../../model/auth';
2223
import { IdTokenResponse } from '../../model/id_token';
23-
import { AuthErrorCode, AUTH_ERROR_FACTORY } from '../errors';
24+
import { AUTH_ERROR_FACTORY, AuthErrorCode } from '../errors';
2425
import { EmailAuthProvider } from '../providers/email';
2526
import { debugFail } from '../util/assert';
26-
import { AuthCredential } from '.';
27+
import { AuthCredential } from './';
2728

2829
export class EmailAuthCredential implements AuthCredential {
2930
readonly providerId = EmailAuthProvider.PROVIDER_ID;
@@ -66,7 +67,7 @@ export class EmailAuthCredential implements AuthCredential {
6667
debugFail('Method not implemented.');
6768
}
6869

69-
_matchIdTokenWithUid(_auth: Auth, _uid: string): Promise<never> {
70+
_getReauthenticationResolver(_auth: Auth): Promise<never> {
7071
debugFail('Method not implemented.');
7172
}
7273
}

packages-exp/auth-exp/src/core/credentials/index.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
*/
1717

1818
import * as externs from '@firebase/auth-types-exp';
19+
1920
import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa';
20-
import { IdTokenResponse } from '../../model/id_token';
2121
import { Auth } from '../../model/auth';
22+
import { IdTokenResponse } from '../../model/id_token';
2223

2324
export abstract class AuthCredential extends externs.AuthCredential {
2425
static fromJSON(json: object | string): AuthCredential | null;
2526

2627
_getIdTokenResponse(auth: Auth): Promise<PhoneOrOauthTokenResponse>;
2728
_linkToIdToken(auth: Auth, idToken: string): Promise<IdTokenResponse>;
28-
_matchIdTokenWithUid(auth: Auth, uid: string): Promise<IdTokenResponse>;
29+
_getReauthenticationResolver(auth: Auth): Promise<IdTokenResponse>;
2930
}

packages-exp/auth-exp/src/core/credentials/phone.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa';
2121
import {
2222
linkWithPhoneNumber,
2323
signInWithPhoneNumber,
24-
SignInWithPhoneNumberRequest
24+
SignInWithPhoneNumberRequest,
25+
verifyPhoneNumberForExisting
2526
} from '../../api/authentication/sms';
2627
import { Auth } from '../../model/auth';
2728
import { IdTokenResponse } from '../../model/id_token';
28-
import { debugFail } from '../util/assert';
29-
import { AuthCredential } from '.';
29+
import { AuthCredential } from './';
3030

3131
export interface PhoneAuthCredentialParameters {
3232
verificationId?: string;
@@ -53,10 +53,8 @@ export class PhoneAuthCredential
5353
});
5454
}
5555

56-
_matchIdTokenWithUid(auth: Auth, uid: string): Promise<IdTokenResponse> {
57-
void auth;
58-
void uid;
59-
return debugFail('not implemented');
56+
_getReauthenticationResolver(auth: Auth): Promise<IdTokenResponse> {
57+
return verifyPhoneNumberForExisting(auth, this.makeVerificationRequest());
6058
}
6159

6260
private makeVerificationRequest(): SignInWithPhoneNumberRequest {

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { FirebaseError } from '@firebase/util';
2727

2828
import { mockEndpoint } from '../../../test/api/helper';
29+
import { makeJWT } from '../../../test/jwt';
2930
import { testAuth, testUser } from '../../../test/mock_auth';
3031
import { MockAuthCredential } from '../../../test/mock_auth_credential';
3132
import * as mockFetch from '../../../test/mock_fetch';
@@ -37,6 +38,7 @@ import { User } from '../../model/user';
3738
import {
3839
_assertLinkedStatus,
3940
linkWithCredential,
41+
reauthenticateWithCredential,
4042
signInWithCredential
4143
} from './credential';
4244

@@ -105,6 +107,38 @@ describe('core/strategies/credential', () => {
105107
});
106108
});
107109

110+
describe('reauthenticateWithCredential', () => {
111+
it('should throw an error if the uid is mismatched', async () => {
112+
authCredential._setIdTokenResponse({
113+
...idTokenResponse,
114+
idToken: makeJWT({ sub: 'not-my-uid' })
115+
});
116+
117+
await expect(
118+
reauthenticateWithCredential(user, authCredential)
119+
).to.be.rejectedWith(
120+
FirebaseError,
121+
'Firebase: The supplied credentials do not correspond to the previously signed in user. (auth/user-mismatch).'
122+
);
123+
});
124+
125+
it('sould return the expected user credential', async () => {
126+
authCredential._setIdTokenResponse({
127+
...idTokenResponse,
128+
idToken: makeJWT({ sub: 'uid' })
129+
});
130+
131+
const {
132+
credential,
133+
user: newUser,
134+
operationType
135+
} = await reauthenticateWithCredential(user, authCredential);
136+
expect(operationType).to.eq(OperationType.REAUTHENTICATE);
137+
expect(newUser).to.eq(user);
138+
expect(credential).to.be.null;
139+
});
140+
});
141+
108142
describe('linkWithCredential', () => {
109143
it('should throw an error if the provider is already linked', async () => {
110144
getAccountInfoEndpoint.response = {

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

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ import { OperationType, UserCredential } from '@firebase/auth-types-exp';
2121
import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa';
2222
import { SignInWithPhoneNumberResponse } from '../../api/authentication/sms';
2323
import { Auth } from '../../model/auth';
24+
import { IdTokenResponse } from '../../model/id_token';
2425
import { User } from '../../model/user';
2526
import { AuthCredential } from '../credentials';
2627
import { PhoneAuthCredential } from '../credentials/phone';
2728
import { AuthErrorCode } from '../errors';
29+
import { _parseToken } from '../user/id_token_result';
2830
import { _reloadWithoutSaving } from '../user/reload';
2931
import { UserCredentialImpl } from '../user/user_credential_impl';
30-
import { assert } from '../util/assert';
32+
import { assert, fail } from '../util/assert';
3133
import { providerDataAsNames } from '../util/providers';
3234

3335
export async function signInWithCredential(
@@ -61,9 +63,24 @@ export async function linkWithCredential(
6163
await user.getIdToken()
6264
);
6365

64-
const newCred = _authCredentialFromTokenResponse(response);
65-
await user._updateTokensIfNecessary(response, /* reload */ true);
66-
return new UserCredentialImpl(user, newCred, OperationType.LINK);
66+
return userCredForOperation(user, OperationType.LINK, response);
67+
}
68+
69+
export async function reauthenticateWithCredential(
70+
userExtern: externs.User,
71+
credentialExtern: externs.AuthCredential
72+
): Promise<externs.UserCredential> {
73+
const credential = credentialExtern as AuthCredential;
74+
const user = userExtern as User;
75+
76+
const { auth, uid } = user;
77+
const response = await verifyTokenResponseUid(
78+
credential._getReauthenticationResolver(auth),
79+
uid,
80+
auth.name
81+
);
82+
83+
return userCredForOperation(user, OperationType.REAUTHENTICATE, response);
6784
}
6885

6986
export function _authCredentialFromTokenResponse(
@@ -95,3 +112,37 @@ export async function _assertLinkedStatus(
95112
: AuthErrorCode.NO_SUCH_PROVIDER;
96113
assert(providerIds.has(provider) === expected, user.auth.name, code);
97114
}
115+
116+
async function verifyTokenResponseUid(
117+
idTokenResolver: Promise<IdTokenResponse>,
118+
uid: string,
119+
appName: string
120+
): Promise<IdTokenResponse> {
121+
try {
122+
const response = await idTokenResolver;
123+
assert(response.idToken, appName, AuthErrorCode.INTERNAL_ERROR);
124+
const parsed = _parseToken(response.idToken);
125+
assert(parsed, appName, AuthErrorCode.INTERNAL_ERROR);
126+
127+
const { sub: localId } = parsed;
128+
assert(uid === localId, appName, AuthErrorCode.USER_MISMATCH);
129+
130+
return response;
131+
} catch (e) {
132+
// Convert user deleted error into user mismatch
133+
if (e?.code === `auth/${AuthErrorCode.USER_DELETED}`) {
134+
fail(appName, AuthErrorCode.USER_MISMATCH);
135+
}
136+
throw e;
137+
}
138+
}
139+
140+
async function userCredForOperation(
141+
user: User,
142+
opType: OperationType,
143+
response: IdTokenResponse
144+
): Promise<UserCredentialImpl> {
145+
const newCred = _authCredentialFromTokenResponse(response);
146+
await user._updateTokensIfNecessary(response, /* reload */ true);
147+
return new UserCredentialImpl(user, newCred, opType);
148+
}

0 commit comments

Comments
 (0)