2
votes

I have written a Google Cloud Function Express app and a command-line tool that uses Node.js on my local Mac.

Calling myclitool login, a one-time prompt asks the user for their email and password. The CLI tool sends the email and password inside the request body using an HTTP POST request to the Express server, over SSL.

The server will send back a private API Key (generated by a trigger function at the time the user was registered) that will be written to ~/.myclitoolrc and will be used for all subsequent calls to my API endpoint.

Each subsequent call from the CLI tool will lookup the private API Key in the Firestore accounts collection, and authenticate on per API call basis.

admin.firestore()
  .collection('accounts')
  .where('privateApiKey', '==', privateApiKey)
  .get() // and so on

So far, the following code will locate the admin.auth.UserRecord.

Service.prototype.signin = function signin(email, password) {
  return new Promise(function(resolve, reject) {
    admin.auth().getUserByEmail(email)
    .then(userRecord => {
      console.log(userRecord);
      resolve('some value later');
    })
    .catch(err => {
      reject(err);
    });
  });
};

The Firebase documentation says: https://firebase.google.com/docs/reference/admin/node/admin.auth.UserRecord

passwordHash (string or null)

The user’s hashed password (base64-encoded), only if Firebase Auth hashing algorithm (SCRYPT) is used. If a different hashing algorithm had been used when uploading this user, as is typical when migrating from another Auth system, this will be an empty string. If no password is set, this will be null. This is only available when the user is obtained from listUsers().

passwordSalt (string or null)

The user’s password salt (base64-encoded), only if Firebase Auth hashing algorithm (SCRYPT) is used. If a different hashing algorithm had been used to upload this user, typical when migrating from another Auth system, this will be an empty string. If no password is set, this will be null. This is only available when the user is obtained from listUsers().

The UserRecord is retrieved and contains the SCRYPTd passwordHash and passwordSalt properties.

UserRecord {
  uid: 'kjep.[snip]..i2',
  email: '[email protected]',
  emailVerified: false,
  displayName: undefined,
  photoURL: undefined,
  phoneNumber: undefined,
  disabled: false,
  metadata: 
   UserMetadata {
     creationTime: 'Thu, 12 Apr 2018 09:15:23 GMT',
     lastSignInTime: 'Thu, 03 May 2018 03:57:06 GMT' },
  providerData: 
   [ UserInfo {
       uid: '[email protected]',
       displayName: undefined,
       email: '[email protected]',
       photoURL: undefined,
       providerId: 'password',
       phoneNumber: undefined } ],
  passwordHash: 'U..base64..Q=',
  passwordSalt: undefined,
  customClaims: undefined,
  tokensValidAfterTime: 'Thu, 12 Apr 2018 09:15:23 GMT' }

There appears to be no verification functions as part of the Firebase Admin SDK admin.auth().

Should I implement the SCRYPT verification myself by finding an algorithm or ready-made Node module, or should I take the absence of any verification functions as a sign that this is not the best approach?

If so, please recommend a better design, bearing in mind this is a prototype project and to implement full Oauth2 would be quite time consuming.

1
If you simply want to allow controlled access to Cloud Firestore from a Node application, you can use the standard Firebase Javascript SDK and enforce security rules. Let me know if this is your use case and I'll post some code examples.Jason Berryman
I want to expose my services via a RESTful endpoint, so using the Firebase JavaScript SDK might not be suitable for all of the type of commands the CLI tool would offer. Would be grateful to see any examples to get some more ideas though.Andy Fusniak

1 Answers

0
votes

As requested in the comments, here is some example code for accessing Cloud Firestore using Node.js via the Firebase Javascript SDK (enforces security rules).

There is a bug filed in v4.13.0 (now closed). I haven't tested 4.13.1 yet, but the fix has been merged into the master branch. If it doesn't work, you should try v4.12.0.

const firebase = require('firebase');
require("firebase/firestore");

// Initialize Firebase
// You get these details from the Firebase Console
let config = {
  apiKey: "yourAPIkey",
  authDomain: "yourAuthDomain",
  databaseURL: "https://yourProjectID.firebaseio.com",
  projectId: "yourProjectID",
  messagingSenderId: "yourId"
};
firebase.initializeApp(config);

let email = '[email protected]';
let password = 'yourVerySecurePassword';

firebase.auth().signInWithEmailAndPassword(email, password)
  .catch(error => {
    console.log(error);
  });

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    console.log('I am logged in');

    // Initialise Firestore
    const firestore = firebase.firestore();
    const settings = {timestampsInSnapshots: true};
    firestore.settings(settings);

    return firestore
      .collection('accounts')
      .where('privateApiKey', '==', privateApiKey)
      .get()
      .then((querySnapshot) => {
        querySnapshot.forEach((documentSnapshot) => {
          if (documentSnapshot.exists) {
            console.log(documentSnapshot.id);
          }
        });
      });
  } else {
    // User is signed out.
    // ...
  }
});