1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | 'use strict';
|
8 |
|
9 | const assert = require('bsert');
|
10 | const {isAscii} = require('../util');
|
11 |
|
12 | let crypto = null;
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | function basicAuth(options) {
|
21 | assert(options, 'Basic auth requires options.');
|
22 |
|
23 | const hash = options.hash || sha256;
|
24 | assert(typeof hash === 'function');
|
25 |
|
26 | let userHash = null;
|
27 | let passHash = null;
|
28 | let realm = 'server';
|
29 |
|
30 | if (options.username != null) {
|
31 | assert(typeof options.username === 'string');
|
32 | assert(options.username.length <= 255, 'Username too long.');
|
33 | assert(isAscii(options.username), 'Username must be ASCII.');
|
34 | userHash = hash(Buffer.from(options.username, 'ascii'));
|
35 | }
|
36 |
|
37 | assert(typeof options.password === 'string');
|
38 | assert(options.password.length <= 255, 'Password too long.');
|
39 | assert(isAscii(options.password), 'Password must be ASCII.');
|
40 | passHash = hash(Buffer.from(options.password, 'ascii'));
|
41 |
|
42 | if (options.realm != null) {
|
43 | assert(typeof options.realm === 'string');
|
44 | realm = options.realm;
|
45 | }
|
46 |
|
47 | return async (req, res) => {
|
48 | const hdr = req.headers['authorization'];
|
49 |
|
50 | if (!hdr) {
|
51 | fail(res, realm);
|
52 | return;
|
53 | }
|
54 |
|
55 | if (hdr.length > 690) {
|
56 | fail(res, realm);
|
57 | return;
|
58 | }
|
59 |
|
60 | const parts = hdr.split(' ');
|
61 |
|
62 | if (parts.length !== 2) {
|
63 | fail(res, realm);
|
64 | return;
|
65 | }
|
66 |
|
67 | const [type, credentials] = parts;
|
68 |
|
69 | if (type !== 'Basic') {
|
70 | fail(res, realm);
|
71 | return;
|
72 | }
|
73 |
|
74 | const auth = unbase64(credentials);
|
75 | const items = auth.split(':');
|
76 |
|
77 | const username = items.shift();
|
78 | const password = items.join(':');
|
79 |
|
80 | const user = Buffer.from(username, 'ascii');
|
81 | const pass = Buffer.from(password, 'ascii');
|
82 |
|
83 | if (userHash) {
|
84 | if (user.length > 255) {
|
85 | fail(res, realm);
|
86 | return;
|
87 | }
|
88 |
|
89 | if (!ccmp(hash(user), userHash)) {
|
90 | fail(res, realm);
|
91 | return;
|
92 | }
|
93 | }
|
94 |
|
95 | if (pass.length > 255) {
|
96 | fail(res, realm);
|
97 | return;
|
98 | }
|
99 |
|
100 | if (!ccmp(hash(pass), passHash)) {
|
101 | fail(res, realm);
|
102 | return;
|
103 | }
|
104 |
|
105 | req.username = username;
|
106 | };
|
107 | };
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | function fail(res, realm) {
|
114 | res.setStatus(401);
|
115 | res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
116 | res.end();
|
117 | }
|
118 |
|
119 | function unbase64(str) {
|
120 | return Buffer.from(str, 'base64').toString('ascii');
|
121 | }
|
122 |
|
123 | function sha256(data) {
|
124 | if (!crypto)
|
125 | crypto = require('crypto');
|
126 |
|
127 | return crypto.createHash('sha256').update(data).digest();
|
128 | }
|
129 |
|
130 | function ccmp(a, b) {
|
131 | if (b.length === 0)
|
132 | return a.length === 0;
|
133 |
|
134 | let res = a.length ^ b.length;
|
135 |
|
136 | for (let i = 0; i < a.length; i++)
|
137 | res |= a[i] ^ b[i % b.length];
|
138 |
|
139 | return res === 0;
|
140 | }
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | module.exports = basicAuth;
|