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' ;
44import {
55 FIREBASE_OPTIONS ,
66 FIREBASE_APP_NAME ,
@@ -18,7 +18,8 @@ import { isPlatformServer } from '@angular/common';
1818import { proxyPolyfillCompat } from './base' ;
1919import { ɵfetchInstance } from '@angular/fire' ;
2020import { 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' ;
2223
2324export interface AngularFireAuth extends ɵPromiseProxy < firebase . auth . Auth > { }
2425
@@ -35,7 +36,9 @@ export const EXPERIMENTAL_COOKIE_AUTH = new InjectionToken<boolean>('angularfire
3536@Injectable ( {
3637 providedIn : 'any'
3738} )
38- export class AngularFireAuth {
39+ export class AngularFireAuth implements OnDestroy {
40+
41+ private disposables : SubscriptionLike [ ] = [ ] ;
3942
4043 /**
4144 * Observable of authentication state; as of Firebase 4.0 this is only triggered via sign-in/out
@@ -76,7 +79,7 @@ export class AngularFireAuth {
7679 @Optional ( ) @Inject ( LANGUAGE_CODE ) languageCode : string | null ,
7780 @Optional ( ) @Inject ( USE_DEVICE_LANGUAGE ) useDeviceLanguage : boolean | null ,
7881 @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 ,
8083 @Optional ( ) @Inject ( REQUEST ) request : any ,
8184 ) {
8285 const schedulers = new ɵAngularFireSchedulers ( zone ) ;
@@ -120,27 +123,25 @@ export class AngularFireAuth {
120123 shareReplay ( { bufferSize : 1 , refCount : false } ) ,
121124 ) ;
122125
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+
123133 const cookieAuthCredential = auth . pipe (
124134 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+ } ) ;
144145 } else {
145146 return of ( null ) ;
146147 }
@@ -149,13 +150,6 @@ export class AngularFireAuth {
149150 shareReplay ( { bufferSize : 1 , refCount : false } ) ,
150151 ) ;
151152
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-
159153 const cookieAuthUser = cookieAuthCredential . pipe (
160154 map ( it => it ?. user )
161155 ) ;
@@ -220,33 +214,6 @@ export class AngularFireAuth {
220214 observeOn ( schedulers . insideAngular ) ,
221215 ) ;
222216
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-
250217 }
251218
252219 this . idToken = this . user . pipe (
@@ -257,6 +224,33 @@ export class AngularFireAuth {
257224 switchMap ( user => user ? from ( user . getIdTokenResult ( ) ) : of ( null ) )
258225 ) ;
259226
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+
260254 return ɵlazySDKProxy ( this , auth , zone , { spy : {
261255 apply : ( name , _ , val ) => {
262256 // If they call a signIn or createUser function listen into the promise
@@ -271,6 +265,11 @@ export class AngularFireAuth {
271265
272266 }
273267
268+ ngOnDestroy ( ) {
269+ this . disposables . forEach ( it => it . unsubscribe ( ) ) ;
270+ firebase . apps . forEach ( app => app . auth ( ) . signOut ( ) ) ;
271+ }
272+
274273}
275274
276275ɵapplyMixins ( AngularFireAuth , [ proxyPolyfillCompat ] ) ;
0 commit comments