Skip to content

Commit 2958818

Browse files
authored
Add signInAnonymously to auth-next (#2986)
* Add signInWithCredential to auth-next * Add signInAnonymously to auth-next * [AUTOMATED]: Prettier Code Styling * Add more tests * Move these back into providers * One more rebase
1 parent 8452cf7 commit 2958818

File tree

9 files changed

+297
-10
lines changed

9 files changed

+297
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19+
import { expect, use } from 'chai';
20+
import * as chaiAsPromised from 'chai-as-promised';
21+
import { testAuth } from '../../../test/mock_auth';
22+
import { Auth } from '../../model/auth';
23+
import { AnonymousCredential, AnonymousProvider } from './anonymous';
24+
25+
use(chaiAsPromised);
26+
27+
describe('core/providers/anonymous', () => {
28+
let auth: Auth;
29+
30+
beforeEach(async () => {
31+
auth = await testAuth();
32+
});
33+
34+
describe('AnonymousCredential', () => {
35+
const credential = new AnonymousCredential();
36+
37+
it('should have an anonymous provider', () => {
38+
expect(credential.providerId).to.eq(ProviderId.ANONYMOUS);
39+
});
40+
41+
it('should have an anonymous sign in method', () => {
42+
expect(credential.signInMethod).to.eq(SignInMethod.ANONYMOUS);
43+
});
44+
45+
describe('#toJSON', () => {
46+
it('throws', () => {
47+
expect(credential.toJSON).to.throw(Error);
48+
});
49+
});
50+
51+
describe('#_getIdTokenResponse', () => {
52+
it('throws', async () => {
53+
await expect(
54+
credential._getIdTokenResponse(auth)
55+
).to.be.rejectedWith(Error);
56+
});
57+
});
58+
59+
describe('#_linkToIdToken', () => {
60+
it('throws', async () => {
61+
await expect(
62+
credential._linkToIdToken(auth, 'id-token')
63+
).to.be.rejectedWith(Error);
64+
});
65+
});
66+
67+
describe('#_matchIdTokenWithUid', () => {
68+
it('throws', () => {
69+
expect(() =>
70+
credential._matchIdTokenWithUid(auth, 'other-uid')
71+
).to.throw(Error);
72+
});
73+
});
74+
});
75+
76+
describe('AnonymousProvider', () => {
77+
describe('.credential', () => {
78+
it('should return an anonymous credential', () => {
79+
const credential = AnonymousProvider.credential();
80+
expect(credential.providerId).to.eq(ProviderId.ANONYMOUS);
81+
expect(credential.signInMethod).to.eq(SignInMethod.ANONYMOUS);
82+
});
83+
});
84+
});
85+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 { AuthCredential, ProviderId, SignInMethod, AuthProvider } from "@firebase/auth-types-exp";
19+
import { signUp } from '../../api/authentication/sign_up';
20+
import { Auth } from '../../model/auth';
21+
import { IdTokenResponse } from "../../model/id_token";
22+
import { debugFail } from '../util/assert';
23+
24+
export class AnonymousCredential implements AuthCredential {
25+
providerId = ProviderId.ANONYMOUS;
26+
signInMethod = SignInMethod.ANONYMOUS;
27+
28+
toJSON(): never {
29+
debugFail('Method not implemented.');
30+
}
31+
32+
static fromJSON(): never {
33+
debugFail('Method not implemented');
34+
}
35+
36+
async _getIdTokenResponse(auth: Auth): Promise<IdTokenResponse> {
37+
return signUp(auth, {
38+
returnSecureToken: true
39+
});
40+
}
41+
42+
async _linkToIdToken(_auth: Auth, _idToken: string): Promise<never> {
43+
debugFail("Can't link to an anonymous credential");
44+
}
45+
46+
_matchIdTokenWithUid(_auth: Auth, _uid: string): Promise<never> {
47+
debugFail('Method not implemented.');
48+
}
49+
}
50+
51+
export class AnonymousProvider implements AuthProvider {
52+
providerId = ProviderId.ANONYMOUS;
53+
54+
static credential(): AnonymousCredential {
55+
return new AnonymousCredential();
56+
}
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 {
19+
OperationType,
20+
ProviderId,
21+
SignInMethod
22+
} from '@firebase/auth-types-exp';
23+
import { expect } from 'chai';
24+
import { mockEndpoint } from '../../../test/api/helper';
25+
import { testAuth, testUser } from '../../../test/mock_auth';
26+
import * as mockFetch from '../../../test/mock_fetch';
27+
import { Endpoint } from '../../api';
28+
import { APIUserInfo } from '../../api/account_management/account';
29+
import { Auth } from '../../model/auth';
30+
import { signInAnonymously } from './anonymous';
31+
32+
describe('core/strategies/anonymous', () => {
33+
let auth: Auth;
34+
const serverUser: APIUserInfo = {
35+
localId: 'local-id'
36+
};
37+
38+
beforeEach(async () => {
39+
auth = await testAuth();
40+
mockFetch.setUp();
41+
mockEndpoint(Endpoint.SIGN_UP, {
42+
idToken: 'id-token',
43+
refreshToken: 'refresh-token',
44+
expiresIn: '1234',
45+
localId: serverUser.localId!
46+
});
47+
mockEndpoint(Endpoint.GET_ACCOUNT_INFO, {
48+
users: [serverUser]
49+
});
50+
});
51+
afterEach(mockFetch.tearDown);
52+
53+
describe('signInAnonymously', () => {
54+
it('should sign in an anonymous user', async () => {
55+
const { credential, user, operationType } = await signInAnonymously(
56+
auth
57+
);
58+
expect(credential?.providerId).to.eq(ProviderId.ANONYMOUS);
59+
expect(credential?.signInMethod).to.eq(SignInMethod.ANONYMOUS);
60+
expect(operationType).to.eq(OperationType.SIGN_IN);
61+
expect(user.uid).to.eq(serverUser.localId);
62+
expect(user.isAnonymous).to.be.true;
63+
});
64+
65+
context('already signed in anonymously', () => {
66+
it('should return the current user', async () => {
67+
const userCredential = await signInAnonymously(auth);
68+
expect(userCredential.user.isAnonymous).to.be.true;
69+
70+
const { credential, user, operationType } = await signInAnonymously(
71+
auth
72+
);
73+
expect(credential?.providerId).to.eq(ProviderId.ANONYMOUS);
74+
expect(credential?.signInMethod).to.eq(SignInMethod.ANONYMOUS);
75+
expect(operationType).to.eq(OperationType.SIGN_IN);
76+
expect(user.uid).to.eq(userCredential.user.uid);
77+
expect(user.isAnonymous).to.be.true;
78+
});
79+
});
80+
81+
context('already signed in with a non-anonymous account', () => {
82+
it('should sign in as a new user user', async () => {
83+
const fakeUser = testUser(auth, 'other-uid');
84+
await auth.updateCurrentUser(fakeUser);
85+
expect(fakeUser.isAnonymous).to.be.false;
86+
87+
const { credential, user, operationType } = await signInAnonymously(
88+
auth
89+
);
90+
expect(credential?.providerId).to.eq(ProviderId.ANONYMOUS);
91+
expect(credential?.signInMethod).to.eq(SignInMethod.ANONYMOUS);
92+
expect(operationType).to.eq(OperationType.SIGN_IN);
93+
expect(user.uid).to.not.eq(fakeUser.uid);
94+
expect(user.isAnonymous).to.be.true;
95+
});
96+
});
97+
});
98+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 * as externs from '@firebase/auth-types-exp';
19+
import { Auth } from '../../model/auth';
20+
import { AnonymousProvider } from '../providers/anonymous';
21+
import { UserCredentialImpl } from '../user/user_credential_impl';
22+
import { signInWithCredential } from './credential';
23+
24+
export async function signInAnonymously(externAuth: externs.Auth): Promise<externs.UserCredential> {
25+
const auth = externAuth as Auth;
26+
const credential = AnonymousProvider.credential();
27+
if (auth.currentUser?.isAnonymous) {
28+
// If an anonymous user is already signed in, no need to sign them in again.
29+
return new UserCredentialImpl(
30+
auth.currentUser,
31+
credential,
32+
externs.OperationType.SIGN_IN
33+
);
34+
}
35+
return signInWithCredential(auth, credential);
36+
}
37+

packages-exp/auth-exp/src/core/user/user_credential_impl.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { OperationType, UserCredential } from '@firebase/auth-types-exp';
18+
import { OperationType, UserCredential, ProviderId } from '@firebase/auth-types-exp';
1919
import { Auth } from '../../model/auth';
2020
import { AuthCredential } from '../../model/auth_credential';
2121
import { IdTokenResponse } from '../../model/id_token';
@@ -35,7 +35,7 @@ export class UserCredentialImpl implements UserCredential {
3535
operationType: OperationType,
3636
idTokenResponse: IdTokenResponse
3737
): Promise<UserCredential> {
38-
const user = await UserImpl._fromIdTokenResponse(auth, idTokenResponse);
38+
const user = await UserImpl._fromIdTokenResponse(auth, idTokenResponse, credential?.providerId === ProviderId.ANONYMOUS);
3939
const userCred = new UserCredentialImpl(user, credential, operationType);
4040
// TODO: handle additional user info
4141
// updateAdditionalUserInfoFromIdTokenResponse(userCred, idTokenResponse);

packages-exp/auth-exp/src/core/user/user_impl.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface UserParameters {
3535
email?: string;
3636
phoneNumber?: string;
3737
photoURL?: string;
38+
isAnonymous?: boolean;
3839
}
3940

4041
function assertStringOrUndefined(
@@ -64,8 +65,8 @@ export class UserImpl implements User {
6465
email: string | null;
6566
phoneNumber: string | null;
6667
photoURL: string | null;
67-
isAnonymous = false;
68-
68+
isAnonymous: boolean = false;
69+
6970
constructor({ uid, auth, stsTokenManager, ...opt }: UserParameters) {
7071
this.uid = uid;
7172
this.auth = auth;
@@ -74,6 +75,7 @@ export class UserImpl implements User {
7475
this.email = opt.email || null;
7576
this.phoneNumber = opt.phoneNumber || null;
7677
this.photoURL = opt.photoURL || null;
78+
this.isAnonymous = opt.isAnonymous || false;
7779
}
7880

7981
async getIdToken(forceRefresh?: boolean): Promise<string> {
@@ -176,7 +178,8 @@ export class UserImpl implements User {
176178
*/
177179
static async _fromIdTokenResponse(
178180
auth: Auth,
179-
idTokenResponse: IdTokenResponse
181+
idTokenResponse: IdTokenResponse,
182+
isAnonymous: boolean = false
180183
): Promise<User> {
181184
const stsTokenManager = new StsTokenManager();
182185
stsTokenManager.updateFromServerResponse(idTokenResponse);
@@ -185,7 +188,8 @@ export class UserImpl implements User {
185188
const user = new UserImpl({
186189
uid: idTokenResponse.localId,
187190
auth,
188-
stsTokenManager
191+
stsTokenManager,
192+
isAnonymous
189193
});
190194

191195
// Updates the user info and data and resolves with a user instance.

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

+2
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ export { inMemoryPersistence } from './core/persistence/in_memory';
2727
export { indexedDBLocalPersistence } from './core/persistence/indexed_db';
2828

2929
// core/providers
30+
export { AnonymousProvider } from './core/providers/anonymous';
3031
export { PhoneAuthProvider } from './core/providers/phone';
3132

3233
// core/strategies
34+
export { signInAnonymously } from './core/strategies/anonymous';
3335
export { signInWithCredential } from './core/strategies/credential';
3436
export {
3537
sendPasswordResetEmail,

packages-exp/auth-exp/src/model/user.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface User extends externs.User {
3939
tenantId: string | null;
4040
providerData: MutableUserInfo[];
4141
metadata: externs.UserMetadata;
42+
4243
_updateTokensIfNecessary(response: IdTokenResponse): boolean;
4344

4445
getIdToken(forceRefresh?: boolean): Promise<string>;

packages-exp/auth-types-exp/index.d.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ export const enum SignInMethod {
183183
TWITTER = 'twitter.com'
184184
}
185185

186-
export interface AuthProvider {
187-
readonly providerId: string;
188-
}
189-
190186
export abstract class AuthCredential {
191187
readonly providerId: ProviderId;
192188
readonly signInMethod: SignInMethod;
@@ -233,3 +229,10 @@ export interface PhoneInfoOptions {
233229
// session?: MultiFactorSession;
234230
// multiFactorHint?: MultiFactorInfo;
235231
}
232+
233+
/**
234+
* A provider for generating credentials
235+
*/
236+
export interface AuthProvider {
237+
readonly providerId: ProviderId;
238+
}

0 commit comments

Comments
 (0)