Skip to content

Instantly share code, notes, and snippets.

@pixelastic
Created July 2, 2018 10:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pixelastic/b0320db42350e3a8fdc42e9c9fb7d753 to your computer and use it in GitHub Desktop.
Save pixelastic/b0320db42350e3a8fdc42e9c9fb7d753 to your computer and use it in GitHub Desktop.
Firebase function to return an Auth0 custom token
import cors from 'cors';
import Promise from 'bluebird';
import axios from 'axios';
import database from '../database/database.js';
/**
* The goal of this function is to mint custom token that can be used to
* authenticate to Firebase.
* The function will be called through https, and will check the credentials of
* the requested user (through the usage of its Auth0 token). If the Auth0 token
* checks out, a new Firebase token will be mint and returned
**/
const module = {
/**
* Main entry point for the Firebase function. Will wrap the call in the
* required CORS options to make it accessible through cross origin calls.
* Will resolve to the customToken needed to authenticate to Firebase as an
* admin
*
* @param {Request} req Request sent to the endpoint
* @param {Response} res Response returned by the call
* @returns {void}
**/
onRequest(req, res) {
module.req = req;
module.res = res;
const wrapCorsRequest = cors({ origin: true });
wrapCorsRequest(req, res, module.resolveToCustomToken);
},
/**
* Will set the response to an object containing the custom token. This is
* were the main logic flow lies.
*
* @returns {void}
**/
resolveToCustomToken() {
const originToken = module.getOriginUserToken();
const originEmail = module.getOriginUserEmail();
return module
.getAuth0User(originToken)
.then(userData =>
module.assertDataIsFromOriginUser(userData, originEmail)
)
.then(() => module.mintCustomToken(originEmail))
.then(customToken => {
module.res.send({ token: customToken });
});
},
/**
* Get all user metadata from Auth0, based on the token specified.
*
* @param {String} accessToken The Auth0 access token of the user
* @returns {Promise} The metadata associated with that user
**/
getAuth0User(accessToken) {
const url = 'https://algolia.auth0.com/userinfo';
const headers = { Authorization: `Bearer ${accessToken}` };
return axios({
url,
headers,
}).then(response => response.data);
},
/**
* Check that the data we got from Auth0 is matching the email we specified
*
* @param {Object} userData Data coming from Auth0
* @param {String} originEmail Email specified in the request
* @returns {Promise} Resolves if they match, rejects otherwise
**/
assertDataIsFromOriginUser(userData, originEmail) {
// The token is not valid for this user
if (userData.email !== originEmail) {
return Promise.reject(
new Error('Specified token does not match specified email')
);
}
return Promise.resolve(true);
},
/**
* Mint a new admin token for the specified userName and resolve to it
*
* @param {String} userName Name of the token owner
* @returns {Promise.<String>} Token to authenticate to Firebase as an admin
**/
mintCustomToken(userName) {
return database.init().then(() => database.generateAuthToken(userName));
},
/**
* Return the email sent in the request body
*
* @return {String} The email sent in the body
**/
getOriginUserEmail() {
return module.req.body.email;
},
/**
* Return the token sent in the request body
*
* @return {String} The token sent in the body
**/
getOriginUserToken() {
return module.req.body.token;
},
};
export default module;
import * as firebaseAdmin from 'firebase-admin';
import firebase from 'firebase';
import config from '../config/config.js';
import { map, isEmpty } from 'lodash';
const module = {
database: null,
authId: 'dashboard',
/**
* Init the Firebase database.
* This must be called before calling any other method on this component.
*
* @returns {Promise.<this>} Resolved to the module properly initiated
**/
init() {
// Already initiated before
if (module.database) {
return Promise.resolve(module);
}
return module.initializeAuthenticatedApp().then(() => {
module.database = firebase.database();
return module;
});
},
/**
* Authenticate and initialize the app
*
* @returns {Promise} Resolves when successfully authenticated and with the
* app initiated
**/
initializeAuthenticatedApp() {
const firebaseConfig = config.get().firebase;
return module.generateAuthToken(module.authId).then(token => {
firebase.initializeApp(firebaseConfig);
return firebase.auth().signInWithCustomToken(token);
});
},
/**
* Returns the options object to initialize a firebase admin app.
*
* @returns {Object} Options object to pass to firebaseAdmin.initializeApp()
**/
getAdminCredentials() {
const firebaseConfig = config.get().firebase || {};
const serviceAccountConfig = config.get().serviceAccount || {};
return {
credential: firebaseAdmin.credential.cert(serviceAccountConfig),
databaseURL: firebaseConfig.databaseURL,
};
},
/**
* Initialize the firebaseAdmin app. Making sure only one app can be
* initialized at a time.
*
* @param {Object} options Options to init the Admin app
* @returns {void}
**/
initFirebaseAdmin(options) {
if (!isEmpty(firebaseAdmin.apps)) {
return;
}
firebaseAdmin.initializeApp(options);
},
/**
* Generates an auth token
*
* @param {String} tokenUser The user that will be bound to this token
* @returns {Promise.<String>} A custom token to be used for authentication
**/
generateAuthToken(tokenUser) {
module.initFirebaseAdmin(module.getAdminCredentials());
return firebaseAdmin.auth().createCustomToken(tokenUser);
},
/**
* Get the list of all elements stored in a Firebase node
*
* @param {String} nodePath Path to the list of elements in Firebase
* @returns {Promise.<Array.<Object>>} List of elements under that node
**/
getAll(nodePath) {
const rootNode = module.database.ref(`/${nodePath}`);
return rootNode.once('value').then(items =>
map(items.val(), (item, key) => ({
...item,
objectID: key,
}))
);
},
/**
* Pushes the list of items to the specified node
*
* @param {String} nodePath Firebase database path where to add new items
* @param {Array.<Object>} items Items to add to the specified path
* @returns {Promise.<Array.<Object>>} The list of items initially passed
**/
push(nodePath, items) {
const rootNode = module.database.ref(`/${nodePath}`);
return Promise.all(map(items, item => rootNode.push(item))).then(
() => items
);
},
/**
* Update a specified list of path with values
* Each key of the `updates` key should be the objectID of the element, and
* each value an object of each fields to update
*
* @param {String} nodePath Firebase database path where to update items
* @param {Object} updates Object of path and values to update
* @returns {Promise.<Object>} The list of updates initially passed
**/
update(nodePath, updates) {
const rootNode = module.database.ref(`/${nodePath}`);
return rootNode.update(updates).then(() => updates);
},
/**
* Utility method for testing purpose. Allow clearing the internal cache to be
* able to call .init() several times
*
* @returns {void}
**/
tearDown() {
module.database = null;
},
};
export default module;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment