Skip to content

Commit 5b7ec28

Browse files
committed
fix(auth): make auth API compatible with new Firebase SDK
1 parent 5bde057 commit 5b7ec28

File tree

9 files changed

+634
-489
lines changed

9 files changed

+634
-489
lines changed

src/providers/auth.spec.ts

Lines changed: 315 additions & 297 deletions
Large diffs are not rendered by default.

src/providers/auth.ts

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import {Provider, Inject, provide, Injectable, Optional} from '@angular/core';
2+
import { Observable } from 'rxjs/Observable';
3+
import { Observer } from 'rxjs/Observer';
24
import {ReplaySubject} from 'rxjs/ReplaySubject';
3-
import {FirebaseRef, FirebaseAuthConfig} from '../tokens';
5+
6+
import 'rxjs/add/operator/mergeMap';
7+
import 'rxjs/add/operator/take';
8+
import 'rxjs/add/operator/concat';
9+
import 'rxjs/add/operator/skip';
10+
import 'rxjs/add/observable/of';
11+
12+
import {FirebaseApp, FirebaseAuthConfig} from '../tokens';
413
import {isPresent} from '../utils/utils';
514
import * as utils from '../utils/utils';
615
import {
16+
authDataToAuthState,
717
AuthBackend,
818
AuthProviders,
919
AuthMethods,
10-
OAuthCredentials,
11-
OAuth1Credentials,
12-
OAuth2Credentials,
13-
AuthCredentials,
14-
FirebaseAuthState,
20+
EmailPasswordCredentials,
21+
OAuthCredential,
1522
AuthConfiguration,
16-
FirebaseAuthDataAllProviders,
17-
authDataToAuthState
23+
FirebaseAuthState,
24+
stripProviderId
1825
} from './auth_backend';
1926

2027
const kBufferSize = 1;
@@ -26,20 +33,39 @@ export const firebaseAuthConfig = (config: AuthConfiguration): Provider => {
2633
};
2734

2835
@Injectable()
29-
export class FirebaseAuth extends ReplaySubject<FirebaseAuthState> {
36+
export class AngularFireAuth extends ReplaySubject<FirebaseAuthState> {
37+
private _credentialCache: {[key:string]: OAuthCredential} = {};
3038
constructor(private _authBackend: AuthBackend,
3139
@Optional() @Inject(FirebaseAuthConfig) private _config?: AuthConfiguration) {
3240
super(kBufferSize);
3341

34-
this._authBackend.onAuth((authData) => this._emitAuthData(authData));
42+
let firstPass = true;
43+
this._authBackend.onAuth()
44+
.mergeMap((authState: FirebaseAuthState) => {
45+
// TODO: get rid of side effect
46+
if (firstPass) {
47+
firstPass = false;
48+
return this._authBackend.getRedirectResult()
49+
.map((userCredential: firebase.auth.UserCredential) => {
50+
if (userCredential && userCredential.credential) {
51+
authState = attachCredentialToAuthState(authState, userCredential.credential, userCredential.credential.provider);
52+
this._credentialCache[userCredential.credential.provider] = <OAuthCredential>userCredential.credential;
53+
}
54+
return authState;
55+
})
56+
}
57+
return Observable.of(authState);
58+
})
59+
.subscribe((authData: FirebaseAuthState) => this._emitAuthData(authData));
3560
}
3661

37-
public login(config?: AuthConfiguration): Promise<FirebaseAuthState>;
38-
public login(credentials?: FirebaseCredentials): Promise<FirebaseAuthState>;
39-
public login(credentials: AuthCredentials, config?: AuthConfiguration): Promise<FirebaseAuthState>;
40-
public login(obj1?: any, obj2?: AuthConfiguration): Promise<FirebaseAuthState> {
62+
public login(config?: AuthConfiguration): firebase.Promise<FirebaseAuthState>;
63+
// If logging in with email and password
64+
public login(credentials?: EmailPasswordCredentials | firebase.auth.AuthCredential | string): firebase.Promise<FirebaseAuthState>;
65+
public login(credentials: EmailPasswordCredentials | firebase.auth.AuthCredential | string, config?: AuthConfiguration): firebase.Promise<FirebaseAuthState>;
66+
public login(obj1?: any, obj2?: AuthConfiguration): firebase.Promise<FirebaseAuthState> {
4167
let config: AuthConfiguration = null;
42-
let credentials: AuthCredentials = null;
68+
let credentials: EmailPasswordCredentials | firebase.auth.AuthCredential | string = null;
4369
if (arguments.length > 2) {
4470
return this._reject('Login only accepts a maximum of two arguments.');
4571
} else if (arguments.length == 2) {
@@ -74,33 +100,37 @@ export class FirebaseAuth extends ReplaySubject<FirebaseAuthState> {
74100

75101
switch (config.method) {
76102
case AuthMethods.Popup:
77-
return this._authBackend.authWithOAuthPopup(config.provider, this._scrubConfig(config));
103+
return this._authBackend.authWithOAuthPopup(config.provider, this._scrubConfig(config))
104+
.then((userCredential: firebase.auth.UserCredential) => {
105+
// Incorrect type information
106+
this._credentialCache[userCredential.credential.provider] = <OAuthCredential>userCredential.credential;
107+
return authDataToAuthState(userCredential.user, <OAuthCredential>(<any>userCredential).credential);
108+
});
78109
case AuthMethods.Redirect:
79-
return this._authBackend.authWithOAuthRedirect(config.provider, this._scrubConfig(config));
110+
// Gets around typings issue since this method doesn't resolve with a user.
111+
// The method really only does anything with an error, since it redirects.
112+
return <Promise<FirebaseAuthState>>(<any>this._authBackend).authWithOAuthRedirect(config.provider, this._scrubConfig(config));
80113
case AuthMethods.Anonymous:
81114
return this._authBackend.authAnonymously(this._scrubConfig(config));
82115
case AuthMethods.Password:
83-
return this._authBackend.authWithPassword(<FirebaseCredentials>credentials, this._scrubConfig(config, false));
116+
return this._authBackend.authWithPassword(<EmailPasswordCredentials>credentials);
84117
case AuthMethods.OAuthToken:
85-
return this._authBackend.authWithOAuthToken(config.provider, <OAuthCredentials>credentials,
118+
return this._authBackend.authWithOAuthToken(<firebase.auth.AuthCredential>credentials,
86119
this._scrubConfig(config));
87120
case AuthMethods.CustomToken:
88-
return this._authBackend.authWithCustomToken((<OAuth2Credentials>credentials).token,
89-
this._scrubConfig(config, false));
121+
return this._authBackend.authWithCustomToken(<string>credentials);
90122
}
91123
}
92124

93125
public logout(): void {
94-
if (this._authBackend.getAuth() !== null) {
95-
this._authBackend.unauth();
96-
}
126+
this._authBackend.unauth();
97127
}
98128

99-
public getAuth(): FirebaseAuthData {
100-
return this._authBackend.getAuth();
129+
public getAuth(): FirebaseAuthState {
130+
return this._authBackend.getAuth()
101131
}
102132

103-
public createUser(credentials: FirebaseCredentials): Promise<FirebaseAuthData> {
133+
public createUser(credentials: EmailPasswordCredentials): firebase.Promise<FirebaseAuthState> {
104134
return this._authBackend.createUser(credentials);
105135
}
106136

@@ -115,10 +145,10 @@ export class FirebaseAuth extends ReplaySubject<FirebaseAuthState> {
115145
return Object.assign({}, this._config, config);
116146
}
117147

118-
private _reject(msg: string): Promise<FirebaseAuthState> {
119-
return new Promise((res, rej) => {
148+
private _reject(msg: string): firebase.Promise<FirebaseAuthState> {
149+
return (<Promise<FirebaseAuthState>>new Promise((res, rej) => {
120150
return rej(msg);
121-
});
151+
}));
122152
}
123153

124154
private _scrubConfig(config: AuthConfiguration, scrubProvider = true): any {
@@ -131,11 +161,26 @@ export class FirebaseAuth extends ReplaySubject<FirebaseAuthState> {
131161
}
132162

133163

134-
private _emitAuthData(authData: FirebaseAuthDataAllProviders): void {
164+
private _emitAuthData(authData: FirebaseAuthState): void {
135165
if (authData == null) {
136166
this.next(null);
137167
} else {
138-
this.next(authDataToAuthState(authData));
168+
if (authData.auth && authData.auth.providerData && authData.auth.providerData[0]) {
169+
let providerId = authData.auth.providerData[0].providerId;
170+
let providerCredential = this._credentialCache[providerId];
171+
if (providerCredential) {
172+
authData = attachCredentialToAuthState(authData, providerCredential, providerId);
173+
}
174+
}
175+
176+
this.next(authData);
139177
}
140178
}
141179
}
180+
181+
function attachCredentialToAuthState (authState: FirebaseAuthState, credential, providerId: string): FirebaseAuthState {
182+
if (!authState) return authState;
183+
// TODO make authState immutable
184+
authState[stripProviderId(providerId)] = credential;
185+
return authState;
186+
}

src/providers/auth_backend.spec.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {
2+
expect,
3+
ddescribe,
4+
describe,
5+
it,
6+
iit,
7+
beforeEach
8+
} from '@angular/core/testing';
9+
10+
import {
11+
authDataToAuthState,
12+
AuthProviders,
13+
FirebaseAuthState,
14+
CommonOAuthCredential,
15+
GoogleCredential,
16+
TwitterCredential
17+
} from './auth_backend';
18+
19+
const baseFBUser = {
20+
uid: '12345',
21+
providerId: '',
22+
providerData: [{}]
23+
};
24+
25+
const baseAuthState: FirebaseAuthState = {
26+
uid: baseFBUser.uid,
27+
provider: AuthProviders.Anonymous,
28+
auth: <firebase.User>baseFBUser
29+
};
30+
31+
const baseGithubCredential: CommonOAuthCredential = {
32+
accessToken: 'GH_ACCESS_TOKEN',
33+
provider: 'github.com'
34+
};
35+
36+
const baseFacebookCredential: CommonOAuthCredential = {
37+
accessToken: 'FB_ACCESS_TOKEN',
38+
provider: 'facebook.com'
39+
};
40+
41+
const baseGoogleCredential: GoogleCredential = {
42+
idToken: 'GOOGLE_ID_TOKEN',
43+
provider: 'google.com'
44+
};
45+
46+
const baseTwitterCredential: TwitterCredential = {
47+
accessToken: 'TWITTER_ACCESS_TOKEN',
48+
provider: 'twitter.com',
49+
secret: 'TWITTER_SECRET'
50+
};
51+
52+
describe('auth_backend', () => {
53+
describe('authDataToAuthState', () => {
54+
it('Github: should return a FirebaseAuthState object with full provider data', () => {
55+
let githubUser = Object.assign({}, baseFBUser, {
56+
providerData: [{providerId: 'github.com'}]
57+
});
58+
let expectedAuthState = Object.assign({}, baseAuthState, {
59+
github: baseGithubCredential,
60+
auth: githubUser
61+
});
62+
63+
let actualAuthState = authDataToAuthState(githubUser, baseGithubCredential);
64+
expect(actualAuthState.github.accessToken).toEqual(baseGithubCredential.accessToken);
65+
});
66+
});
67+
68+
it('Google: should return a FirebaseAuthState object with full provider data', () => {
69+
let googleUser = Object.assign({}, baseFBUser, {
70+
providerData: [{providerId: 'google.com'}]
71+
});
72+
let expectedAuthState = Object.assign({}, baseAuthState, {
73+
google: baseGoogleCredential,
74+
auth: googleUser
75+
});
76+
77+
let actualAuthState = authDataToAuthState(googleUser, baseGoogleCredential);
78+
expect(actualAuthState.google.idToken).toEqual(baseGoogleCredential.idToken);
79+
});
80+
81+
it('Twitter: should return a FirebaseAuthState object with full provider data', () => {
82+
let twitterUser = Object.assign({}, baseFBUser, {
83+
providerData: [{providerId: 'twitter.com'}]
84+
});
85+
let expectedAuthState = Object.assign({}, baseAuthState, {
86+
twitter: baseTwitterCredential,
87+
auth: twitterUser
88+
});
89+
90+
let actualAuthState = authDataToAuthState(twitterUser, baseTwitterCredential);
91+
expect(actualAuthState.twitter.secret).toEqual(baseTwitterCredential.secret);
92+
});
93+
94+
it('Facebook: should return a FirebaseAuthState object with full provider data', () => {
95+
let facebookUser = Object.assign({}, baseFBUser, {
96+
providerData: [{providerId: 'facebook.com'}]
97+
});
98+
let expectedAuthState = Object.assign({}, baseAuthState, {
99+
facebook: baseFacebookCredential,
100+
auth: facebookUser
101+
});
102+
103+
let actualAuthState = authDataToAuthState(facebookUser, baseFacebookCredential);
104+
expect(actualAuthState.facebook.accessToken).toEqual(baseFacebookCredential.accessToken);
105+
});
106+
});

0 commit comments

Comments
 (0)