Skip to content

Commit 3c63b16

Browse files
authored
Migrate authentication to Firebase Modular SDK (#201)
1 parent a25735e commit 3c63b16

File tree

12 files changed

+82
-162
lines changed

12 files changed

+82
-162
lines changed

addon/authenticators/firebase.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { getOwner } from '@ember/application';
22
import { inject as service } from '@ember/service';
33

4+
import { User, UserCredential } from 'firebase/auth';
45
import BaseAuthenticator from 'ember-simple-auth/authenticators/base';
5-
import FirebaseService from 'ember-cloud-firestore-adapter/services/-firebase';
66
import firebase from 'firebase/compat/app';
77

8+
import {
9+
getRedirectResult,
10+
onAuthStateChanged,
11+
signInWithCustomToken,
12+
signOut,
13+
} from 'ember-cloud-firestore-adapter/firebase/auth';
14+
import FirebaseService from 'ember-cloud-firestore-adapter/services/-firebase';
15+
816
interface AuthenticateCallback {
9-
(auth: firebase.auth.Auth): Promise<firebase.auth.UserCredential>;
17+
(auth: firebase.auth.Auth): Promise<UserCredential>;
1018
}
1119

1220
export default class FirebaseAuthenticator extends BaseAuthenticator {
@@ -18,20 +26,20 @@ export default class FirebaseAuthenticator extends BaseAuthenticator {
1826
return getOwner(this).lookup('service:fastboot');
1927
}
2028

21-
public async authenticate(
22-
callback: AuthenticateCallback,
23-
): Promise<{ user: firebase.User | null }> {
29+
public async authenticate(callback: AuthenticateCallback): Promise<{ user: User | null }> {
2430
const auth = this.firebase.auth();
2531
const credential = await callback(auth);
2632

2733
return { user: credential.user };
2834
}
2935

3036
public invalidate(): Promise<void> {
31-
return this.firebase.auth().signOut();
37+
const auth = this.firebase.auth();
38+
39+
return signOut(auth);
3240
}
3341

34-
public restore(): Promise<{ user: firebase.User | null }> {
42+
public restore(): Promise<{ user: User | null }> {
3543
return new Promise((resolve, reject) => {
3644
const auth = this.firebase.auth();
3745

@@ -42,7 +50,7 @@ export default class FirebaseAuthenticator extends BaseAuthenticator {
4250
const token = this.fastboot.request.headers.get('Authorization')?.split('Bearer ')[1];
4351

4452
if (token) {
45-
auth.signInWithCustomToken(token).then((credential) => {
53+
signInWithCustomToken(auth, token).then((credential) => {
4654
resolve({ user: credential.user });
4755
}).catch(() => {
4856
reject();
@@ -51,13 +59,13 @@ export default class FirebaseAuthenticator extends BaseAuthenticator {
5159
reject();
5260
}
5361
} else {
54-
const unsubscribe = auth.onAuthStateChanged(async (user) => {
62+
const unsubscribe = onAuthStateChanged(auth, async (user) => {
5563
unsubscribe();
5664

5765
if (user) {
5866
resolve({ user });
5967
} else {
60-
auth.getRedirectResult().then((credential) => {
68+
getRedirectResult(auth).then((credential) => {
6169
if (credential) {
6270
resolve({ user: credential.user });
6371
} else {

docs/authentication.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ Using the `session` service provided by ember-simple-auth, a callback must be pa
99
The callback will have the [`firebase.auth.Auth`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth) as a param. Use this to authenticate the user using any of the providers available. It **must** also return a promise that will resolve to an instance of [`firebase.auth.UserCredential`](https://firebase.google.com/docs/reference/js/v8/firebase.auth#usercredential).
1010

1111
```javascript
12+
import { signInWithEmailAndPassword } from 'ember-cloud-firestore-adapter/firebase/auth';
13+
1214
this.session.authenticate('authenticator:firebase', (auth) => {
13-
return auth.signInWithEmailAndPassword('my_email@gmail.com', 'my_password');
15+
return signInWithEmailAndPassword(auth, 'my_email@gmail.com', 'my_password');
1416
});
1517
```
1618

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
1+
import { action } from '@ember/object';
2+
import { inject as service } from '@ember/service';
13
import Controller from '@ember/controller';
24

5+
import SessionService from 'ember-simple-auth/services/session';
6+
import firebase from 'firebase/compat/app';
7+
8+
import {
9+
createUserWithEmailAndPassword,
10+
signInWithEmailAndPassword,
11+
} from 'ember-cloud-firestore-adapter/firebase/auth';
12+
313
export default class ApplicationController extends Controller {
14+
@service
15+
declare public session: SessionService;
16+
417
public updateRecordParam: string = Math.random().toString(32).slice(2).substr(0, 5);
18+
19+
@action
20+
login(): void {
21+
this.session.authenticate('authenticator:firebase', (auth: firebase.auth.Auth) => (
22+
createUserWithEmailAndPassword(auth, 'foo@gmail.com', 'foobar')
23+
).then((credential) => credential.user).catch(() => (
24+
signInWithEmailAndPassword(auth, 'foo@gmail.com', 'foobar')
25+
)));
26+
}
27+
28+
@action
29+
logout(): void {
30+
this.session.invalidate();
31+
}
532
}

tests/dummy/app/controllers/login.ts

-9
This file was deleted.

tests/dummy/app/controllers/logout.ts

-9
This file was deleted.

tests/dummy/app/router.js

-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,4 @@ Router.map(function() {
1414
this.route('update-record', { path: '/update-record/:title' });
1515
this.route('delete-record');
1616
this.route('features');
17-
this.route('login');
18-
this.route('logout');
1917
});

tests/dummy/app/routes/login.ts

-18
This file was deleted.

tests/dummy/app/routes/logout.ts

-13
This file was deleted.
+7-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
<h2 id="title">Ember Cloud Firestore Dummy App</h2>
22

3+
<p>Authenticated: {{this.session.isAuthenticated}}</p>
4+
5+
<div>
6+
<button type="button" {{on 'click' this.login}}>Login</button>
7+
<button type="button" {{on 'click' this.logout}}>Logout</button>
8+
</div>
9+
310
<LinkTo @route="create-record">Create Record</LinkTo>
411
<LinkTo @route="update-record" @model={{this.updateRecordParam}}>Update Record</LinkTo>
512
<LinkTo @route="delete-record">Delete Record</LinkTo>
613
<LinkTo @route="find-all">Find All</LinkTo>
714
<LinkTo @route="find-record">Find Record</LinkTo>
815
<LinkTo @route="query">Query</LinkTo>
9-
<LinkTo @route="login">Login</LinkTo>
10-
<LinkTo @route="logout">Logout</LinkTo>
1116

1217
{{outlet}}

tests/dummy/app/templates/login.hbs

-3
This file was deleted.

tests/dummy/app/templates/logout.hbs

-3
This file was deleted.
+27-92
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
import { module, test } from 'qunit';
22
import { setupTest } from 'ember-qunit';
33

4-
import sinon from 'sinon';
4+
import firebase from 'firebase/compat/app';
5+
6+
import { signInAnonymously, signOut } from 'ember-cloud-firestore-adapter/firebase/auth';
7+
import resetFixtureData from '../../helpers/reset-fixture-data';
58

69
module('Unit | Authenticator | firebase', function (hooks) {
10+
let auth: firebase.auth.Auth;
11+
let db: firebase.firestore.Firestore;
12+
713
setupTest(hooks);
814

15+
hooks.beforeEach(async function () {
16+
const app = this.owner.lookup('service:-firebase');
17+
18+
auth = app.auth();
19+
db = app.firestore();
20+
21+
await signInAnonymously(auth);
22+
await resetFixtureData(db);
23+
});
24+
25+
hooks.afterEach(async function () {
26+
await signOut(auth);
27+
await resetFixtureData(db);
28+
});
29+
930
module('authenticate()', function () {
1031
test('should use the callback', async function (assert) {
1132
assert.expect(1);
@@ -22,118 +43,32 @@ module('Unit | Authenticator | firebase', function (hooks) {
2243
});
2344

2445
module('invalidate()', function () {
25-
test('should sign out firebase user', function (assert) {
46+
test('should sign out firebase user', async function (assert) {
2647
assert.expect(1);
2748

2849
// Arrange
2950
const authenticator = this.owner.lookup('authenticator:firebase');
30-
const signOutStub = sinon.stub();
31-
const firebase = {
32-
auth: sinon.stub().returns({ signOut: signOutStub }),
33-
};
34-
35-
authenticator.set('firebase', firebase);
3651

3752
// Act
38-
authenticator.invalidate();
53+
await authenticator.invalidate();
3954

4055
// Assert
41-
assert.ok(signOutStub.calledOnce);
56+
assert.equal(auth.currentUser, null);
4257
});
4358
});
4459

4560
module('restore()', function () {
46-
test('should sign in using custom token when fastboot header contains authorization token', async function (assert) {
47-
assert.expect(1);
48-
49-
// Arrange
50-
const authenticator = this.owner.lookup('authenticator:firebase');
51-
const fastboot = this.owner.lookup('service:fastboot');
52-
53-
fastboot.set('isFastBoot', true);
54-
fastboot.set('request', {
55-
headers: new Headers({
56-
Authorization: 'Bearer 123',
57-
}),
58-
});
59-
60-
const userCredential = {
61-
user: { id: 'foo' },
62-
};
63-
const firebase = {
64-
auth: sinon.stub().returns({
65-
signInWithCustomToken: sinon
66-
.stub()
67-
.withArgs('123')
68-
.returns(Promise.resolve(userCredential)),
69-
}),
70-
};
71-
72-
authenticator.set('firebase', firebase);
73-
74-
// Act
75-
const result = await authenticator.restore();
76-
77-
// Assert
78-
assert.deepEqual(result, {
79-
user: { id: 'foo' },
80-
});
81-
});
82-
83-
test('should return the authenticated user from auth state changed', async function (assert) {
84-
assert.expect(1);
85-
86-
// Arrange
87-
const authenticator = this.owner.lookup('authenticator:firebase');
88-
const user = { id: 'foo' };
89-
const firebase = {
90-
auth: sinon.stub().returns({
91-
onAuthStateChanged(callback: (firebaseUser: { id: string }) => void) {
92-
setTimeout(() => callback(user));
93-
94-
return () => {};
95-
},
96-
}),
97-
};
98-
99-
authenticator.set('firebase', firebase);
100-
101-
// Act
102-
const result = await authenticator.restore();
103-
104-
// Assert
105-
assert.deepEqual(result, {
106-
user: { id: 'foo' },
107-
});
108-
});
109-
110-
test('should return the authenticated user from a redirect result', async function (assert) {
61+
test('should return the authenticated user', async function (assert) {
11162
assert.expect(1);
11263

11364
// Arrange
11465
const authenticator = this.owner.lookup('authenticator:firebase');
115-
const user = { id: 'foo' };
116-
const firebase = {
117-
auth: sinon.stub().returns({
118-
getRedirectResult: sinon.stub().returns(Promise.resolve({ user })),
119-
120-
onAuthStateChanged(callback: () => void) {
121-
setTimeout(() => callback());
122-
123-
return () => {};
124-
},
125-
}),
126-
};
127-
128-
authenticator.set('firebase', firebase);
12966

13067
// Act
13168
const result = await authenticator.restore();
13269

13370
// Assert
134-
assert.deepEqual(result, {
135-
user: { id: 'foo' },
136-
});
71+
assert.ok(result.user.isAnonymous);
13772
});
13873
});
13974
});

0 commit comments

Comments
 (0)