forked from LionC/express-basic-auth
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.ts
More file actions
135 lines (117 loc) · 4.32 KB
/
index.ts
File metadata and controls
135 lines (117 loc) · 4.32 KB
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
import assert from 'assert';
import auth from 'basic-auth';
import { timingSafeEqual } from 'crypto';
import { NextFunction, RequestHandler, Response } from 'express';
import { BasicAuthMiddlewareOptions, IBasicAuthedRequest, IUnauthorizeOptions } from './express-basic-auth';
buildMiddleware.safeCompare = safeCompare;
module.exports = buildMiddleware;
function buildMiddleware(options: BasicAuthMiddlewareOptions): RequestHandler {
const { challenge,
authorizer,
isAsync,
getResponseBody,
realm
} = setupOptions(options);
return function authMiddleware(req: IBasicAuthedRequest, res: Response, next: NextFunction): any {
const unauthOpts: IUnauthorizeOptions = {
challenge,
getResponseBody,
req,
realm,
res,
};
const authentication = auth(req);
if (!authentication) {
return unauthorized(unauthOpts);
}
req.auth = {
user: authentication.name,
password: authentication.pass
}
if (isAsync) {
return authorizer(authentication.name, authentication.pass, (err: Error, approved: boolean) => {
return authorizerCallback(err, approved, next, unauthOpts);
});
} else if (!authorizer(authentication.name, authentication.pass)) {
return unauthorized(unauthOpts);
}
return next();
}
}
function setupOptions(options: any) {
const challenge = options.challenge === true;
const users = options['users'] || {}; // tslint:disable-line
const authorizer = options['authorizer'] || ((user: string, pass: string) => { return staticUsersAuthorizer(user, pass, users); });
const isAsync = options['authorizeAsync'] === true;
const getResponseBody = ensureFunction(options.unauthorizedResponse, '');
const realm = ensureFunction(options['realm']);
assert(typeof users === 'object', `Expected an object for the basic auth users, found ${typeof users} instead`);
assert(typeof authorizer === 'function', `Expected a function for the basic auth authorizer, found ${typeof authorizer} instead`);
return {
challenge,
users,
authorizer,
isAsync,
getResponseBody,
realm,
};
}
function authorizerCallback(err: any, approved: boolean, next: NextFunction, options: IUnauthorizeOptions) {
assert.ifError(err);
if (approved) {
return next();
}
return unauthorized(options);
}
function unauthorized({
challenge,
getResponseBody,
req,
realm,
res
}: IUnauthorizeOptions) {
if (challenge) {
let challengeString = 'Basic'
const realmName = realm(req);
if (realmName) {
challengeString = `${challengeString} realm="${realmName}"`;
}
res.set('WWW-Authenticate', challengeString);
}
//TODO: Allow response body to be JSON (maybe autodetect?)
const response = getResponseBody(req);
if (typeof response == 'string') {
return res.status(401).send(response);
}
return res.status(401).json(response);
}
function ensureFunction(option: BasicAuthMiddlewareOptions, defaultValue: any = null) {
if (option == undefined) {
return () => { return defaultValue };
}
if (typeof option !== 'function') {
return () => { return option };
}
return option;
}
function staticUsersAuthorizer(username: string, password: string, users: []): boolean {
for (var currentUser in users) {
const checkUser = safeCompare(username, currentUser);
const checkPassword = safeCompare(password, users[currentUser]);
if (checkUser && checkPassword) {
return true
}
}
return false
}
// Credits for the actual algorithm go to github/@Bruce17
// Thanks to github/@hraban for making me implement this
function safeCompare(userInput:string , secret: string) {
const userInputLength = Buffer.byteLength(userInput)
const secretLength = Buffer.byteLength(secret)
const userInputBuffer = Buffer.alloc(userInputLength, 0, 'utf8')
userInputBuffer.write(userInput)
const secretBuffer = Buffer.alloc(userInputLength, 0, 'utf8')
secretBuffer.write(secret)
return !!(timingSafeEqual(userInputBuffer, secretBuffer) && (userInputLength === secretLength)) // tslint:disable-line
}