Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix merge conflict and some additional typos #43

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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

Expand All @@ -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. 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.

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!

```
Expand Down Expand Up @@ -89,15 +89,15 @@ 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
// Pass a new email and password.
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');
Expand Down
63 changes: 31 additions & 32 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
/**
* Object response from a "fetchProvidersForEmail" request.
* @typedef {Object} ProvidersForEmailResponse
* @property {Array.<string>} 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.<string>} 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.<string>} signinMethods All sign-in methods this user has used.
* @property {Array.<string>} signinMethods All sign in methods this user has used.
*/

/**
Expand Down Expand Up @@ -66,15 +66,15 @@ 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 localStorage for this user
// data was updated from a different browser window.
// 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);
});
}

/**
* Emits an event and triggers all of the listeners.
* Emits an event and triggers all the listeners.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the other one (line78), thanks.

* @param {string} name Name of the event to trigger.
* @param {any} data Data you want to pass to the event listeners.
* @private
Expand Down Expand Up @@ -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 (it's a bug).
// So we remove the unnecessary part.
if (!response.ok) {
const code = data.error.message.replace(/ ?: [\w ,.'"()]+$/, '');
Expand All @@ -137,19 +137,19 @@ 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.
}

/**
* Updates the user data in localStorage.
* @param {Object} userData New user data.
* @param {boolean} [updateStorage = true] Check whether to update localStorage or not.
* @param {boolean} [persist = true] Whether to update localStorage or not.
* @private
*/
async setState(userData, persist = true, emit = true) {
Expand All @@ -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() {
Expand Down Expand Up @@ -199,7 +199,7 @@ export default class Auth {
}

/**
* Uses native fetch, but adds authorization headers, otherwise, the API is exactly the same as native fetch.
* Uses native fetch but adds authorization headers, otherwise the API is exactly the same as native fetch.
* @param {Request|Object|string} resource A request to send. It can be a resource or an options object.
* @param {Object} init An options object.
*/
Expand Down Expand Up @@ -231,7 +231,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) {
Expand All @@ -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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn’t need to move this comment

// 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.
Expand All @@ -256,11 +256,11 @@ 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);
// Save if this is a fresh log-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.
Expand All @@ -278,16 +278,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');
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing error messages should be done separately or with a notice as some users might depend on the exact string.
Since this is a beta I’ll approve it but it’s can usually be considered a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important to know, I didn't know. Thanks.


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,
Expand All @@ -304,14 +303,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];
Expand Down Expand Up @@ -361,8 +360,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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign-in is used as a noun here, so the hyphen should remain.

* The email argument is not needed if verifying an email (the argument is ignored). Otherwise, it is required.
* 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}
Expand Down Expand Up @@ -404,9 +403,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.
* @param {Object} [tokenManager] Only when not signed in.
* @throws Will throw if the user is not signed in.
DaniyelMe marked this conversation as resolved.
Show resolved Hide resolved
*/
async fetchProfile(tokenManager = this.user && this.user.tokenManager) {
if (!tokenManager) await this.enforceAuth();
Expand All @@ -420,9 +419,9 @@ export default class Auth {
}

/**
* Updates the user's profile.
* Update user's profile.
* @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.
*/
async updateProfile(newData) {
await this.enforceAuth();
Expand Down Expand Up @@ -450,8 +449,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();
Expand Down
Loading