Skip to content

Instantly share code, notes, and snippets.

@justsml
Forked from TimMensch/auth-complex.js
Last active November 8, 2018 23:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justsml/ab813f9208009befbfa3890028d67559 to your computer and use it in GitHub Desktop.
Save justsml/ab813f9208009befbfa3890028d67559 to your computer and use it in GitHub Desktop.
Minor updates
// Easier to follow. Equally easy to test -- and with one fewer function in need of testing.
import { userInfo } from "os";
const Promise = require("bluebird");
const { hashStringAsync } = Promise.promisifyAll(require("./lib/crypto"));
const { logEventAsync } = Promise.promisifyAll(require("./lib/log"));
const { openAsync } = Promise.promisifyAll(require("./lib/db"));
const { TimeoutError, ValidationError, NotFoundError } = require("./errors");
const _openHandle = openAsync(); // FYI: Promises include memoization (caching) built into same API
module.exports = { auth };
/* auth is our main function */
async function auth({
username,
password,
_onTimeoutError = errorHandler,
_onNotFoundError = errorHandler,
_onValidationError = errorHandler,
}) {
try {
// No more reusing "password" as "hashed password".
// Still starting the hash ASAP and not waiting for it.
const hashedPasswordPromise = hashStringAsync(password);
authValidate({ username, password });
const { users, admin } = await getModels();
const user = await users.findOneAsync({
username,
// I would have renamed "password" here; bad form to have a field named
// password that's expecting a hash.
password: await hashedPasswordPromise, // Deferred await to when we need it.
});
// Inventing a side tributary: If a user is an admin, then grab their admin
// permissions.
await userFound(user);
return loadAdmin(user);
} catch (e) {
// Left this in because Bluebird's extension for matching errors is cute.
// It's super easy to forget the `await` here - it changes how functions work in pretty substantial, yet non-obvious ways.
// if the custom handlers (_onTimeoutError) fail to output the error info, they wouldn't be able re-throw errors to be caught higher up
await Promise.reject(e) // I agree, it is pretty cute :)
.catch(TimeoutError, _onTimeoutError)
.catch(NotFoundError, _onNotFoundError)
.catch(ValidationError, _onValidationError)
.catch(errorHandler);
}
}
async function loadAdmin(user) {
if (user.admin) {
const admin = await admin.findOneAsync({ id: user.id });
user.adminPermissions = admin.permissions;
}
return user;
}
function authValidate({ username, password }) {
if (!username || username.length < 4) {
throw new ValidationError("Invalid username. Required, 4 char minimum.");
}
if (!password || password.length < 4) {
throw new ValidationError("Invalid password. Required, 4 char minimum.");
}
}
function userFound(results) {
if (results) return;
throw new NotFoundError("No users matched. Login failed");
}
function getModels() {
return _openHandle.then(({ models }) => models);
}
function errorHandler(err) {
console.error("Failed auth!", err);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment