/** * Authentication API service. * @module app/services/authentication/index */ import { encryptPassword, generateAccessToken, checkAccessToken, checkUserIsActive, } from "app/services/authentication/helpers"; import { ROLES } from "app/constants"; import { randomFruit } from "app/utils/random_utils"; import Service from "app/db/service/base"; /** * Service API for authenticating users */ export default async function AuthenticationService(bookshelf) { const service = await Service({ bookshelf, name: "authentication", authenticate: false, routes: [ { method: "post", route: "/login", handlers: [login, sendAccessToken], summary: "Generate a JWT for a user", requestBody: { username: { type: "string", description: "The username", }, password: { type: "string", description: "The SHA256 of the user's password", }, }, }, { route: "/check", handlers: [checkAccessToken(), checkUserIsActive, sendIsActive], summary: "Check if the user is still active. Simply queries using the bearer token", requestBody: { type: "object", properties: { active: { type: "boolean", description: "Will be true if the user is active", }, }, }, }, { route: "/generate", handlers: [checkAccessToken(), checkUserIsActive, generatePassword], summary: "Generate a temporary random password. Passwords are long and memorable, but are not guaranteed to be cryptographically secure.", requestBody: { type: "object", properties: { password: { type: "string", description: "A temporary password.", }, }, }, }, ], }); /** * Login API handler */ async function login(request, response, next) { const User = bookshelf.model("User"); const { username, password: plainPassword } = request.body; const password = encryptPassword(plainPassword); let user, error; try { user = await new User({ username, password, }).fetch(); } catch (dbError) { error = dbError; } if (user && user.get("is_active")) { request.user = user; } else { error = new Error("UserNotActive"); } next(error); } /** * Generate a JWT if the login was successful, and set it on the response object. */ function sendAccessToken(request, response, next) { const token = { user_id: request.user.get("user_id"), username: request.user.get("username"), role: request.user.get("role"), }; if (token.role < ROLES.moderator) { token.can_search = request.user.get("can_search"); } response.locals.token = generateAccessToken(token); next(); } /** * Generate a random passphrase */ async function generatePassword(request, response, next) { // this emits a weird error when using `import` syntax response.locals.password = [ randomFruit("en", { maxWords: 1 }), randomFruit("en", { maxWords: 1 }), randomFruit("en", { maxWords: 1 }), randomFruit("en", { maxWords: 1 }), ] .join(" ") .toLowerCase(); next(); } /** * Indicate that a user is active. Should have already been interrupted if the user is inactive. */ function sendIsActive(request, response, next) { response.locals.active = true; next(); } return service; }