From 5e4fa3481a5bda3d85475ed7faf9e5e52bf2b0e1 Mon Sep 17 00:00:00 2001 From: Daniyel Date: Wed, 24 Jun 2020 11:59:25 +0300 Subject: [PATCH 1/8] Fix some typos, missing spaces. --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 19ef870..0fa87c0 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,18 @@ A performance focused alternative to the official firebase auth library that is designed to work with my other alternatives for [storage](https://github.com/samuelgozi/firebase-storage-lite) and [firestore](https://github.com/samuelgozi/firebase-firestore-lite). -The goal of this library is to provide a performance focused alternative to the official SDKs. This comes with some costs. The big one is browser support, we only support modern browsers, but you can always rin them through Babel. +The goal of this library is to provide a performance focused alternative to the official SDKs. This comes with some costs. The big one is browser support, we only support modern browsers, but you can always run them through Babel. [My Alternative SDK performs in average 13 times better and is 27 times smaller than the official ones](https://github.com/samuelgozi/firebase-firestore-lite/wiki/Firebase-Alternative-SDK-Benchmarks). ## What else do I need to consider? -The API is completely different. This is not a drop in replacement, instead our API is much simpler and easier to use. +The API is completely different. This is not a drop-in replacement, instead our API is much simpler and easier to use. In addition you should consider the next points: 1. This is still work in progress and the API will change without warning until version 1.0. 2. There is a small difference working with Federated Identity Providers. -3. Sessions can only be persisted in localStorage(More options will be added). +3. Sessions can only be persisted in localStorage (More options will be added). 4. The code is written with modern JS and you are responsible for tranpiling it for your targets, but babelrc configuration is ready. The code also makes use of the Fetch API and local storage. 5. Not fully tested yet(I don't have a good testing strategy yet...) @@ -21,7 +21,7 @@ In addition you should consider the next points: - [x] Authenticate with Email and password. - [x] Authenticate with Federated Identity Provider. -- [x] Authenticate with link to email(no password required). +- [x] Authenticate with link to email (no password required). - [x] Authenticate with a custom token. - [x] Authenticate anonymously. - [ ] Authenticate with phone. @@ -46,9 +46,9 @@ You might be curious as to why I'm auoiding using firebases endpoint, well, the 1. It is more secure. The reason you need to whitelist in the first place is for security. 2. It is way faster, in some cases up to 5 seconds faster. -3. I don't trust firebase(or anyone) with my user's private data, and you shouldn't either. +3. I don't trust firebase (or anyone) with my user's private data, and you shouldn't either. -Yes I know that the third one sounds exaggerated, especially when we rely on them anyways. But their endpoint works on the client(It's JS) and you shouldn't trust the client. +Yes, I know that the third one sounds exaggerated, especially when we rely on them anyways. But their endpoint works on the client (It's JS) and you shouldn't trust the client. ## How to install @@ -107,7 +107,7 @@ If the data is correct and matches an existing user, the user will be signed in. ### Authenticate with Federated Identity Provider. -When signing in with an IdP, the user will be redirected to their page, and later redirected back into our app. Because of this, wee need to tell the provider where to redirect to by using the `redirectUri` property. It needs to be a page that will finish the sign in flow by running a method(read below how). +When signing in with an IdP, the user will be redirected to their page, and later redirected back into our app. Because of this, we need to tell the provider where to redirect to by using the `redirectUri` property. It needs to be a page that will finish the sign in flow by running a method (read below how). Please make sure the provider is correctly set up in the Firebase console. @@ -138,7 +138,7 @@ In that URL we need to finish the auth flow. We do that very easily by running a auth.handleSignInRedirect(); ``` -Thats it. After this the user should be signed in. +That's it. After this the user should be signed in. ### Authenticate anonymously. @@ -167,7 +167,7 @@ const removeListener = auth.listen(user => { removeListener(); // The callback will no longer be called on updates. ``` -Now every time the user state or data is changed, the callback will be called with tne new data. +Now every time the user state or data is changed, the callback will be called with the new data. The `listen()` method returns a function that can be called when we wish to stop listening for updates. # Full API Reference From 4b488c15867ad680cf75670d527e967612597c93 Mon Sep 17 00:00:00 2001 From: Daniyel Date: Sat, 4 Jul 2020 22:15:47 +0300 Subject: [PATCH 2/8] Fix inconsistencies in the comments and some spaces. --- src/main.js | 24 ++++++++++++------------ test/main.test.js | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main.js b/src/main.js index ef6ba4b..614b022 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ */ /** - * Settings object for an IDP(Identity Provider). + * Settings object for an IDP (Identity Provider). * @typedef {Object} ProviderOptions * @property {string} options.name The name of the provider in lowercase. * @property {string} [options.scope] The scopes for the IDP, this is optional and defaults to "openid email". @@ -74,7 +74,7 @@ export default class Auth { } /** - * Emits an event and triggers all of the listeners. + * Emits an event and triggers all the listeners. * @param {string} name The name of the event to trigger. * @param {any} data The data you want to pass to the event listeners. * @private @@ -86,7 +86,7 @@ export default class Auth { /** * Set up a function that will be called whenever the user state is changed. * @param {function} cb The function to call when the event is triggered. - * @returns {function} function that will unsubscribe your callback from being called. + * @returns {function} Function that will unsubscribe your callback from being called. */ listen(cb) { this.listeners.push(cb); @@ -104,7 +104,7 @@ export default class Auth { } /** - * Make post request to a specific endpoint, and return the response. + * Make post request to a specific endpoint and return the response. * @param {string} endpoint The name of the endpoint. * @param {any} request Body to pass to the request. * @private @@ -122,7 +122,7 @@ export default class Auth { let data = await response.json(); // If the response returned an error, try to get a Firebase error code/message. - // Sometimes the error codes are joined with an explanation, we don't need that(its a bug). + // Sometimes the error codes are joined with an explanation, we don't need that (its a bug). // So we remove the unnecessary part. if (!response.ok) { const code = data.error.message.replace(/: [\w ,.'"()]+$/, ''); @@ -138,7 +138,7 @@ export default class Auth { /** * Makes sure the user is logged in and has up-to-date credentials. - * @throws Will throw if the user is not logged in. + * @throws Will throw if the user is not signed in. * @private */ async enforceAuth() { @@ -148,7 +148,7 @@ export default class Auth { /** * Updates the user data in the localStorage. - * @param {Object} userData the new user data. + * @param {Object} userData The new user data. * @param {boolean} [updateStorage = true] Whether to update local storage or not. * @private */ @@ -201,9 +201,9 @@ export default class Auth { } /** - * Uses native fetch, but adds authorization headers otherwise the API is exactly the same as native fetch. - * @param {Request|Object|string} resource the resource to send the request to, or an options object. - * @param {Object} init an options object. + * Uses native fetch but adds authorization headers, otherwise the API is exactly the same as native fetch. + * @param {Request|Object|string} resource The resource to send the request to, or an options object. + * @param {Object} init An options object. */ async authorizedRequest(resource, init) { const request = resource instanceof Request ? resource : new Request(resource, init); @@ -358,7 +358,7 @@ export default class Auth { /** * Sends an out-of-band confirmation code for an account. * Can be used to reset a password, to verify an email address and send a Sign-in email link. - * The `email` argument is not needed only when verifying an email(In that case it will be completely ignored, even if specified), otherwise it is required. + * The `email` argument is not needed only when verifying an email (In that case it will be completely ignored, even if specified), otherwise it is required. * @param {'PASSWORD_RESET'|'VERIFY_EMAIL'|'EMAIL_SIGNIN'} requestType The type of out-of-band (OOB) code to send. * @param {string} [email] When the `requestType` is `PASSWORD_RESET` or `EMAIL_SIGNIN` you need to provide an email address. * @returns {Promise} @@ -400,7 +400,7 @@ export default class Auth { } /** - * Gets the user data from the server, and updates the local caches. + * Gets the user data from the server and updates the local caches. * @param {Object} [tokenManager] Only when not logged in. * @throws Will throw if the user is not signed in. */ diff --git a/test/main.test.js b/test/main.test.js index 14195fe..0c072b8 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -114,7 +114,7 @@ describe('Auth', () => { email: 'test@example.com', tokenManager: { idToken: 'idTokenString', - expiresAt: Date.now() - 3600 * 1000 // one hour ago. + expiresAt: Date.now() - 3600 * 1000 // One hour ago. } }) ); @@ -122,7 +122,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key' }); // Await for the second update to happen. - // the first one is from local storage. + // The first one is from local storage. await new Promise(resolve => { auth.listen(resolve); }); @@ -382,7 +382,7 @@ describe('Auth', () => { test("Doesn't make any requests when the user is not logged in", async () => { // The constructor makes some requests. - // We have to mock them for this not to throw + // We must mock them, to prevent a throw fetch.mockResponse('{}'); try { From 2e69674cb9b1d579c23ff8669291bd6b5552f95c Mon Sep 17 00:00:00 2001 From: Daniyel Date: Sun, 5 Jul 2020 22:20:04 +0300 Subject: [PATCH 3/8] reverts some mistakes. Fix some inconsistencies. --- src/main.js | 44 +++++++++++++++++++++---------------------- test/main.test.js | 48 +++++++++++++++++++++++------------------------ 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/main.js b/src/main.js index 614b022..90986c2 100644 --- a/src/main.js +++ b/src/main.js @@ -4,10 +4,10 @@ */ /** - * Settings object for an IDP (Identity Provider). + * Settings object for an IdP (Identity Provider). * @typedef {Object} ProviderOptions * @property {string} options.name The name of the provider in lowercase. - * @property {string} [options.scope] The scopes for the IDP, this is optional and defaults to "openid email". + * @property {string} [options.scope] The scopes for the IdP, this is optional and defaults to "openid email". */ /** @@ -66,8 +66,8 @@ export default class Auth { // we need to first check if this environment supports `addEventListener` on the window. 'addEventListener' in window && window.addEventListener('storage', e => { - // This code will run if localStorage for this user - // data was updated from a different browser window. + // This code will run if the local storage for this user + // was updated from a different browser window. if (e.key !== this.sKey('User')) return; this.setState(JSON.parse(e.newValue), false); }); @@ -84,7 +84,7 @@ export default class Auth { } /** - * Set up a function that will be called whenever the user state is changed. + * Sets up a function that will be called whenever the user state is changed. * @param {function} cb The function to call when the event is triggered. * @returns {function} Function that will unsubscribe your callback from being called. */ @@ -104,7 +104,7 @@ export default class Auth { } /** - * Make post request to a specific endpoint and return the response. + * Makes post request to a specific endpoint and return the response. * @param {string} endpoint The name of the endpoint. * @param {any} request Body to pass to the request. * @private @@ -122,7 +122,7 @@ export default class Auth { let data = await response.json(); // If the response returned an error, try to get a Firebase error code/message. - // Sometimes the error codes are joined with an explanation, we don't need that (its a bug). + // Sometimes the error codes are joined with an explanation, we don't need that. // So we remove the unnecessary part. if (!response.ok) { const code = data.error.message.replace(/: [\w ,.'"()]+$/, ''); @@ -137,19 +137,19 @@ export default class Auth { } /** - * Makes sure the user is logged in and has up-to-date credentials. - * @throws Will throw if the user is not signed in. + * Makes sure the user is signed-in and has up-to-date credentials. + * @throws Will throw if the user is not signed-in. * @private */ async enforceAuth() { - if (!this.user) throw Error('The user must be logged-in to use this method.'); + if (!this.user) throw Error('The user must be signed-in to use this method.'); return this.refreshIdToken(); // Won't do anything if the token is valid. } /** - * Updates the user data in the localStorage. + * Updates the user data in the local storage. * @param {Object} userData The new user data. - * @param {boolean} [updateStorage = true] Whether to update local storage or not. + * @param {boolean} [persist = true] Whether to update local storage or not. * @private */ async setState(userData, persist = true, emit = true) { @@ -159,7 +159,7 @@ export default class Auth { } /** - * Sign out the currently signed in user. + * Sign-out the currently signed-in user. * Removes all data stored in the storage that's associated with the user. */ signOut() { @@ -244,7 +244,7 @@ export default class Auth { const { provider, oauthScope, context, linkAccount } = typeof options === 'string' ? { provider: options } : options; - // Make sure the user is logged in when an "account link" was requested. + // Makes sure the user is signed-in when an "account link" was requested. if (linkAccount) await this.enforceAuth(); // Get the url and other data necessary for the authentication. @@ -260,7 +260,7 @@ export default class Auth { // Is required to finish the auth flow, I believe this is used to mitigate CSRF attacks. // (No docs on this...) await this.storage.set(this.sKey('SessionId'), sessionId); - // Save if this is a fresh log-in or a "link account" request. + // Save if this is a fresh signed-in or a "link account" request. linkAccount && (await this.storage.set(this.sKey('LinkAccount'), true)); // Finally - redirect the page to the auth endpoint. @@ -278,14 +278,14 @@ export default class Auth { const sessionId = await this.storage.get(this.sKey('SessionId')); // Get the indication if this was a "link account" request. const linkAccount = await this.storage.get(this.sKey('LinkAccount')); - // Check for the edge case in which the user signed out before completing the linkAccount + // Check for the edge case in which the user signed-out before completing the linkAccount // Request. if (linkAccount && !this.user) throw Error('Request to "Link account" was made, but user is no longer signed-in'); await this.storage.remove(this.sKey('LinkAccount')); // Try to exchange the Auth Code for an idToken and refreshToken. const { idToken, refreshToken, expiresAt, context } = await this.api('signInWithIdp', { - // If this is a "link account" flow, then attach the idToken of the currently logged in account. + // If this is a "link account" flow, then attach the idToken of the currently signed-in account. idToken: linkAccount ? this.user.tokenManager.idToken : undefined, requestUri, sessionId, @@ -401,8 +401,8 @@ export default class Auth { /** * Gets the user data from the server and updates the local caches. - * @param {Object} [tokenManager] Only when not logged in. - * @throws Will throw if the user is not signed in. + * @param {Object} [tokenManager] Only when not signed-in. + * @throws Will throw if the user is not signed-in. */ async fetchProfile(tokenManager = this.user && this.user.tokenManager) { if (!tokenManager) await this.enforceAuth(); @@ -418,7 +418,7 @@ export default class Auth { /** * Update user's profile. * @param {Object} newData An object with the new data to overwrite. - * @throws Will throw if the user is not signed in. + * @throws Will throw if the user is not signed-in. */ async updateProfile(newData) { await this.enforceAuth(); @@ -446,8 +446,8 @@ export default class Auth { } /** - * Deletes the currently logged in account and logs out. - * @throws Will throw if the user is not signed in. + * Deletes the currently signed-in account and logs out. + * @throws Will throw if the user is not signed-in. */ async deleteAccount() { await this.enforceAuth(); diff --git a/test/main.test.js b/test/main.test.js index 0c072b8..8a7833b 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -68,7 +68,7 @@ describe('Auth', () => { }); describe('Initializes the "user" property', () => { - test('Reads the username from storage when already logged in', async () => { + test('Reads the username from storage when already signed-in', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse(`{ "users": [${JSON.stringify(mockUserData)}] }`); @@ -84,7 +84,7 @@ describe('Auth', () => { expect(auth.user).toEqual(mockUserData); }); - test('Updates the stored data if the user is logged in', async () => { + test('Updates the stored data if the user is signed-in', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{"users": [{ "username": "updated!" }]}'); @@ -105,7 +105,7 @@ describe('Auth', () => { expect(userData).toEqual(auth.user); }); - test('Refreshed the token when logged in and token has expired', async () => { + test('Refreshed the token when signed-in and token has expired', async () => { fetch.mockResponses('{ "id_token": "123", "refresh_token": "456" }', '{"users": [{ "updated": true }]}'); localStorage.setItem( @@ -139,7 +139,7 @@ describe('Auth', () => { }); }); - test('Calls the listeners when the user is not logged in', async () => { + test('Calls the listeners when the user is not signed-in', async () => { const auth = new Auth({ apiKey: 'key' }); const userData = await new Promise(resolve => { @@ -150,7 +150,7 @@ describe('Auth', () => { expect(auth.user).toEqual(null); }); - test("Doesn't make any requests when the user is not logged in", async () => { + test("Doesn't make any requests when the user is not signed-in", async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -166,10 +166,10 @@ describe('Auth', () => { expect(fetch.mock.calls.length).toEqual(0); }); - test('Signs out a previously signed in user when their token has expired', async () => { + test('Signs-out a previously signed-in user when their token has expired', async () => { fetch.mockResponse('{"error": {"message": "TOKEN_EXPIRED"}}', { status: 403 }); - // Previously signed in user + // Previously signed-in user localStorage.setItem( 'Auth:User:key:default', JSON.stringify({ @@ -186,7 +186,7 @@ describe('Auth', () => { // First call to the listener will be done after reading the local storage. // After that a request to update the user data will be made, and it will return // the fetch error "TOKEN_EXPIRED", the constructor will catch that, then - // sign out the user and call the listener a second time. + // sign-out the user and call the listener a second time. let calls = 0; const userData = await new Promise(resolve => { auth.listen(user => { @@ -199,10 +199,10 @@ describe('Auth', () => { expect(userData).toBe(null); }); - test('Signs out a previously signed in user when their token is invalid', async () => { + test('Signs-out a previously signed-in user when their token is invalid', async () => { fetch.mockResponse('{"error": {"message": "INVALID_ID_TOKEN"}}', { status: 403 }); - // Previously signed in user + // Previously signed-in user localStorage.setItem( 'Auth:User:key:default', JSON.stringify({ @@ -219,7 +219,7 @@ describe('Auth', () => { // First call to the listener will be done after reading the local storage. // After that a request to update the user data will be made, and it will return // the fetch error "INVALID_ID_TOKEN", the constructor will catch that, then - // sign out the user and call the listener a second time. + // sign-out the user and call the listener a second time. let calls = 0; const userData = await new Promise(resolve => { auth.listen(user => { @@ -375,12 +375,12 @@ describe('Auth', () => { }); describe('enforceAuth()', () => { - test('Throws when the user is not logged in', async () => { + test('Throws when the user is not signed-in', async () => { const auth = new Auth({ apiKey: 'key' }); - await expect(auth.enforceAuth()).rejects.toThrow('The user must be logged-in to use this method.'); + await expect(auth.enforceAuth()).rejects.toThrow('The user must be signed-in to use this method.'); }); - test("Doesn't make any requests when the user is not logged in", async () => { + test("Doesn't make any requests when the user is not signed-in", async () => { // The constructor makes some requests. // We must mock them, to prevent a throw fetch.mockResponse('{}'); @@ -482,7 +482,7 @@ describe('Auth', () => { fetch.mockResponse('{"users": [{ "updated": true }]}'); const auth = new Auth({ apiKey: 'key' }); - // Mock logged in user. + // Mock signed-in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -509,7 +509,7 @@ describe('Auth', () => { }); const auth = new Auth({ apiKey: 'key' }); - // Mock logged in user. + // Mock signed-in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -534,7 +534,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key' }); - // Mock logged in user. + // Mock signed-in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -559,7 +559,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key' }); await mockLoggedIn(auth); - // Mock logged in user. + // Mock signed-in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -577,7 +577,7 @@ describe('Auth', () => { }); describe('AuthorizedRequest()', () => { - test('Adds Authorization headers when the user is logged in.', async () => { + test('Adds Authorization headers when the user is signed-in.', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -593,7 +593,7 @@ describe('Auth', () => { expect(headers.get('Authorization')).toEqual('Bearer idTokenString'); }); - test("Doesn't change the request when the user is not logged in", async () => { + test("Doesn't change the request when the user is not signed-in", async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -645,7 +645,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key', redirectUri: 'redirectHere' }); await expect(auth.signInWithProvider({ provider: 'google.com', linkAccount: true })).rejects.toThrow( - 'The user must be logged-in to use this method.' + 'The user must be signed-in to use this method.' ); }); @@ -764,7 +764,7 @@ describe('Auth', () => { }); describe('senbOobCode()', () => { - test('Throws when request type is "verify email" but not logged in', async () => { + test('Throws when request type is "verify email" but not signed-in', async () => { const auth = new Auth({ apiKey: 'key' }); await expect(auth.sendOobCode('VERIFY_EMAIL')).rejects.toThrow(); }); @@ -909,9 +909,9 @@ describe('Auth', () => { }); describe('fetchProfile()', () => { - test('Throws when the user is not logged in', async () => { + test('Throws when the user is not signed-in', async () => { const auth = new Auth({ apiKey: 'key' }); - await expect(auth.fetchProfile()).rejects.toThrow('The user must be logged-in to use this method.'); + await expect(auth.fetchProfile()).rejects.toThrow('The user must be signed-in to use this method.'); }); test('Makes correct request', async () => { From 61723f7c3f4d8fb67979bdf8884bfc99f37e95d3 Mon Sep 17 00:00:00 2001 From: Daniyel Date: Tue, 7 Jul 2020 22:44:18 +0300 Subject: [PATCH 4/8] Fix typos --- README.md | 26 ++++++++++++------------ src/main.js | 45 +++++++++++++++++++++--------------------- test/main.test.js | 50 +++++++++++++++++++++++------------------------ 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 0fa87c0..20a495a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Firebase auth lite (Beta) [![codecov](https://codecov.io/gh/samuelgozi/firebase-auth-lite/branch/master/graph/badge.svg)](https://codecov.io/gh/samuelgozi/firebase-auth-lite) ![bundlephobia](https://badgen.net/bundlephobia/minzip/firebase-auth-lite) -A performance focused alternative to the official firebase auth library that is designed to work with my other alternatives for [storage](https://github.com/samuelgozi/firebase-storage-lite) and [firestore](https://github.com/samuelgozi/firebase-firestore-lite). +A performance focused alternative to the official Firebase authentication library that is designed to work with my other alternatives for [storage](https://github.com/samuelgozi/firebase-storage-lite) and [firestore](https://github.com/samuelgozi/firebase-firestore-lite). The goal of this library is to provide a performance focused alternative to the official SDKs. This comes with some costs. The big one is browser support, we only support modern browsers, but you can always run them through Babel. @@ -9,13 +9,13 @@ The goal of this library is to provide a performance focused alternative to the ## What else do I need to consider? The API is completely different. This is not a drop-in replacement, instead our API is much simpler and easier to use. -In addition you should consider the next points: +In addition, you should consider the next points: 1. This is still work in progress and the API will change without warning until version 1.0. 2. There is a small difference working with Federated Identity Providers. 3. Sessions can only be persisted in localStorage (More options will be added). -4. The code is written with modern JS and you are responsible for tranpiling it for your targets, but babelrc configuration is ready. The code also makes use of the Fetch API and local storage. -5. Not fully tested yet(I don't have a good testing strategy yet...) +4. The code is written with modern JS and you are responsible for transpiling it for your targets, but babelrc configuration is ready. The code also makes use of the Fetch API and localStorage. +5. Not fully tested yet (I don't have a good testing strategy yet...) ## Features and roadmap @@ -38,21 +38,21 @@ The roadmap and progress to 1.0 can be seen at [issue #2](https://github.com/sam ## Setting up Federated identity providers -You might have noticed that when adding a Oauth Sign-in methid in the firebase console, you are asked to add a URL that looks something like this to the Oauth's configurations: `https://[app-id].firebaseapp.com/__/auth/handler` +You might have noticed that when adding a OAuth sign in method in the Firebase console, you are asked to add a URL that looks something like this to the OAuth's configurations: `https://[app-id].firebaseapp.com/__/auth/handler` -What you are essentially doing is whitelisting that URL, which is a hidden URL that exists in every firebase app. When using this library, you will need to add the URL of **your app** instead of the firebase's one. You need to add the URL of the page in your app that will handle the log in. You'll see what I mean in the docs below. +What you are essentially doing is whitelisting that URL, which is a hidden URL that exists in every firebase app. When using this library, you will need to add the URL of **your app** instead of the Firebase's one. You need to add the URL of the page in your app that will handle the sign in. You'll see what I mean in the docs below. -You might be curious as to why I'm auoiding using firebases endpoint, well, the reasons are: +You might be curious as to why I'm avoiding using Firebases endpoint, well, the reasons are: -1. It is more secure. The reason you need to whitelist in the first place is for security. -2. It is way faster, in some cases up to 5 seconds faster. -3. I don't trust firebase (or anyone) with my user's private data, and you shouldn't either. +1. More secure, it's the reason you need to whitelist in the first place is for security. +2. Way faster, in some cases up to 5 seconds faster. +3. I don't trust Firebase (or anyone) with my user's private data, and you shouldn't either. Yes, I know that the third one sounds exaggerated, especially when we rely on them anyways. But their endpoint works on the client (It's JS) and you shouldn't trust the client. ## How to install -Once again i will say that its all still work in progress. Some things might break, and the API might change. +Once again, it's all still work in progress. Some things might break, and the API might change. However, I do encourage anyone to try it. I need feedback in order to improve it, so please use it and don't hesitate to leave feedback! ``` @@ -89,7 +89,7 @@ const auth = new Auth({ }); ``` -Then to sign-up use the `signUp` method. +Then to sign up use the `signUp` method. Please note that after a sign up, the user will be signed in automatically. ```js @@ -97,7 +97,7 @@ Please note that after a sign up, the user will be signed in automatically. auth.signUp('email', 'password'); ``` -In order to sign-in, pass the email and password to the `signInWithPassword` method. +In order to sign in, pass the email and password to the `signInWithPassword` method. ```js auth.signIn('email', 'password'); diff --git a/src/main.js b/src/main.js index 9dbe2b7..a808dda 100644 --- a/src/main.js +++ b/src/main.js @@ -13,10 +13,10 @@ /** * Object response from a "fetchProvidersForEmail" request. * @typedef {Object} ProvidersForEmailResponse - * @property {Array.} allProviders All providers the user has once used to do federated sign-in. - * @property {boolean} registered All sign-in methods this user has used. + * @property {Array.} allProviders All providers the user has once used to do federated sign in. + * @property {boolean} registered All sign in methods this user has used. * @property {string} sessionId Session ID which should be passed in the following verifyAssertion request. - * @property {Array.} signinMethods All sign-in methods this user has used. + * @property {Array.} signinMethods All sign in methods this user has used. */ /** @@ -138,12 +138,12 @@ export default class Auth { } /** - * Makes sure the user is signed-in and has up-to-date credentials. - * @throws Will throw if the user is not signed-in. + * Makes sure the user is signed in and has up-to-date credentials. + * @throws Will throw if the user is not signed in. * @private */ async enforceAuth() { - if (!this.user) throw Error('The user must be signed-in to use this method.'); + if (!this.user) throw Error('The user must be signed in to use this method.'); return this.refreshIdToken(); // Won't do anything if the token is valid. } @@ -160,7 +160,7 @@ export default class Auth { } /** - * Sign out the currently signed-in user. + * Sign out the currently signed in user. * Removes all data stored in the storage that's associated with the user. */ signOut() { @@ -232,7 +232,7 @@ export default class Auth { /** * Starts the auth flow of a federated ID provider. - * Also, it will redirect the page to the federated sign-in page. + * Also, it will redirect the page to the federated sign in page. * @param {oauthFlowOptions|string} options An options object or a string with the name of the provider. */ async signInWithProvider(options) { @@ -246,7 +246,7 @@ export default class Auth { typeof options === 'string' ? { provider: options } : options; linkAccount && (await this.enforceAuth()); - // Makes sure the user is signed-in when an "account link" was requested. + // Makes sure the user is signed in when an "account link" was requested. // Get the url and other data necessary for the authentication. const { authUri, sessionId } = await this.api('createAuthUri', { @@ -261,7 +261,7 @@ export default class Auth { // Is required to finish the auth flow, I believe this is used to mitigate CSRF attacks. // (No docs on this...) await this.storage.set(this.sKey('SessionId'), sessionId); - // Save if this is a fresh signed-in or a "link account" request. + // Save if this is a fresh signed in or a "link account" request. linkAccount && (await this.storage.set(this.sKey('LinkAccount'), true)); // Finally - redirect the page to the auth endpoint. @@ -279,15 +279,15 @@ export default class Auth { const sessionId = await this.storage.get(this.sKey('SessionId')); // Get the indication if this was a "link account" request. const linkAccount = await this.storage.get(this.sKey('LinkAccount')); - // Check for the edge case in which the user signed-out + // Check for the edge case in which the user signed out // before completing the linkAccount request. - if (linkAccount && !this.user) throw Error('Request to "Link account" was made, but user is no longer signed-in'); + if (linkAccount && !this.user) throw Error('Request to "Link account" was made, but user is no longer signed in'); await this.storage.remove(this.sKey('LinkAccount')); // Try to exchange the Auth Code for an idToken and refreshToken. const { idToken, refreshToken, expiresAt, context } = await this.api('signInWithIdp', { - // If this is a "link account" flow, then attach the idToken of the currently signed-in account. + // If this is a "link account" flow, then attach the idToken of the currently signed in account. idToken: linkAccount ? this.user.tokenManager.idToken : undefined, requestUri, sessionId, @@ -304,14 +304,14 @@ export default class Auth { } /** - * Handles all sign-in flows that complete via redirects. + * Handles all sign in flows that complete via redirects. * Fails silently if no redirect was detected. */ async handleSignInRedirect() { // OAuth Federated Identity Provider flow. if (location.href.match(/[&?]code=/)) return this.finishProviderSignIn(); - // Email sign-in flow. + // Email sign in flow. if (location.href.match(/[&?]oobCode=/)) { const oobCode = location.href.match(/[?&]oobCode=([^&]+)/)[1]; const email = location.href.match(/[?&]email=([^&]+)/)[1]; @@ -361,9 +361,8 @@ export default class Auth { /** * Sends an out-of-band confirmation code for an account. - * It can be used to reset a password, to verify an email address and send a sign-in email link. - * The `email` argument is not needed only when verifying an email (In that case it will be completely ignored, even if specified), otherwise it is required. - * Can be used to reset a password, to verify an email address and send a Sign-in email link. + * Can be used to reset a password, to verify an email address and send a Sign in email link. + * The email argument is not needed when verifying an email (it's ignored). Otherwise, it's required. * @param {'PASSWORD_RESET'|'VERIFY_EMAIL'|'EMAIL_SIGNIN'} requestType The type of out-of-band (OOB) code to send. * @param {string} [email] When the `requestType` is `PASSWORD_RESET` or `EMAIL_SIGNIN` you need to provide an email address. * @returns {Promise} @@ -405,8 +404,8 @@ export default class Auth { } /** - * @param {Object} [tokenManager] Only when not signed-in. - * @throws Will throw if the user is not signed-in. + * @param {Object} [tokenManager] Only when not signed in. + * @throws Will throw if the user is not signed in. * Gets the user data from the server and updates the local caches. */ async fetchProfile(tokenManager = this.user && this.user.tokenManager) { @@ -422,7 +421,7 @@ export default class Auth { /** * @param {Object} newData An object with the new data. - * @throws Will throw if the user is not signed-in. + * @throws Will throw if the user is not signed in. * Update user's profile. */ async updateProfile(newData) { @@ -451,8 +450,8 @@ export default class Auth { } /** - * Deletes the currently signed-in account then sign out. - * @throws Will throw if the user is not signed-in. + * Deletes the currently signed in account then sign out. + * @throws Will throw if the user is not signed in. */ async deleteAccount() { await this.enforceAuth(); diff --git a/test/main.test.js b/test/main.test.js index e42e070..328a561 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -68,7 +68,7 @@ describe('Auth', () => { }); describe('Initializes the "user" property', () => { - test('Reads the username from storage when already signed-in', async () => { + test('Reads the username from storage when already signed in', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse(`{ "users": [${JSON.stringify(mockUserData)}] }`); @@ -84,7 +84,7 @@ describe('Auth', () => { expect(auth.user).toEqual(mockUserData); }); - test('Updates the stored data if the user is signed-in', async () => { + test('Updates the stored data if the user is signed in', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{"users": [{ "username": "updated!" }]}'); @@ -105,7 +105,7 @@ describe('Auth', () => { expect(userData).toEqual(auth.user); }); - test('Refreshed the token when signed-in and token has expired', async () => { + test('Refreshed the token when signed in and token has expired', async () => { fetch.mockResponses('{ "id_token": "123", "refresh_token": "456" }', '{"users": [{ "updated": true }]}'); localStorage.setItem( @@ -139,7 +139,7 @@ describe('Auth', () => { }); }); - test('Calls the listeners when the user is not signed-in', async () => { + test('Calls the listeners when the user is not signed in', async () => { const auth = new Auth({ apiKey: 'key' }); const userData = await new Promise(resolve => { @@ -150,7 +150,7 @@ describe('Auth', () => { expect(auth.user).toEqual(null); }); - test("Doesn't make any requests when the user is not signed-in", async () => { + test("Doesn't make any requests when the user is not signed in", async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -166,10 +166,10 @@ describe('Auth', () => { expect(fetch.mock.calls.length).toEqual(0); }); - test('Signs-out a previously signed-in user when their token has expired', async () => { + test('Signs out a previously signed in user when their token has expired', async () => { fetch.mockResponse('{"error": {"message": "TOKEN_EXPIRED"}}', { status: 403 }); - // Previously signed-in user + // Previously signed in user localStorage.setItem( 'Auth:User:key:default', JSON.stringify({ @@ -186,7 +186,7 @@ describe('Auth', () => { // First call to the listener will be done after reading the local storage. // After that a request to update the user data will be made, and it will return // the fetch error "TOKEN_EXPIRED", the constructor will catch that, then - // sign-out the user and call the listener a second time. + // sign out the user and call the listener a second time. let calls = 0; const userData = await new Promise(resolve => { auth.listen(user => { @@ -199,10 +199,10 @@ describe('Auth', () => { expect(userData).toBe(null); }); - test('Signs-out a previously signed-in user when their token is invalid', async () => { + test('Signs out a previously signed in user when their token is invalid', async () => { fetch.mockResponse('{"error": {"message": "INVALID_ID_TOKEN"}}', { status: 403 }); - // Previously signed-in user + // Previously signed in user localStorage.setItem( 'Auth:User:key:default', JSON.stringify({ @@ -219,7 +219,7 @@ describe('Auth', () => { // First call to the listener will be done after reading the local storage. // After that a request to update the user data will be made, and it will return // the fetch error "INVALID_ID_TOKEN", the constructor will catch that, then - // sign-out the user and call the listener a second time. + // sign out the user and call the listener a second time. let calls = 0; const userData = await new Promise(resolve => { auth.listen(user => { @@ -375,12 +375,12 @@ describe('Auth', () => { }); describe('enforceAuth()', () => { - test('Throws when the user is not signed-in', async () => { + test('Throws when the user is not signed in', async () => { const auth = new Auth({ apiKey: 'key' }); - await expect(auth.enforceAuth()).rejects.toThrow('The user must be signed-in to use this method.'); + await expect(auth.enforceAuth()).rejects.toThrow('The user must be signed in to use this method.'); }); - test("Doesn't make any requests when the user is not signed-in", async () => { + test("Doesn't make any requests when the user is not signed in", async () => { // The constructor makes some requests. // We must mock them, to prevent a throw fetch.mockResponse('{}'); @@ -482,7 +482,7 @@ describe('Auth', () => { fetch.mockResponse('{"users": [{ "updated": true }]}'); const auth = new Auth({ apiKey: 'key' }); - // Mock signed-in user. + // Mock signed in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -509,7 +509,7 @@ describe('Auth', () => { }); const auth = new Auth({ apiKey: 'key' }); - // Mock signed-in user. + // Mock signed in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -534,7 +534,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key' }); - // Mock signed-in user. + // Mock signed in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -559,7 +559,7 @@ describe('Auth', () => { const auth = new Auth({ apiKey: 'key' }); await mockLoggedIn(auth); - // Mock signed-in user. + // Mock signed in user. auth.user = { tokenManager: { idToken: 'idTokenString', @@ -577,7 +577,7 @@ describe('Auth', () => { }); describe('AuthorizedRequest()', () => { - test('Adds Authorization headers when the user is signed-in.', async () => { + test('Adds Authorization headers when the user is signed in.', async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -593,7 +593,7 @@ describe('Auth', () => { expect(headers.get('Authorization')).toEqual('Bearer idTokenString'); }); - test("Doesn't change the request when the user is not signed-in", async () => { + test("Doesn't change the request when the user is not signed in", async () => { // The constructor makes some requests. // We have to mock them for this not to throw fetch.mockResponse('{}'); @@ -641,11 +641,11 @@ describe('Auth', () => { ); }); - test('Enforces signed-in user when performing a "linkAccount"', async () => { + test('Enforces signed in user when performing a "linkAccount"', async () => { const auth = new Auth({ apiKey: 'key', redirectUri: 'redirectHere' }); await expect(auth.signInWithProvider({ provider: 'google.com', linkAccount: true })).rejects.toThrow( - 'The user must be signed-in to use this method.' + 'The user must be signed in to use this method.' ); }); @@ -764,7 +764,7 @@ describe('Auth', () => { }); describe('senbOobCode()', () => { - test('Throws when request type is "verify email" but not signed-in', async () => { + test('Throws when request type is "verify email" but not signed in', async () => { const auth = new Auth({ apiKey: 'key' }); await expect(auth.sendOobCode('VERIFY_EMAIL')).rejects.toThrow(); }); @@ -909,9 +909,9 @@ describe('Auth', () => { }); describe('fetchProfile()', () => { - test('Throws when the user is not signed-in', async () => { + test('Throws when the user is not signed in', async () => { const auth = new Auth({ apiKey: 'key' }); - await expect(auth.fetchProfile()).rejects.toThrow('The user must be signed-in to use this method.'); + await expect(auth.fetchProfile()).rejects.toThrow('The user must be signed in to use this method.'); }); test('Makes correct request', async () => { From 2ea23904d81939e5b0f1ed95ea2b4e0153d60345 Mon Sep 17 00:00:00 2001 From: Daniyel Date: Tue, 7 Jul 2020 22:51:25 +0300 Subject: [PATCH 5/8] fix: correcting some marge conflicts leftovers --- src/main.js | 2 +- test/main.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index a808dda..fe46e6b 100644 --- a/src/main.js +++ b/src/main.js @@ -105,8 +105,8 @@ export default class Auth { } /** - * @param {string} endpoint Name of the endpoint. * Makes post request to a specific endpoint and return the response. + * @param {string} endpoint Name of the endpoint. * @param {any} request Body to pass to the request. * @private */ diff --git a/test/main.test.js b/test/main.test.js index 328a561..ce4f695 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -382,7 +382,7 @@ describe('Auth', () => { test("Doesn't make any requests when the user is not signed in", async () => { // The constructor makes some requests. - // We must mock them, to prevent a throw + // We must mock them to prevent a throw fetch.mockResponse('{}'); try { From 0ee757ac57f52317cf168e23ebb7dd74f90b8de4 Mon Sep 17 00:00:00 2001 From: Daniyel Date: Wed, 8 Jul 2020 00:17:35 +0300 Subject: [PATCH 6/8] fix: undo mistakes --- README.md | 4 ++-- src/main.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 20a495a..5b1a4dc 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ What you are essentially doing is whitelisting that URL, which is a hidden URL t You might be curious as to why I'm avoiding using Firebases endpoint, well, the reasons are: -1. More secure, it's the reason you need to whitelist in the first place is for security. -2. Way faster, in some cases up to 5 seconds faster. +1. It's more secure, the reason you need to whitelist in the first place is for security. +2. It's way faster, in some cases up to 5 seconds faster. 3. I don't trust Firebase (or anyone) with my user's private data, and you shouldn't either. Yes, I know that the third one sounds exaggerated, especially when we rely on them anyways. But their endpoint works on the client (It's JS) and you shouldn't trust the client. diff --git a/src/main.js b/src/main.js index fe46e6b..554124e 100644 --- a/src/main.js +++ b/src/main.js @@ -66,7 +66,7 @@ export default class Auth { // we need to check if this environment supports `addEventListener` on the window. 'addEventListener' in window && window.addEventListener('storage', e => { - // This code will run if the local storage for this user + // This code will run if the localStorage for this user // was updated from a different browser window. if (e.key !== this.sKey('User')) return; this.setState(JSON.parse(e.newValue), false); @@ -105,7 +105,7 @@ export default class Auth { } /** - * Makes post request to a specific endpoint and return the response. + * Makes a post request to a specific endpoint and returns the response. * @param {string} endpoint Name of the endpoint. * @param {any} request Body to pass to the request. * @private @@ -123,7 +123,7 @@ export default class Auth { let data = await response.json(); // If the response returned an error, try to get a Firebase error code/message. - // Sometimes the error codes are joined with an explanation, we don't need that. + // Sometimes the error codes are joined with an explanation, we don't need that (it's a bug). // So we remove the unnecessary part. if (!response.ok) { const code = data.error.message.replace(/: [\w ,.'"()]+$/, ''); @@ -245,8 +245,8 @@ export default class Auth { const { provider, oauthScope, context, linkAccount } = typeof options === 'string' ? { provider: options } : options; - linkAccount && (await this.enforceAuth()); // Makes sure the user is signed in when an "account link" was requested. + linkAccount && (await this.enforceAuth()); // Get the url and other data necessary for the authentication. const { authUri, sessionId } = await this.api('createAuthUri', { @@ -261,7 +261,7 @@ export default class Auth { // Is required to finish the auth flow, I believe this is used to mitigate CSRF attacks. // (No docs on this...) await this.storage.set(this.sKey('SessionId'), sessionId); - // Save if this is a fresh signed in or a "link account" request. + // Save if this is a fresh sign in or a "link account" request. linkAccount && (await this.storage.set(this.sKey('LinkAccount'), true)); // Finally - redirect the page to the auth endpoint. @@ -361,7 +361,7 @@ export default class Auth { /** * Sends an out-of-band confirmation code for an account. - * Can be used to reset a password, to verify an email address and send a Sign in email link. + * Can be used to reset a password, to verify an email address and send a sign-in email link. * The email argument is not needed when verifying an email (it's ignored). Otherwise, it's required. * @param {'PASSWORD_RESET'|'VERIFY_EMAIL'|'EMAIL_SIGNIN'} requestType The type of out-of-band (OOB) code to send. * @param {string} [email] When the `requestType` is `PASSWORD_RESET` or `EMAIL_SIGNIN` you need to provide an email address. @@ -404,9 +404,9 @@ export default class Auth { } /** + * Gets the user data from the server and updates the local caches. * @param {Object} [tokenManager] Only when not signed in. * @throws Will throw if the user is not signed in. - * Gets the user data from the server and updates the local caches. */ async fetchProfile(tokenManager = this.user && this.user.tokenManager) { if (!tokenManager) await this.enforceAuth(); @@ -420,9 +420,9 @@ export default class Auth { } /** + * Update user's profile. * @param {Object} newData An object with the new data. * @throws Will throw if the user is not signed in. - * Update user's profile. */ async updateProfile(newData) { await this.enforceAuth(); From b37b38f9f61378710fa5724b74c54cca44a8535c Mon Sep 17 00:00:00 2001 From: Daniyel Date: Wed, 8 Jul 2020 10:15:09 +0300 Subject: [PATCH 7/8] remove duplication and fix inconsistencies --- src/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.js b/src/main.js index 554124e..3bd404d 100644 --- a/src/main.js +++ b/src/main.js @@ -75,7 +75,6 @@ export default class Auth { /** * Emits an event and triggers all the listeners. - * Emits an event and triggers all of the listeners. * @param {string} name Name of the event to trigger. * @param {any} data Data you want to pass to the event listeners. * @private @@ -150,7 +149,7 @@ export default class Auth { /** * Updates the user data in localStorage. * @param {Object} userData New user data. - * @param {boolean} [persist = true] Whether to update local storage or not. + * @param {boolean} [persist = true] Whether to update localStorage or not. * @private */ async setState(userData, persist = true, emit = true) { @@ -257,7 +256,7 @@ export default class Auth { context }); - // Save the sessionId that we just received in the local storage. + // Save the sessionId that we just received in the localStorage. // Is required to finish the auth flow, I believe this is used to mitigate CSRF attacks. // (No docs on this...) await this.storage.set(this.sKey('SessionId'), sessionId); From 86991a59d61e62f433284f7967f3959c1dab546a Mon Sep 17 00:00:00 2001 From: Daniyel Date: Tue, 28 Jul 2020 08:56:13 +0300 Subject: [PATCH 8/8] fix: resolve a change request --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b1a4dc..dd680fd 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ What you are essentially doing is whitelisting that URL, which is a hidden URL t You might be curious as to why I'm avoiding using Firebases endpoint, well, the reasons are: -1. It's more secure, the reason you need to whitelist in the first place is for security. +1. It’s more secure. That’s the reason you need to whitelist in the first place, is for security 2. It's way faster, in some cases up to 5 seconds faster. 3. I don't trust Firebase (or anyone) with my user's private data, and you shouldn't either.