Skip to content

Commit c0b95b7

Browse files
sam-gcavolkovi
authored andcommitted
Big refactor of reauth / link + basic idp tasks (#3242)
* Big refactor of reauth / link + basic idp tasks * PR feedback * Formatting
1 parent 948aa9c commit c0b95b7

16 files changed

+908
-301
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 } from 'chai';
19+
20+
import { ProviderId } from '@firebase/auth-types-exp';
21+
22+
import { TEST_ID_TOKEN_RESPONSE } from '../../../test/id_token_response';
23+
import { _authCredentialFromTokenResponse } from './from_token_response';
24+
25+
describe('src/core/credentials/inferred', () => {
26+
it('returns a phone credential if response conains correct fields', () => {
27+
const cred = _authCredentialFromTokenResponse({
28+
...TEST_ID_TOKEN_RESPONSE,
29+
temporaryProof: 'temporary-proof',
30+
phoneNumber: 'phone-number'
31+
});
32+
33+
expect(cred!.providerId).to.eq(ProviderId.PHONE);
34+
});
35+
36+
it('returns null if nothing matches', () => {
37+
const cred = _authCredentialFromTokenResponse({
38+
...TEST_ID_TOKEN_RESPONSE
39+
});
40+
41+
expect(cred).to.be.null;
42+
});
43+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa';
19+
import { SignInWithPhoneNumberResponse } from '../../api/authentication/sms';
20+
import { AuthCredential } from './';
21+
import { PhoneAuthCredential } from './phone';
22+
23+
export function _authCredentialFromTokenResponse(
24+
response: PhoneOrOauthTokenResponse
25+
): AuthCredential | null {
26+
const {
27+
temporaryProof,
28+
phoneNumber
29+
} = response as SignInWithPhoneNumberResponse;
30+
if (temporaryProof && phoneNumber) {
31+
return new PhoneAuthCredential({ temporaryProof, phoneNumber });
32+
}
33+
34+
// TODO: Handle Oauth cases
35+
return null;
36+
}

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

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import { Auth } from '../../model/auth';
3636
import { IdTokenResponse } from '../../model/id_token';
3737
import { User } from '../../model/user';
3838
import {
39-
_assertLinkedStatus,
4039
linkWithCredential,
4140
reauthenticateWithCredential,
4241
signInWithCredential
@@ -167,51 +166,4 @@ describe('core/strategies/credential', () => {
167166
expect(credential).to.be.null;
168167
});
169168
});
170-
171-
describe('_assertLinkedStatus', () => {
172-
it('should error with already linked if expectation is true', async () => {
173-
getAccountInfoEndpoint.response = {
174-
users: [
175-
{
176-
...serverUser,
177-
providerUserInfo: [{ providerId: ProviderId.GOOGLE }]
178-
}
179-
]
180-
};
181-
182-
await expect(
183-
_assertLinkedStatus(false, user, ProviderId.GOOGLE)
184-
).to.be.rejectedWith(
185-
FirebaseError,
186-
'Firebase: User can only be linked to one identity for the given provider. (auth/provider-already-linked).'
187-
);
188-
});
189-
190-
it('should not error if provider is not linked', async () => {
191-
await expect(_assertLinkedStatus(false, user, ProviderId.GOOGLE)).not.to
192-
.be.rejected;
193-
});
194-
195-
it('should error if provider is not linked but it was expected to be', async () => {
196-
await expect(
197-
_assertLinkedStatus(true, user, ProviderId.GOOGLE)
198-
).to.be.rejectedWith(
199-
FirebaseError,
200-
'Firebase: User was not linked to an account with the given provider. (auth/no-such-provider).'
201-
);
202-
});
203-
204-
it('should not error if provider is linked and that is expected', async () => {
205-
getAccountInfoEndpoint.response = {
206-
users: [
207-
{
208-
...serverUser,
209-
providerUserInfo: [{ providerId: ProviderId.GOOGLE }]
210-
}
211-
]
212-
};
213-
await expect(_assertLinkedStatus(true, user, ProviderId.GOOGLE)).not.to.be
214-
.rejected;
215-
});
216-
});
217169
});

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

Lines changed: 9 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,14 @@
1818
import * as externs from '@firebase/auth-types-exp';
1919
import { OperationType, UserCredential } from '@firebase/auth-types-exp';
2020

21-
import { PhoneOrOauthTokenResponse } from '../../api/authentication/mfa';
22-
import { SignInWithPhoneNumberResponse } from '../../api/authentication/sms';
2321
import { Auth } from '../../model/auth';
24-
import { IdTokenResponse } from '../../model/id_token';
2522
import { User } from '../../model/user';
2623
import { AuthCredential } from '../credentials';
27-
import { PhoneAuthCredential } from '../credentials/phone';
28-
import { AuthErrorCode } from '../errors';
2924
import { _parseToken } from '../user/id_token_result';
25+
import { _assertLinkedStatus, _link } from '../user/link_unlink';
26+
import { _reauthenticate } from '../user/reauthenticate';
3027
import { _reloadWithoutSaving } from '../user/reload';
3128
import { UserCredentialImpl } from '../user/user_credential_impl';
32-
import { assert, fail } from '../util/assert';
33-
import { providerDataAsNames } from '../util/providers';
3429

3530
export async function signInWithCredential(
3631
authExtern: externs.Auth,
@@ -56,14 +51,13 @@ export async function linkWithCredential(
5651
): Promise<UserCredential> {
5752
const user = userExtern as User;
5853
const credential = credentialExtern as AuthCredential;
54+
5955
await _assertLinkedStatus(false, user, credential.providerId);
6056

61-
const response = await credential._linkToIdToken(
62-
user.auth,
63-
await user.getIdToken()
57+
return _link(
58+
user,
59+
credential._linkToIdToken(user.auth, await user.getIdToken())
6460
);
65-
66-
return userCredForOperation(user, OperationType.LINK, response);
6761
}
6862

6963
export async function reauthenticateWithCredential(
@@ -73,76 +67,8 @@ export async function reauthenticateWithCredential(
7367
const credential = credentialExtern as AuthCredential;
7468
const user = userExtern as User;
7569

76-
const { auth, uid } = user;
77-
const response = await verifyTokenResponseUid(
78-
credential._getReauthenticationResolver(auth),
79-
uid,
80-
auth.name
70+
return _reauthenticate(
71+
user,
72+
credential._getReauthenticationResolver(user.auth)
8173
);
82-
83-
return userCredForOperation(user, OperationType.REAUTHENTICATE, response);
84-
}
85-
86-
export function _authCredentialFromTokenResponse(
87-
response: PhoneOrOauthTokenResponse
88-
): AuthCredential | null {
89-
const {
90-
temporaryProof,
91-
phoneNumber
92-
} = response as SignInWithPhoneNumberResponse;
93-
if (temporaryProof && phoneNumber) {
94-
return new PhoneAuthCredential({ temporaryProof, phoneNumber });
95-
}
96-
97-
// TODO: Handle Oauth cases
98-
return null;
99-
}
100-
101-
export async function _assertLinkedStatus(
102-
expected: boolean,
103-
user: User,
104-
provider: externs.ProviderId
105-
): Promise<void> {
106-
await _reloadWithoutSaving(user);
107-
const providerIds = providerDataAsNames(user.providerData);
108-
109-
const code =
110-
expected === false
111-
? AuthErrorCode.PROVIDER_ALREADY_LINKED
112-
: AuthErrorCode.NO_SUCH_PROVIDER;
113-
assert(providerIds.has(provider) === expected, user.auth.name, code);
114-
}
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);
14874
}

0 commit comments

Comments
 (0)