1
- import { Injectable , Inject , Optional , NgZone , PLATFORM_ID , InjectionToken } from '@angular/core' ;
2
- import { Observable , of , from , merge , Subject , Subscriber } from 'rxjs' ;
3
- import { switchMap , map , observeOn , shareReplay , first , filter , switchMapTo , subscribeOn , tap , take , mapTo } from 'rxjs/operators' ;
1
+ import { Injectable , Inject , Optional , NgZone , PLATFORM_ID , InjectionToken , OnDestroy } from '@angular/core' ;
2
+ import { Observable , of , from , merge , Subject , Subscriber , SubscriptionLike } from 'rxjs' ;
3
+ import { switchMap , map , observeOn , shareReplay , filter , switchMapTo , subscribeOn , tap , pairwise , take } from 'rxjs/operators' ;
4
4
import {
5
5
FIREBASE_OPTIONS ,
6
6
FIREBASE_APP_NAME ,
@@ -18,7 +18,8 @@ import { isPlatformServer } from '@angular/common';
18
18
import { proxyPolyfillCompat } from './base' ;
19
19
import { ɵfetchInstance } from '@angular/fire' ;
20
20
import { REQUEST } from '@nguniversal/express-engine/tokens' ;
21
- import { experimental } from '@angular-devkit/core' ;
21
+ import { get as getCookie , set as setCookie , remove as removeCookie } from 'js-cookie' ;
22
+ import { parse as parseCookies } from 'cookie' ;
22
23
23
24
export interface AngularFireAuth extends ɵPromiseProxy < firebase . auth . Auth > { }
24
25
@@ -35,7 +36,9 @@ export const EXPERIMENTAL_COOKIE_AUTH = new InjectionToken<boolean>('angularfire
35
36
@Injectable ( {
36
37
providedIn : 'any'
37
38
} )
38
- export class AngularFireAuth {
39
+ export class AngularFireAuth implements OnDestroy {
40
+
41
+ private disposables : SubscriptionLike [ ] = [ ] ;
39
42
40
43
/**
41
44
* Observable of authentication state; as of Firebase 4.0 this is only triggered via sign-in/out
@@ -76,7 +79,7 @@ export class AngularFireAuth {
76
79
@Optional ( ) @Inject ( LANGUAGE_CODE ) languageCode : string | null ,
77
80
@Optional ( ) @Inject ( USE_DEVICE_LANGUAGE ) useDeviceLanguage : boolean | null ,
78
81
@Optional ( ) @Inject ( PERSISTENCE ) persistence : string | null ,
79
- @Optional ( ) @Inject ( EXPERIMENTAL_COOKIE_AUTH ) experimentalCookieAuth : boolean | null ,
82
+ @Optional ( ) @Inject ( EXPERIMENTAL_COOKIE_AUTH ) private experimentalCookieAuth : boolean | null ,
80
83
@Optional ( ) @Inject ( REQUEST ) request : any ,
81
84
) {
82
85
const schedulers = new ɵAngularFireSchedulers ( zone ) ;
@@ -120,27 +123,25 @@ export class AngularFireAuth {
120
123
shareReplay ( { bufferSize : 1 , refCount : false } ) ,
121
124
) ;
122
125
126
+ // HACK, as we're exporting auth.Auth, rather than auth, developers importing firebase.auth
127
+ // (e.g, `import { auth } from 'firebase/app'`) are getting an undefined auth object unexpectedly
128
+ // as we're completely lazy. Let's eagerly load the Auth SDK here.
129
+ // There could potentially be race conditions still... but this greatly decreases the odds while
130
+ // we reevaluate the API.
131
+ this . disposables . push ( auth . pipe ( take ( 1 ) ) . subscribe ( ) ) ;
132
+
123
133
const cookieAuthCredential = auth . pipe (
124
134
switchMap ( auth => {
125
- // grabbed from ngx-cookie-service, they don't support universal
126
- const getCookieRegExp = ( name : string ) : RegExp => {
127
- const escapedName : string = name . replace ( / ( [ \[ \] \{ \} \( \) \| \= \; \+ \? \, \. \* \^ \$ ] ) / gi, '\\$1' ) ;
128
- return new RegExp ( '(?:^' + escapedName + '|;\\s*' + escapedName + ')=(.*?)(?:;|$)' , 'g' ) ;
129
- } ;
130
- const cookie = isPlatformServer ( platformId ) ? request . headers . cookie : document . cookie ;
131
- const session = getCookieRegExp ( 'session' ) . exec ( cookie ) ?. [ 1 ] ;
132
- if ( session ) {
133
- const authCredentialJSON = JSON . parse ( decodeURIComponent ( session ) ) ;
134
- const credentialUid = authCredentialJSON . _uid ;
135
- delete authCredentialJSON . _uid ;
136
- const authCredential = firebase . auth . AuthCredential . fromJSON ( authCredentialJSON ) ;
137
- // TODO investigate, this is likely a security concern but is there a way we could utilize this property
138
- // perhaps we can build the _uid into the application name?
139
- if ( auth . currentUser ?. uid === credentialUid ) {
140
- return of ( { user : auth . currentUser , additionalUserInfo : null , credential : authCredential , operationType : null } ) ;
141
- } else {
142
- return auth . signInWithCredential ( authCredential ) ;
143
- }
135
+ const encodedSession = isPlatformServer ( platformId ) ?
136
+ request . headers . cookie && parseCookies ( request . headers . cookie ) . __session :
137
+ getCookie ( '__session' ) ;
138
+ if ( encodedSession ) {
139
+ const session = JSON . parse ( decodeURIComponent ( encodedSession ) ) ;
140
+ const authCredential = firebase . auth . AuthCredential . fromJSON ( session . credential ) ;
141
+ return auth . signInWithCredential ( authCredential ) . then ( it => it , ( e ) => {
142
+ console . warn ( e ) ;
143
+ return null ;
144
+ } ) ;
144
145
} else {
145
146
return of ( null ) ;
146
147
}
@@ -149,13 +150,6 @@ export class AngularFireAuth {
149
150
shareReplay ( { bufferSize : 1 , refCount : false } ) ,
150
151
) ;
151
152
152
- // HACK, as we're exporting auth.Auth, rather than auth, developers importing firebase.auth
153
- // (e.g, `import { auth } from 'firebase/app'`) are getting an undefined auth object unexpectedly
154
- // as we're completely lazy. Let's eagerly load the Auth SDK here.
155
- // There could potentially be race conditions still... but this greatly decreases the odds while
156
- // we reevaluate the API.
157
- const _ = auth . pipe ( first ( ) ) . subscribe ( ) ;
158
-
159
153
const cookieAuthUser = cookieAuthCredential . pipe (
160
154
map ( it => it ?. user )
161
155
) ;
@@ -220,33 +214,6 @@ export class AngularFireAuth {
220
214
observeOn ( schedulers . insideAngular ) ,
221
215
) ;
222
216
223
- if ( experimentalCookieAuth ) {
224
-
225
- /* TODO add the side-effects
226
- it's now seeming like this should be a service instead
227
-
228
- this.cookieExchangeDisposable = this.credential.pipe(
229
- filter(it => !!it),
230
- tap(it => console.log(it.credential)),
231
- ).subscribe(userCredential => {
232
- const json = userCredential.credential.toJSON();
233
- // TODO do we want to put the instanceId on here too?
234
- // any other properties that we can use from the server side?
235
- json['_uid'] = userCredential.user.uid;
236
- // are we taking on ngx-cookie?
237
- cookies.set('session', JSON.stringify(json));
238
- });
239
-
240
- this.user.pipe(
241
- pairwise(),
242
- filter(([a,b]) => !!a && !b)
243
- ).subscribe(() => {
244
- cookies.delete('session');
245
- });
246
- */
247
-
248
- }
249
-
250
217
}
251
218
252
219
this . idToken = this . user . pipe (
@@ -257,6 +224,33 @@ export class AngularFireAuth {
257
224
switchMap ( user => user ? from ( user . getIdTokenResult ( ) ) : of ( null ) )
258
225
) ;
259
226
227
+
228
+ if ( this . experimentalCookieAuth ) {
229
+
230
+ this . disposables . push (
231
+ this . credential . pipe (
232
+ filter ( it => ! ! it )
233
+ ) . subscribe ( userCredential => {
234
+ const session = {
235
+ uid : userCredential . user . uid ,
236
+ credential : userCredential . credential . toJSON ( ) ,
237
+ } ;
238
+ // TODO look at security
239
+ setCookie ( '__session' , JSON . stringify ( session ) ) ;
240
+ } )
241
+ ) ;
242
+
243
+ this . disposables . push (
244
+ this . user . pipe (
245
+ pairwise ( ) ,
246
+ filter ( ( [ a , b ] ) => ! ! a && ! b ) ,
247
+ ) . subscribe ( ( ) => {
248
+ removeCookie ( '__session' ) ;
249
+ } )
250
+ ) ;
251
+
252
+ }
253
+
260
254
return ɵlazySDKProxy ( this , auth , zone , { spy : {
261
255
apply : ( name , _ , val ) => {
262
256
// If they call a signIn or createUser function listen into the promise
@@ -271,6 +265,11 @@ export class AngularFireAuth {
271
265
272
266
}
273
267
268
+ ngOnDestroy ( ) {
269
+ this . disposables . forEach ( it => it . unsubscribe ( ) ) ;
270
+ firebase . apps . forEach ( app => app . auth ( ) . signOut ( ) ) ;
271
+ }
272
+
274
273
}
275
274
276
275
ɵapplyMixins ( AngularFireAuth , [ proxyPolyfillCompat ] ) ;
0 commit comments