diff options
Diffstat (limited to 'src/app/services/authentication/index.js')
| -rw-r--r-- | src/app/services/authentication/index.js | 140 |
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; +} |
