Skip to content

Commit d232313

Browse files
authored
Add AdditionalUserInfo class to auth-exp (#2979)
Add AdditionalUserInfo
1 parent a575f2d commit d232313

File tree

4 files changed

+399
-7
lines changed

4 files changed

+399
-7
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
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+
import { _fromIdTokenResponse } from './additional_user_info';
20+
import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token';
21+
import {
22+
UserProfile,
23+
ProviderId
24+
} from '@firebase/auth-types-exp';
25+
26+
describe('core/user/additional_user_info', () => {
27+
describe('_fromIdTokenResponse', () => {
28+
const userProfileWithLogin: UserProfile = {
29+
login: 'scott',
30+
friends: [],
31+
netWorth: 5.0
32+
};
33+
const rawUserInfoWithLogin = JSON.stringify(userProfileWithLogin);
34+
const userProfileNoLogin: UserProfile = { sample: 'data' };
35+
const rawUserInfoNoLogin = JSON.stringify(userProfileNoLogin);
36+
37+
describe('parses federated IDP response tokens', () => {
38+
it('for FacebookAdditionalUserInfo', () => {
39+
const idResponse = idTokenResponse({
40+
providerId: ProviderId.FACEBOOK,
41+
rawUserInfo: rawUserInfoWithLogin
42+
});
43+
const {
44+
isNewUser,
45+
providerId,
46+
username,
47+
profile
48+
} = _fromIdTokenResponse(idResponse)!;
49+
expect(isNewUser).to.be.false;
50+
expect(providerId).to.eq(ProviderId.FACEBOOK);
51+
expect(username).to.be.null;
52+
expect(profile).to.eq(userProfileWithLogin);
53+
});
54+
55+
it('for GithubAdditionalUserInfo', () => {
56+
const idResponse = idTokenResponse({
57+
providerId: ProviderId.GITHUB,
58+
rawUserInfo: rawUserInfoWithLogin
59+
});
60+
const {
61+
isNewUser,
62+
providerId,
63+
username,
64+
profile
65+
} = _fromIdTokenResponse(idResponse)!;
66+
expect(isNewUser).to.be.false;
67+
expect(providerId).to.eq(ProviderId.GITHUB);
68+
expect(username).to.eq('scott');
69+
expect(profile).to.eq(userProfileWithLogin);
70+
});
71+
72+
it('for GoogleAdditionalUserInfo', () => {
73+
const idResponse = idTokenResponse({
74+
providerId: ProviderId.GOOGLE,
75+
rawUserInfo: rawUserInfoWithLogin
76+
});
77+
const {
78+
isNewUser,
79+
providerId,
80+
username,
81+
profile
82+
} = _fromIdTokenResponse(idResponse)!;
83+
expect(isNewUser).to.be.false;
84+
expect(providerId).to.eq(ProviderId.GOOGLE);
85+
expect(username).to.be.null;
86+
expect(profile).to.eq(userProfileWithLogin);
87+
});
88+
89+
it('for TwitterAdditionalUserInfo', () => {
90+
const idResponse = idTokenResponse({
91+
providerId: ProviderId.TWITTER,
92+
rawUserInfo: rawUserInfoNoLogin,
93+
screenName: 'scott'
94+
});
95+
const {
96+
isNewUser,
97+
providerId,
98+
username,
99+
profile
100+
} = _fromIdTokenResponse(idResponse)!;
101+
expect(isNewUser).to.be.false;
102+
expect(providerId).to.eq(ProviderId.TWITTER);
103+
expect(username).to.eq('scott');
104+
expect(profile).to.eql(userProfileNoLogin);
105+
});
106+
});
107+
108+
describe('parses profile data', () => {
109+
it('for valid JSON', () => {
110+
const idResponse = idTokenResponse({
111+
providerId: ProviderId.FACEBOOK,
112+
rawUserInfo: rawUserInfoWithLogin
113+
});
114+
expect(_fromIdTokenResponse(idResponse)!.profile).to.eql(
115+
userProfileWithLogin
116+
);
117+
});
118+
119+
it('for missing JSON', () => {
120+
const idResponse = idTokenResponse({ providerId: ProviderId.FACEBOOK });
121+
expect(_fromIdTokenResponse(idResponse)!.profile).to.be.empty;
122+
});
123+
});
124+
125+
describe('determines new-user status', () => {
126+
it('for new users by token response', () => {
127+
const idResponse = idTokenResponse({
128+
providerId: ProviderId.FACEBOOK,
129+
isNewUser: true
130+
});
131+
expect(_fromIdTokenResponse(idResponse)!.isNewUser).to.be.true;
132+
});
133+
134+
it('for new users by toolkit response kind', () => {
135+
const idResponse = idTokenResponse({
136+
providerId: ProviderId.FACEBOOK,
137+
kind: IdTokenResponseKind.SignupNewUser
138+
});
139+
expect(_fromIdTokenResponse(idResponse)!.isNewUser).to.be.true;
140+
});
141+
142+
it('for old users', () => {
143+
const idResponse = idTokenResponse({ providerId: ProviderId.FACEBOOK });
144+
expect(_fromIdTokenResponse(idResponse)!.isNewUser).to.be.false;
145+
});
146+
});
147+
148+
describe('creates generic AdditionalUserInfo', () => {
149+
it('for custom auth', () => {
150+
const idResponse = idTokenResponse({
151+
providerId: ProviderId.CUSTOM,
152+
rawUserInfo: rawUserInfoWithLogin
153+
});
154+
const {
155+
isNewUser,
156+
providerId,
157+
username,
158+
profile
159+
} = _fromIdTokenResponse(idResponse)!;
160+
expect(isNewUser).to.be.false;
161+
expect(providerId).to.be.null;
162+
expect(username).to.be.null;
163+
expect(profile).to.eq(profile);
164+
});
165+
166+
it('for anonymous auth', () => {
167+
const idResponse = idTokenResponse({
168+
providerId: ProviderId.ANONYMOUS,
169+
rawUserInfo: rawUserInfoWithLogin
170+
});
171+
const {
172+
isNewUser,
173+
providerId,
174+
username,
175+
profile
176+
} = _fromIdTokenResponse(idResponse)!;
177+
expect(isNewUser).to.be.false;
178+
expect(providerId).to.be.null;
179+
expect(username).to.be.null;
180+
expect(profile).to.eq(profile);
181+
});
182+
183+
it('for missing provider IDs in response but not in token', () => {
184+
const {
185+
isNewUser,
186+
providerId,
187+
username,
188+
profile
189+
} = _fromIdTokenResponse(
190+
idTokenResponse({ rawUserInfo: rawUserInfoWithLogin })
191+
)!;
192+
expect(isNewUser).to.be.false;
193+
expect(providerId).to.eq(ProviderId.FACEBOOK);
194+
expect(username).to.be.null;
195+
expect(profile).to.eq(profile);
196+
});
197+
});
198+
199+
describe('returns null', () => {
200+
it('for missing provider IDs', () => {
201+
const idResponse = idTokenResponse({});
202+
expect(_fromIdTokenResponse(idResponse)).to.be.null;
203+
});
204+
});
205+
});
206+
});
207+
208+
function idTokenResponse(partial: Partial<IdTokenResponse>): IdTokenResponse {
209+
return {
210+
idToken: 'id-token',
211+
refreshToken: "refresh-token",
212+
expiresIn: "expires-in",
213+
localId: "local-id",
214+
kind: IdTokenResponseKind.CreateAuthUri,
215+
...partial
216+
};
217+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
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+
import {
18+
AdditionalUserInfo,
19+
UserProfile,
20+
ProviderId
21+
} from '@firebase/auth-types-exp';
22+
import { IdTokenResponse, IdTokenResponseKind } from '../../model/id_token';
23+
import { _parseToken } from './id_token_result';
24+
25+
/**
26+
* Parse the `AdditionalUserInfo` from the ID token response.
27+
*/
28+
export function _fromIdTokenResponse(
29+
idTokenResponse: IdTokenResponse,
30+
): AdditionalUserInfo | null {
31+
const { providerId } = idTokenResponse;
32+
const profile = idTokenResponse.rawUserInfo
33+
? JSON.parse(idTokenResponse.rawUserInfo)
34+
: {};
35+
const isNewUser =
36+
idTokenResponse.isNewUser ||
37+
idTokenResponse.kind === IdTokenResponseKind.SignupNewUser;
38+
if (!providerId && idTokenResponse) {
39+
const providerId = _parseToken(idTokenResponse.idToken)?.firebase?.[
40+
'sign_in_provider'
41+
];
42+
if (providerId) {
43+
const filteredProviderId =
44+
providerId !== ProviderId.ANONYMOUS && providerId !== ProviderId.CUSTOM
45+
? (providerId as ProviderId)
46+
: null;
47+
// Uses generic class in accordance with the legacy SDK.
48+
return new GenericAdditionalUserInfo(isNewUser, filteredProviderId);
49+
}
50+
}
51+
if (!providerId) {
52+
return null;
53+
}
54+
switch (providerId) {
55+
case ProviderId.FACEBOOK:
56+
return new FacebookAdditionalUserInfo(isNewUser, profile);
57+
case ProviderId.GITHUB:
58+
return new GithubAdditionalUserInfo(isNewUser, profile);
59+
case ProviderId.GOOGLE:
60+
return new GoogleAdditionalUserInfo(isNewUser, profile);
61+
case ProviderId.TWITTER:
62+
return new TwitterAdditionalUserInfo(
63+
isNewUser,
64+
profile,
65+
idTokenResponse.screenName || null
66+
);
67+
case ProviderId.CUSTOM:
68+
case ProviderId.ANONYMOUS:
69+
return new GenericAdditionalUserInfo(isNewUser, null);
70+
default:
71+
return new FederatedAdditionalUserInfo(
72+
isNewUser,
73+
providerId,
74+
profile
75+
);
76+
}
77+
}
78+
79+
class GenericAdditionalUserInfo implements AdditionalUserInfo {
80+
constructor(
81+
readonly isNewUser: boolean,
82+
readonly providerId: ProviderId | null,
83+
) {}
84+
}
85+
86+
class FederatedAdditionalUserInfo extends GenericAdditionalUserInfo {
87+
constructor(
88+
isNewUser: boolean,
89+
providerId: ProviderId,
90+
readonly profile: UserProfile,
91+
) {
92+
super(isNewUser, providerId);
93+
}
94+
}
95+
96+
class FederatedAdditionalUserInfoWithUsername extends FederatedAdditionalUserInfo {
97+
constructor(
98+
isNewUser: boolean,
99+
providerId: ProviderId,
100+
profile: UserProfile,
101+
readonly username: string | null,
102+
) {
103+
super(isNewUser, providerId, profile);
104+
}
105+
}
106+
107+
class FacebookAdditionalUserInfo extends FederatedAdditionalUserInfo {
108+
constructor(isNewUser: boolean, profile: UserProfile) {
109+
super(isNewUser, ProviderId.FACEBOOK, profile);
110+
}
111+
}
112+
113+
class GithubAdditionalUserInfo extends FederatedAdditionalUserInfoWithUsername {
114+
constructor(isNewUser: boolean, profile: UserProfile) {
115+
super(
116+
isNewUser,
117+
ProviderId.GITHUB,
118+
profile,
119+
typeof profile?.login === 'string' ? profile?.login : null
120+
);
121+
}
122+
}
123+
124+
class GoogleAdditionalUserInfo extends FederatedAdditionalUserInfo {
125+
constructor(isNewUser: boolean, profile: UserProfile) {
126+
super(isNewUser, ProviderId.GOOGLE, profile);
127+
}
128+
}
129+
130+
class TwitterAdditionalUserInfo extends FederatedAdditionalUserInfoWithUsername {
131+
constructor(
132+
isNewUser: boolean,
133+
profile: UserProfile,
134+
screenName: string | null
135+
) {
136+
super(isNewUser, ProviderId.TWITTER, profile, screenName);
137+
}
138+
}

0 commit comments

Comments
 (0)