1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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;
}
|