diff --git a/src/providers/auth.spec.ts b/src/providers/auth.spec.ts index fcc429376..0d1fd2414 100644 --- a/src/providers/auth.spec.ts +++ b/src/providers/auth.spec.ts @@ -1,6 +1,6 @@ /// -import {expect, describe,it,beforeEach} from 'angular2/testing'; +import {expect, describe, it, iit, beforeEach} from 'angular2/testing'; import {Injector, provide, Provider} from 'angular2/core'; import {Observable} from 'rxjs/Observable' import { @@ -29,7 +29,8 @@ describe('FirebaseAuth', () => { accessToken: 'accessToken', displayName: 'github User', username: 'githubUsername', - id: '12345' + id: '12345', + expires: 0 } const authObj = { @@ -40,22 +41,24 @@ describe('FirebaseAuth', () => { provider: 'github', uid: 'github:12345', github: providerMetadata, - auth: authObj + auth: authObj, + expires: 0 }; const AngularFireAuthState = { provider: AuthProviders.Github, uid: 'github:12345', github: providerMetadata, - auth: authObj + auth: authObj, + expires: 0 } - beforeEach (() => { + beforeEach(() => { authData = null; authCb = null; injector = Injector.resolveAndCreate([ provide(FirebaseUrl, { - useValue: 'https://angularfire2.firebaseio-demo.com/' + useValue: 'https://angularfire2-auth.firebaseio-demo.com/' }), FIREBASE_PROVIDERS ]); @@ -121,41 +124,41 @@ describe('FirebaseAuth', () => { function getArgIndex(callbackName: string): number { //In the firebase API, the completion callback is the second argument for all but a few functions. - switch (callbackName){ + switch (callbackName) { case 'authAnonymously': case 'onAuth': return 0; case 'authWithOAuthToken': return 2; - default : + default: return 1; } } // calls the firebase callback - function callback(callbackName: string, callIndex?: number): Function{ + function callback(callbackName: string, callIndex?: number): Function { callIndex = callIndex || 0; //assume the first call. var argIndex = getArgIndex(callbackName); - return ( ref)[callbackName].calls.argsFor(callIndex)[argIndex]; + return (ref)[callbackName].calls.argsFor(callIndex)[argIndex]; } - describe ('firebaseAuthConfig', () => { + describe('firebaseAuthConfig', () => { beforeEach(() => { ref = jasmine.createSpyObj('ref', - ['authWithCustomToken','authAnonymously','authWithPassword', - 'authWithOAuthPopup','authWithOAuthRedirect','authWithOAuthToken', - 'unauth','getAuth', 'onAuth', 'offAuth', - 'createUser','changePassword','changeEmail','removeUser','resetPassword' + ['authWithCustomToken', 'authAnonymously', 'authWithPassword', + 'authWithOAuthPopup', 'authWithOAuthRedirect', 'authWithOAuthToken', + 'unauth', 'getAuth', 'onAuth', 'offAuth', + 'createUser', 'changePassword', 'changeEmail', 'removeUser', 'resetPassword' ]); - backend = new FirebaseSdkAuthBackend(ref); + backend = new FirebaseSdkAuthBackend(ref); }); it('should return a provider', () => { - expect(firebaseAuthConfig({method: AuthMethods.Password})).toBeAnInstanceOf(Provider); + expect(firebaseAuthConfig({ method: AuthMethods.Password })).toBeAnInstanceOf(Provider); }); it('should use config in login', () => { - let config= { + let config = { method: AuthMethods.Anonymous }; let auth = new FirebaseAuth(backend, config); @@ -170,7 +173,7 @@ describe('FirebaseAuth', () => { }; let auth = new FirebaseAuth(backend, config); auth.login(); - expect(ref.authAnonymously).toHaveBeenCalledWith(jasmine.any(Function), {remember: 'default'}); + expect(ref.authAnonymously).toHaveBeenCalledWith(jasmine.any(Function), { remember: 'default' }); }); it('should be overridden by login\'s arguments', () => { @@ -201,21 +204,44 @@ describe('FirebaseAuth', () => { }); }); - describe ('login', () => { + describe('createUser', () => { + let auth: FirebaseAuth = null; + let credentials = { email: 'myname', password: 'password' }; + + beforeEach(() => { + ref = jasmine.createSpyObj('ref', + ['authWithCustomToken', 'authAnonymously', 'authWithPassword', + 'authWithOAuthPopup', 'authWithOAuthRedirect', 'authWithOAuthToken', + 'unauth', 'getAuth', 'onAuth', 'offAuth', + 'createUser', 'changePassword', 'changeEmail', 'removeUser', 'resetPassword' + ]); + backend = new FirebaseSdkAuthBackend(ref); + auth = new FirebaseAuth(backend); + }); + + it('should call createUser on a db reference', () => { + auth.createUser(credentials); + expect(ref.createUser) + .toHaveBeenCalledWith(credentials, jasmine.any(Function)); + }); + + }); + + describe('login', () => { let auth: FirebaseAuth = null; beforeEach(() => { ref = jasmine.createSpyObj('ref', - ['authWithCustomToken','authAnonymously','authWithPassword', - 'authWithOAuthPopup','authWithOAuthRedirect','authWithOAuthToken', - 'unauth','getAuth', 'onAuth', 'offAuth', - 'createUser','changePassword','changeEmail','removeUser','resetPassword' + ['authWithCustomToken', 'authAnonymously', 'authWithPassword', + 'authWithOAuthPopup', 'authWithOAuthRedirect', 'authWithOAuthToken', + 'unauth', 'getAuth', 'onAuth', 'offAuth', + 'createUser', 'changePassword', 'changeEmail', 'removeUser', 'resetPassword' ]); backend = new FirebaseSdkAuthBackend(ref); auth = new FirebaseAuth(backend); }); - it('should reject if password is used without credentials', (done:any) => { + it('should reject if password is used without credentials', (done: any) => { let config = { method: AuthMethods.Password }; @@ -223,7 +249,7 @@ describe('FirebaseAuth', () => { auth.login().then(done.fail, done); }); - it('should reject if custom token is used without credentials', (done:any) => { + it('should reject if custom token is used without credentials', (done: any) => { let config = { method: AuthMethods.CustomToken }; @@ -231,7 +257,7 @@ describe('FirebaseAuth', () => { auth.login().then(done.fail, done);; }); - it('should reject if oauth token is used without credentials', (done:any) => { + it('should reject if oauth token is used without credentials', (done: any) => { let config = { method: AuthMethods.OAuthToken }; @@ -239,7 +265,7 @@ describe('FirebaseAuth', () => { auth.login().then(done.fail, done); }); - it('should reject if popup is used without a provider', (done:any) => { + it('should reject if popup is used without a provider', (done: any) => { let config = { method: AuthMethods.Popup }; @@ -247,7 +273,7 @@ describe('FirebaseAuth', () => { auth.login().then(done.fail, done); }); - it('should reject if redirect is used without a provider', (done:any) => { + it('should reject if redirect is used without a provider', (done: any) => { let config = { method: AuthMethods.Redirect }; @@ -267,15 +293,15 @@ describe('FirebaseAuth', () => { it('passes custom token to underlying method', () => { auth.login(credentials, options); expect(ref.authWithCustomToken) - .toHaveBeenCalledWith('myToken', jasmine.any(Function), {remember: 'default'}); + .toHaveBeenCalledWith('myToken', jasmine.any(Function), { remember: 'default' }); }); - it('will reject the promise if authentication fails', (done:any) => { + it('will reject the promise if authentication fails', (done: any) => { auth.login(credentials, options).then(done.fail, done); callback('authWithCustomToken')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(credentials, options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); @@ -291,15 +317,15 @@ describe('FirebaseAuth', () => { }; it('passes options object to underlying method', () => { auth.login(options); - expect(ref.authAnonymously).toHaveBeenCalledWith(jasmine.any(Function), {remember: 'default'}); + expect(ref.authAnonymously).toHaveBeenCalledWith(jasmine.any(Function), { remember: 'default' }); }); - it('will reject the promise if authentication fails', (done:any) => { + it('will reject the promise if authentication fails', (done: any) => { auth.login(options).then(done.fail, done); callback('authAnonymously')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); @@ -309,24 +335,40 @@ describe('FirebaseAuth', () => { }); describe('authWithPassword', () => { - let options = {remember: 'default', method: AuthMethods.Password}; - let credentials = {email:'myname', password:'password'}; + let options = { remember: 'default', method: AuthMethods.Password }; + let credentials = { email: 'myname', password: 'password' }; + + it('should login with password credentials', () => { + let config = { + method: AuthMethods.Password, + provider: AuthProviders.Password + }; + const credentials = { + email: 'david@fire.com', + password: 'supersecretpassword' + }; + let auth = new FirebaseAuth(backend, config); + auth.login(credentials); + expect(ref.authWithPassword).toHaveBeenCalledWith(credentials, + jasmine.any(Function), + { provider: config.provider }); + }); it('passes options and credentials object to underlying method', () => { auth.login(credentials, options); expect(ref.authWithPassword).toHaveBeenCalledWith( credentials, jasmine.any(Function), - {remember: options.remember} + { remember: options.remember } ); }); - it('will revoke the promise if authentication fails', (done:any) => { + it('will revoke the promise if authentication fails', (done: any) => { auth.login(credentials, options).then(done.fail, done); callback('authWithPassword')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(credentials, options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); @@ -335,28 +377,28 @@ describe('FirebaseAuth', () => { }); }); - describe('authWithOAuthPopup',function(){ + describe('authWithOAuthPopup', function() { let options = { method: AuthMethods.Popup, provider: AuthProviders.Github }; it('passes provider and options object to underlying method', () => { - let customOptions = Object.assign ({}, options); + let customOptions = Object.assign({}, options); customOptions.scope = ['email']; auth.login(customOptions); expect(ref.authWithOAuthPopup).toHaveBeenCalledWith( 'github', jasmine.any(Function), - {scope: ['email']} + { scope: ['email'] } ); }); - it('will reject the promise if authentication fails', (done:any) => { + it('will reject the promise if authentication fails', (done: any) => { auth.login(options).then(done.fail, done); callback('authWithOAuthPopup')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); @@ -371,27 +413,27 @@ describe('FirebaseAuth', () => { provider: AuthProviders.Github }; it('passes provider and options object to underlying method', () => { - let customOptions = Object.assign({} , options); + let customOptions = Object.assign({}, options); customOptions.scope = ['email']; auth.login(customOptions); expect(ref.authWithOAuthRedirect).toHaveBeenCalledWith( 'github', jasmine.any(Function), - {scope: ['email']} + { scope: ['email'] } ); }); - it('will reject the promise if authentication fails', (done:any) => { + it('will reject the promise if authentication fails', (done: any) => { auth.login(options).then(done.fail, done); callback('authWithOAuthRedirect')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); }, done.fail); - callback('authWithOAuthRedirect')(null,authState); + callback('authWithOAuthRedirect')(null, authState); }); }); @@ -411,12 +453,12 @@ describe('FirebaseAuth', () => { 'github', token, jasmine.any(Function), - {scope: ['email']} + { scope: ['email'] } ); }); it('passes provider, OAuth credentials, and options object to underlying method', () => { - let customOptions = Object.assign ({}, options); + let customOptions = Object.assign({}, options); customOptions.provider = AuthProviders.Twitter; let twitterCredentials = { "user_id": "", @@ -428,11 +470,11 @@ describe('FirebaseAuth', () => { 'twitter', twitterCredentials, jasmine.any(Function), - {scope: ['email']} + { scope: ['email'] } ); }); - it('will reject the promise if authentication fails', (done:any) => { + it('will reject the promise if authentication fails', (done: any) => { let creds = { token: '' }; @@ -440,7 +482,7 @@ describe('FirebaseAuth', () => { callback('authWithOAuthToken')('myError'); }); - it('will resolve the promise upon authentication', (done:any) => { + it('will resolve the promise upon authentication', (done: any) => { auth.login(credentials, options).then(result => { expect(result).toEqual(AngularFireAuthState); done(); @@ -450,14 +492,14 @@ describe('FirebaseAuth', () => { }); - describe('unauth()',() => { + describe('unauth()', () => { it('will call unauth() on the backing ref if logged in', () => { - ( ref).getAuth.and.returnValue({provider: 'twitter'}); auth.logout(); + (ref).getAuth.and.returnValue({ provider: 'twitter' }); auth.logout(); expect(ref.unauth).toHaveBeenCalled(); }); it('will NOT call unauth() on the backing ref if NOT logged in', () => { - ( ref).getAuth.and.returnValue(null); + (ref).getAuth.and.returnValue(null); auth.logout(); expect(ref.unauth).not.toHaveBeenCalled(); }); diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 605c1db16..c1dd42f9d 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -2,6 +2,7 @@ import {Provider, Inject, provide, Injectable, Optional} from 'angular2/core'; import {ReplaySubject} from 'rxjs/subject/ReplaySubject'; import {FirebaseRef, FirebaseAuthConfig} from '../tokens'; import {isPresent} from '../utils/utils'; +import * as utils from '../utils/utils'; import { AuthBackend, AuthProviders, @@ -26,14 +27,15 @@ export const firebaseAuthConfig = (config: AuthConfiguration): Provider => { @Injectable() export class FirebaseAuth extends ReplaySubject { - constructor (private _authBackend: AuthBackend, - @Optional() @Inject(FirebaseAuthConfig) private _config?: AuthConfiguration) { - super (kBufferSize); + constructor(private _authBackend: AuthBackend, + @Optional() @Inject(FirebaseAuthConfig) private _config?: AuthConfiguration) { + super(kBufferSize); this._authBackend.onAuth((authData) => this._emitAuthData(authData)); } public login(config?: AuthConfiguration): Promise; + public login(credentials?: FirebaseCredentials): Promise; public login(credentials: AuthCredentials, config?: AuthConfiguration): Promise; public login(obj1?: any, obj2?: AuthConfiguration): Promise { let config: AuthConfiguration = null; @@ -44,7 +46,13 @@ export class FirebaseAuth extends ReplaySubject { credentials = obj1; config = obj2; } else if (arguments.length == 1) { - config = obj1; + // Check if obj1 is password credentials + if (obj1.password && obj1.email) { + credentials = obj1; + config = {}; + } else { + config = obj1; + } } config = this._mergeConfigs(config); @@ -52,13 +60,13 @@ export class FirebaseAuth extends ReplaySubject { return this._reject('You must provide a login method'); } let providerMethods = [AuthMethods.Popup, AuthMethods.Redirect, AuthMethods.OAuthToken]; - if (providerMethods.indexOf(config.method) != -1){ - if (!isPresent(config.provider)) { - return this._reject('You must include a provider to use this auth method.'); - } + if (providerMethods.indexOf(config.method) != -1) { + if (!isPresent(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 (credentialsMethods.indexOf(config.method) != -1) { if (!credentials) { return this._reject('You must include credentials to use this auth method.'); } @@ -72,13 +80,13 @@ export class FirebaseAuth extends ReplaySubject { case AuthMethods.Anonymous: return this._authBackend.authAnonymously(this._scrubConfig(config)); case AuthMethods.Password: - return this._authBackend.authWithPassword( credentials, this._scrubConfig(config, false)); + return this._authBackend.authWithPassword(credentials, this._scrubConfig(config, false)); case AuthMethods.OAuthToken: - return this._authBackend.authWithOAuthToken(config.provider, credentials, - this._scrubConfig(config)); + return this._authBackend.authWithOAuthToken(config.provider, credentials, + this._scrubConfig(config)); case AuthMethods.CustomToken: - return this._authBackend.authWithCustomToken(( credentials).token, - this._scrubConfig(config, false)); + return this._authBackend.authWithCustomToken((credentials).token, + this._scrubConfig(config, false)); } } @@ -88,6 +96,14 @@ export class FirebaseAuth extends ReplaySubject { } } + public getAuth(): FirebaseAuthData { + return this._authBackend.getAuth(); + } + + public createUser(credentials: FirebaseCredentials): Promise { + return this._authBackend.createUser(credentials); + } + /** * Merges the config object that is passed in with the configuration * provided through DI. Giving precendence to the one that was passed. @@ -101,12 +117,12 @@ export class FirebaseAuth extends ReplaySubject { private _reject(msg: string): Promise { return new Promise((res, rej) => { - return rej (msg); + return rej(msg); }); } - private _scrubConfig (config: AuthConfiguration, scrubProvider = true): any { - let scrubbed = Object.assign ({}, config); + private _scrubConfig(config: AuthConfiguration, scrubProvider = true): any { + let scrubbed = Object.assign({}, config); if (scrubProvider) { delete scrubbed.provider; } diff --git a/src/providers/auth_backend.ts b/src/providers/auth_backend.ts index 8be91bc88..9d99a9010 100644 --- a/src/providers/auth_backend.ts +++ b/src/providers/auth_backend.ts @@ -5,10 +5,11 @@ export abstract class AuthBackend { abstract authWithOAuthPopup(provider: AuthProviders, options?: any): Promise; abstract authWithOAuthRedirect(provider: AuthProviders, options?: any): Promise; abstract authWithOAuthToken(provider: AuthProviders, credentialsObj: OAuthCredentials, options?: any) - : Promise; + : Promise; abstract onAuth(onComplete: (authData: FirebaseAuthData) => void): void; abstract getAuth(): FirebaseAuthData; abstract unauth(): void; + abstract createUser(credentials: FirebaseCredentials): Promise; } // Firebase only provides typings for google @@ -65,10 +66,10 @@ export type AuthCredentials = FirebaseCredentials | OAuthCredentials; export interface FirebaseAuthState { uid: string; provider: AuthProviders; - auth: Object; + auth: Object; expires?: number; github?: any; - google?: any; + google?: any; twitter?: any; facebook?: any; password?: any; @@ -77,7 +78,7 @@ export interface FirebaseAuthState { export function authDataToAuthState(authData: FirebaseAuthDataAllProviders): FirebaseAuthState { let {auth, uid, provider, github, twitter, facebook, google, password, anonymous} = authData; - let authState: FirebaseAuthState = {auth, uid, provider: null}; + let authState: FirebaseAuthState = { auth, uid, expires: authData.expires, provider: null }; switch (provider) { case 'github': authState.github = github; diff --git a/src/providers/firebase_sdk_auth_backend.ts b/src/providers/firebase_sdk_auth_backend.ts index 0f31a2802..264b83af0 100644 --- a/src/providers/firebase_sdk_auth_backend.ts +++ b/src/providers/firebase_sdk_auth_backend.ts @@ -13,12 +13,24 @@ import {isPresent} from '../utils/utils'; import * as Firebase from 'firebase'; @Injectable() -export class FirebaseSdkAuthBackend extends AuthBackend{ - constructor (@Inject(FirebaseRef) private _fbRef: Firebase, - private _webWorkerMode = false) { +export class FirebaseSdkAuthBackend extends AuthBackend { + constructor( @Inject(FirebaseRef) private _fbRef: Firebase, + private _webWorkerMode = false) { super(); } + createUser(creds: FirebaseCredentials): Promise { + return new Promise((resolve, reject) => { + this._fbRef.createUser(creds, (err, authData) => { + if (err) { + reject(err); + } else { + resolve(authData); + } + }); + }); + } + onAuth(onComplete: (authData: FirebaseAuthData) => void): void { this._fbRef.onAuth(onComplete); } @@ -59,7 +71,7 @@ export class FirebaseSdkAuthBackend extends AuthBackend{ authWithOAuthPopup(provider: AuthProviders, options?: any): Promise { let p = new Promise((res, rej) => { this._fbRef.authWithOAuthPopup(this._providerToString(provider), - this._handleFirebaseCb(res, rej, options), options); + this._handleFirebaseCb(res, rej, options), options); }); return p; @@ -73,20 +85,20 @@ export class FirebaseSdkAuthBackend extends AuthBackend{ authWithOAuthRedirect(provider: AuthProviders, options?: any): Promise { let p = new Promise((res, rej) => { this._fbRef.authWithOAuthRedirect(this._providerToString(provider), - this._handleFirebaseCb(res, rej, options), options); + this._handleFirebaseCb(res, rej, options), options); }); return p; } authWithOAuthToken(provider: AuthProviders, credentialsObj: OAuthCredentials, options?: any) - : Promise { + : Promise { let p = new Promise((res, rej) => { let credentials = isPresent((credentialsObj).token) - ? ( credentialsObj).token - : credentialsObj; + ? (credentialsObj).token + : credentialsObj; this._fbRef.authWithOAuthToken(this._providerToString(provider), credentials, - this._handleFirebaseCb(res, rej, options), options); + this._handleFirebaseCb(res, rej, options), options); }); return p; @@ -95,15 +107,15 @@ export class FirebaseSdkAuthBackend extends AuthBackend{ private _handleFirebaseCb(res: Function, rej: Function, options: any): (err: any, auth?: FirebaseAuthData) => void { return (err, auth?) => { if (err) { - return rej (err); + return rej(err); } else { if (!this._webWorkerMode) - return res (authDataToAuthState(auth)); + return res(authDataToAuthState(auth)); else { if (isPresent(options) && isPresent(options.remember)) { // Add remember value in WebWorker mode so that the worker // can auth with the same value - ( auth).remember = options.remember; + (auth).remember = options.remember; } return res(auth); } @@ -121,7 +133,7 @@ export class FirebaseSdkAuthBackend extends AuthBackend{ return 'facebook'; case AuthProviders.Google: return 'google'; - default: + default: throw new Error(`Unsupported firebase auth provider ${provider}`); } } diff --git a/src/providers/web_workers/worker/auth.ts b/src/providers/web_workers/worker/auth.ts index 99c74af1a..4f367e6be 100644 --- a/src/providers/web_workers/worker/auth.ts +++ b/src/providers/web_workers/worker/auth.ts @@ -58,6 +58,18 @@ export class WebWorkerFirebaseAuth extends AuthBackend { getAuth(): FirebaseAuthData { return this._fbRef.getAuth(); } + + createUser(creds: FirebaseCredentials): Promise { + return new Promise((resolve, reject) => { + this._fbRef.createUser(creds, (err, authData) => { + if (err) { + reject(err); + } else { + resolve(authData); + } + }); + }); + } authAnonymously(options?: any): Promise { let args = new UiArguments('authAnonymously', [new FnArg(options, PRIMITIVE)]);