summaryrefslogtreecommitdiff
path: root/src/app/services/authentication/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/services/authentication/index.js')
-rw-r--r--src/app/services/authentication/index.js140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/app/services/authentication/index.js b/src/app/services/authentication/index.js
new file mode 100644
index 0000000..df8d7d0
--- /dev/null
+++ b/src/app/services/authentication/index.js
@@ -0,0 +1,140 @@
+/**
+ * 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;
+}