From 12aa422798170d3c0a069144a79d8d1c82ad0967 Mon Sep 17 00:00:00 2001 From: David East Date: Sun, 12 Mar 2017 20:57:47 -0700 Subject: [PATCH] feat(auth): New Auth API --- src/angularfire2.ts | 83 ++--------------- src/auth/auth.spec.ts | 4 +- src/auth/auth.ts | 197 +++++---------------------------------- src/database/database.ts | 2 +- src/interfaces.ts | 3 +- 5 files changed, 34 insertions(+), 255 deletions(-) diff --git a/src/angularfire2.ts b/src/angularfire2.ts index 87d5ac85a..3a42125ca 100644 --- a/src/angularfire2.ts +++ b/src/angularfire2.ts @@ -2,100 +2,29 @@ import * as firebase from 'firebase/app'; import * as utils from './utils'; import { FirebaseAppConfigToken, FirebaseApp, _firebaseAppFactory } from './app/index'; import { FirebaseListFactoryOpts, FirebaseObjectFactoryOpts, FirebaseAppConfig } from './interfaces'; -import { FirebaseAppName, WindowLocation, FirebaseAuthConfig } from './tokens'; +import { FirebaseAppName } from './tokens'; import { Injectable, OpaqueToken, NgModule } from '@angular/core'; import { FirebaseSdkAuthBackend, AuthBackend, AngularFireAuth, AuthConfiguration } from './auth/index'; -@Injectable() -export class AngularFire { - constructor( - public auth: AngularFireAuth) {} -} - -export function _getAngularFire(auth: AngularFireAuth) { - return new AngularFire(auth); -} - -export function _getAngularFireAuth(backend: AuthBackend, location: any, config: any) { - return new AngularFireAuth(backend, location, config); -} - -export function _getWindowLocation(){ - return window.location; -} - -export function _getFirebaseAuthBackend(app: FirebaseApp) { - return new FirebaseSdkAuthBackend(app); -} - -export const AuthBackendProvder = { - provide: AuthBackend, - useFactory: _getFirebaseAuthBackend, - deps: [ FirebaseApp ] -}; - -export const WindowLocationProvider = { - provide: WindowLocation, - useFactory: _getWindowLocation -}; - export const FirebaseAppProvider = { provide: FirebaseApp, useFactory: _firebaseAppFactory, deps: [ FirebaseAppConfigToken, FirebaseAppName ] }; -export const FirebaseAuthBackendProvider = { - provide: FirebaseSdkAuthBackend, - useFactory: _getFirebaseAuthBackend, - deps: [ FirebaseApp ] -}; - -export const AngularFireAuthProvider = { - provide: AngularFireAuth, - useFactory: _getAngularFireAuth, - deps: [ AuthBackend, WindowLocation, FirebaseAuthConfig ] -}; - -export const AngularFireProvider = { - provide: AngularFire, - useFactory: _getAngularFire, - deps: [ AngularFireAuth ] -}; - -export const FIREBASE_PROVIDERS:any[] = [ - FirebaseAppProvider, - FirebaseAuthBackendProvider, - AuthBackendProvder, - WindowLocationProvider, - AngularFireAuthProvider, - AngularFireProvider -]; - -export { - FirebaseApp, - FirebaseAppConfigToken, - AngularFireAuth, - WindowLocation -} - -export { AuthMethods, firebaseAuthConfig, AuthProviders, FirebaseAuthState } from './auth/index'; - -export { FirebaseConfig, FirebaseAuthConfig, FirebaseRef, FirebaseUrl, FirebaseUserConfig } from './tokens'; -export { FirebaseAppConfig } from './interfaces'; - @NgModule({ - providers: [FIREBASE_PROVIDERS], + providers: [ FirebaseAppProvider ], }) export class AngularFireModule { - static initializeApp(config: FirebaseAppConfig, authConfig?: AuthConfiguration, appName?: string) { + static initializeApp(config: FirebaseAppConfig, appName?: string) { return { ngModule: AngularFireModule, providers: [ { provide: FirebaseAppConfigToken, useValue: config }, - { provide: FirebaseAppName, useValue: appName }, - { provide: FirebaseAuthConfig, useValue: authConfig } + { provide: FirebaseAppName, useValue: appName } ] } } } + +export { FirebaseApp, FirebaseAppConfigToken, FirebaseAppConfig } diff --git a/src/auth/auth.spec.ts b/src/auth/auth.spec.ts index 6e69be827..fdf0d7820 100644 --- a/src/auth/auth.spec.ts +++ b/src/auth/auth.spec.ts @@ -7,8 +7,8 @@ import { TestBed, inject } from '@angular/core/testing'; import { _do } from 'rxjs/operator/do'; import { take } from 'rxjs/operator/take'; import { skip } from 'rxjs/operator/skip'; -import { FIREBASE_PROVIDERS, FirebaseApp, FirebaseAppConfig, FirebaseAuthState, FirebaseAppConfigToken, AngularFireAuth, AuthMethods, firebaseAuthConfig, AuthProviders, WindowLocation, AngularFireModule } from '../angularfire2'; -import { COMMON_CONFIG, ANON_AUTH_CONFIG } from '../test-config'; +import { FirebaseApp, FirebaseAppConfig, AngularFireModule } from '../angularfire2'; +import { COMMON_CONFIG } from '../test-config'; import { AuthBackend } from './auth_backend'; import { FirebaseSdkAuthBackend } from './firebase_sdk_auth_backend'; diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 8fe958607..95da42722 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -1,188 +1,37 @@ import * as firebase from 'firebase/app'; +import * as utils from '../utils'; import 'firebase/auth'; -import { Provider, Inject, Injectable, Optional } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; +import { Auth } from '../interfaces'; import { Observable } from 'rxjs/Observable'; -import { Observer } from 'rxjs/Observer'; -import { ReplaySubject } from 'rxjs/ReplaySubject'; -import { FirebaseApp, FirebaseAuthConfig, WindowLocation } from '../tokens'; -import * as utils from '../utils'; -import { - authDataToAuthState, - AuthBackend, - AuthProviders, - AuthMethods, - EmailPasswordCredentials, - AuthConfiguration, - FirebaseAuthState, - stripProviderId -} from './auth_backend'; -import { mergeMap } from 'rxjs/operator/mergeMap'; -import { of as observableOf } from 'rxjs/observable/of'; -import { map } from 'rxjs/operator/map'; - -const kBufferSize = 1; - -export const firebaseAuthConfig = (config: AuthConfiguration): any => { - return { provide: FirebaseAuthConfig, useValue: config } -}; +import { observeOn } from 'rxjs/operator/observeOn'; +import { FirebaseApp } from '../app/index'; @Injectable() -export class AngularFireAuth extends ReplaySubject { - private _credentialCache: {[key:string]: any} = {}; - constructor(private _authBackend: AuthBackend, - @Inject(WindowLocation) loc: any, - @Optional() @Inject(FirebaseAuthConfig) private _config?: AuthConfiguration) { - super(kBufferSize); - - let firstPass = true; - let onAuth = this._authBackend.onAuth(); - - mergeMap.call(onAuth, (authState: FirebaseAuthState) => { - if (firstPass) { - firstPass = false; - if(['http:', 'https:'].indexOf(loc.protocol) > -1) { - // Only call getRedirectResult() in a browser - return map.call(this._authBackend.getRedirectResult(), (userCredential: firebase.auth.UserCredential) => { - if (userCredential && userCredential.credential) { - authState = attachCredentialToAuthState(authState, userCredential.credential, userCredential.credential.provider); - this._credentialCache[userCredential.credential.provider] = userCredential.credential; - } - return authState; - }); - } - - } - return observableOf(authState); - }) - .subscribe((authData: FirebaseAuthState) => this._emitAuthData(authData)); - } - - public login(config?: AuthConfiguration): firebase.Promise; - // If logging in with email and password - public login(credentials?: EmailPasswordCredentials | firebase.auth.AuthCredential | string): firebase.Promise; - public login(credentials: EmailPasswordCredentials | firebase.auth.AuthCredential | string, config?: AuthConfiguration): firebase.Promise; - public login(obj1?: any, obj2?: AuthConfiguration): firebase.Promise { - let config: AuthConfiguration = null; - let credentials: EmailPasswordCredentials | firebase.auth.AuthCredential | string = null; - if (arguments.length > 2) { - return this._reject('Login only accepts a maximum of two arguments.'); - } else if (arguments.length == 2) { - credentials = obj1; - config = obj2; - } else if (arguments.length == 1) { - // Check if obj1 is password credentials - if (obj1.password && obj1.email) { - credentials = obj1; - config = {}; - } else { - config = obj1; - } - } - config = this._mergeConfigs(config); - - if (utils.isNil(config.method)) { - return this._reject('You must provide a login method'); - } - let providerMethods = [AuthMethods.Popup, AuthMethods.Redirect, AuthMethods.OAuthToken]; - if (providerMethods.indexOf(config.method) != -1) { - if (utils.isNil(config.provider)) { - return this._reject('You must include a provider to use this auth method.'); - } - } - let credentialsMethods = [AuthMethods.Password, AuthMethods.OAuthToken, AuthMethods.CustomToken]; - if (credentialsMethods.indexOf(config.method) != -1) { - if (!credentials) { - return this._reject('You must include credentials to use this auth method.'); - } - } - - switch (config.method) { - case AuthMethods.Popup: - return this._authBackend.authWithOAuthPopup(config.provider, this._scrubConfig(config)) - .then((userCredential: firebase.auth.UserCredential) => { - // Incorrect type information - this._credentialCache[userCredential.credential.provider] = userCredential.credential; - return authDataToAuthState(userCredential.user, (userCredential).credential); - }); - case AuthMethods.Redirect: - // Gets around typings issue since this method doesn't resolve with a user. - // The method really only does anything with an error, since it redirects. - return >(this._authBackend).authWithOAuthRedirect(config.provider, this._scrubConfig(config)); - case AuthMethods.Anonymous: - return this._authBackend.authAnonymously(this._scrubConfig(config)); - case AuthMethods.Password: - return this._authBackend.authWithPassword(credentials); - case AuthMethods.OAuthToken: - return this._authBackend.authWithOAuthToken(credentials, - this._scrubConfig(config)); - case AuthMethods.CustomToken: - return this._authBackend.authWithCustomToken(credentials); - } - } - - public logout(): Promise { - return this._authBackend.unauth(); - } - - public getAuth(): FirebaseAuthState { - console.warn(`WARNING: the getAuth() API has changed behavior since adding support for Firebase 3. - This will return null for the initial value when the page loads, even if the user is actually logged in. - Please observe the actual authState asynchronously by subscribing to the auth service: af.auth.subscribe(). - The getAuth method will be removed in future releases`); - return this._authBackend.getAuth() - } - - public createUser(credentials: EmailPasswordCredentials): firebase.Promise { - return this._authBackend.createUser(credentials); - } +export class AngularFireAuth { /** - * Merges the config object that is passed in with the configuration - * provided through DI. Giving precendence to the one that was passed. + * Firebase Auth instance */ - private _mergeConfigs(config: AuthConfiguration): AuthConfiguration { - if (this._config == null) - return config; - - return Object.assign({}, this._config, config); - } - - private _reject(msg: string): firebase.Promise { - return (>new Promise((res, rej) => { - return rej(msg); - })); - } + auth: firebase.auth.Auth; + + /** + * Observable of authentication state + */ + authState: Observable; - private _scrubConfig(config: AuthConfiguration, scrubProvider = true): any { - let scrubbed = Object.assign({}, config); - if (scrubProvider) { - delete scrubbed.provider; - } - delete scrubbed.method; - return scrubbed; + constructor(public app: FirebaseApp) { + this.authState = FirebaseAuthStateObservable(app); } - - private _emitAuthData(authData: FirebaseAuthState): void { - if (authData == null) { - this.next(null); - } else { - if (authData.auth && authData.auth.providerData && authData.auth.providerData[0]) { - let providerId = authData.auth.providerData[0].providerId; - let providerCredential = this._credentialCache[providerId]; - if (providerCredential) { - authData = attachCredentialToAuthState(authData, providerCredential, providerId); - } - } - - this.next(authData); - } - } } -function attachCredentialToAuthState (authState: FirebaseAuthState, credential, providerId: string): FirebaseAuthState { - if (!authState) return authState; - // TODO make authState immutable - authState[stripProviderId(providerId)] = credential; - return authState; +/** + * Create an Observable of Firebase authentication state. Each event is called + * within the current zone. + * @param app - Firebase App instance + */ +export function FirebaseAuthStateObservable(app: FirebaseApp) { + const authState = Observable.create(firebase.auth().onAuthStateChanged); + return observeOn.call(authState, new utils.ZoneScheduler(Zone.current)); } diff --git a/src/database/database.ts b/src/database/database.ts index afcc99d87..212a964ba 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -10,7 +10,7 @@ import { FirebaseListObservable, FirebaseObjectObservable, FirebaseObjectFactory @Injectable() export class AngularFireDatabase { - constructor(private app: FirebaseApp) {} + constructor(public app: FirebaseApp) {} list(pathOrRef: PathReference, opts?:FirebaseListFactoryOpts):FirebaseListObservable { const ref = utils.getRef(this.app, pathOrRef); diff --git a/src/interfaces.ts b/src/interfaces.ts index 100c113a4..1d9b0f6ad 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -93,4 +93,5 @@ export type DatabaseSnapshot = firebase.database.DataSnapshot; export type DatabaseReference = firebase.database.Reference; export type DatabaseQuery = firebase.database.Query; export type QueryReference = DatabaseReference | DatabaseQuery; -export type PathReference = QueryReference | string; \ No newline at end of file +export type PathReference = QueryReference | string; +export type Auth = firebase.auth.Auth; \ No newline at end of file