UNPKG

2.89 kBJavaScriptView Raw
1/*!
2 * basicauth.js - basic auth for bweb
3 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
4 * https://github.com/bcoin-org/bweb
5 */
6
7'use strict';
8
9const assert = require('bsert');
10const {isAscii} = require('../util');
11
12let crypto = null;
13
14/**
15 * Basic auth middleware.
16 * @param {Object} options
17 * @returns {Function}
18 */
19
20function 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 * Helpers
111 */
112
113function fail(res, realm) {
114 res.setStatus(401);
115 res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
116 res.end();
117}
118
119function unbase64(str) {
120 return Buffer.from(str, 'base64').toString('ascii');
121}
122
123function sha256(data) {
124 if (!crypto)
125 crypto = require('crypto');
126
127 return crypto.createHash('sha256').update(data).digest();
128}
129
130function 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 * Expose
144 */
145
146module.exports = basicAuth;