http: better handling of api keys.
This commit is contained in:
parent
1a4268544f
commit
1d7b8ca7c1
@ -181,27 +181,22 @@ HTTPBase.prototype.cors = function cors() {
|
||||
*/
|
||||
|
||||
HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
assert(options, 'Basic auth requires options.');
|
||||
|
||||
let user = options.username;
|
||||
let pass = options.password;
|
||||
let realm = options.realm;
|
||||
|
||||
if (user) {
|
||||
if (typeof user === 'string') {
|
||||
assert(user.length <= 255, 'Username too long.');
|
||||
user = Buffer.from(user, 'utf8');
|
||||
}
|
||||
assert(Buffer.isBuffer(user));
|
||||
if (user != null) {
|
||||
assert(typeof user === 'string');
|
||||
assert(user.length <= 255, 'Username too long.');
|
||||
assert(util.isAscii(user), 'Username must be ASCII.');
|
||||
user = digest.hash256(user);
|
||||
}
|
||||
|
||||
if (typeof pass === 'string') {
|
||||
assert(pass.length <= 255, 'Password too long.');
|
||||
pass = Buffer.from(pass, 'utf8');
|
||||
}
|
||||
|
||||
assert(Buffer.isBuffer(pass));
|
||||
assert(typeof pass === 'string');
|
||||
assert(pass.length <= 255, 'Password too long.');
|
||||
assert(util.isAscii(pass), 'Password must be ASCII.');
|
||||
pass = digest.hash256(pass);
|
||||
|
||||
if (!realm)
|
||||
@ -209,11 +204,11 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
|
||||
assert(typeof realm === 'string');
|
||||
|
||||
function fail(res) {
|
||||
const fail = (res) => {
|
||||
res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
||||
res.setStatus(401);
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
return async (req, res) => {
|
||||
const hdr = req.headers['authorization'];
|
||||
@ -223,7 +218,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hdr.length > 1000) {
|
||||
if (hdr.length > 674) {
|
||||
fail(res);
|
||||
return;
|
||||
}
|
||||
@ -235,12 +230,14 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parts[0] !== 'Basic') {
|
||||
const [type, b64] = parts;
|
||||
|
||||
if (type !== 'Basic') {
|
||||
fail(res);
|
||||
return;
|
||||
}
|
||||
|
||||
const auth = Buffer.from(parts[1], 'base64').toString('utf8');
|
||||
const auth = Buffer.from(b64, 'base64').toString('ascii');
|
||||
const items = auth.split(':');
|
||||
|
||||
const username = items.shift();
|
||||
@ -252,7 +249,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const raw = Buffer.from(username, 'utf8');
|
||||
const raw = Buffer.from(username, 'ascii');
|
||||
const hash = digest.hash256(raw);
|
||||
|
||||
if (!ccmp(hash, user)) {
|
||||
@ -266,7 +263,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
|
||||
return;
|
||||
}
|
||||
|
||||
const raw = Buffer.from(password, 'utf8');
|
||||
const raw = Buffer.from(password, 'ascii');
|
||||
const hash = digest.hash256(raw);
|
||||
|
||||
if (!ccmp(hash, pass)) {
|
||||
@ -316,16 +313,19 @@ HTTPBase.prototype.parseBody = async function parseBody(req, options) {
|
||||
if (req.method === 'GET')
|
||||
return body;
|
||||
|
||||
const data = await this.readBody(req, 'utf8', options);
|
||||
|
||||
if (!data)
|
||||
return body;
|
||||
|
||||
let type = req.contentType;
|
||||
|
||||
if (options.contentType)
|
||||
type = options.contentType;
|
||||
|
||||
if (type === 'bin')
|
||||
return body;
|
||||
|
||||
const data = await this.readBody(req, 'utf8', options);
|
||||
|
||||
if (!data)
|
||||
return body;
|
||||
|
||||
switch (type) {
|
||||
case 'json':
|
||||
body = JSON.parse(data);
|
||||
@ -333,8 +333,6 @@ HTTPBase.prototype.parseBody = async function parseBody(req, options) {
|
||||
case 'form':
|
||||
body = parsePairs(data, options.keyLimit);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return body;
|
||||
|
||||
@ -370,7 +370,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) {
|
||||
if (key.length > 255)
|
||||
throw new Error('Invalid API key.');
|
||||
|
||||
const data = Buffer.from(key, 'utf8');
|
||||
const data = Buffer.from(key, 'ascii');
|
||||
const hash = digest.hash256(data);
|
||||
|
||||
if (!ccmp(hash, this.options.apiHash))
|
||||
@ -701,7 +701,7 @@ function HTTPOptions(options) {
|
||||
this.logger = null;
|
||||
this.node = null;
|
||||
this.apiKey = base58.encode(random.randomBytes(20));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8'));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
|
||||
this.noAuth = false;
|
||||
|
||||
this.prefix = null;
|
||||
@ -742,8 +742,10 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
'API key must be a string.');
|
||||
assert(options.apiKey.length <= 255,
|
||||
'API key must be under 256 bytes.');
|
||||
assert(util.isAscii(options.apiKey),
|
||||
'API key must be ascii.');
|
||||
this.apiKey = options.apiKey;
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8'));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
|
||||
}
|
||||
|
||||
if (options.noAuth != null) {
|
||||
|
||||
@ -772,6 +772,17 @@ if (!''.startsWith) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a string is a plain
|
||||
* ascii string (no control characters).
|
||||
* @param {String} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
util.isAscii = function isAscii(str) {
|
||||
return /^[\t\n\r -~]*$/.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get memory usage info.
|
||||
* @returns {Object}
|
||||
|
||||
@ -395,12 +395,11 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
|
||||
for (const output of outputs) {
|
||||
const valid = new Validator([output]);
|
||||
const raw = valid.buf('script');
|
||||
let script = null;
|
||||
|
||||
let script;
|
||||
if (valid.has('script')) {
|
||||
const raw = valid.buf('script');
|
||||
if (raw)
|
||||
script = Script.fromRaw(raw);
|
||||
}
|
||||
|
||||
options.outputs.push({
|
||||
script: script,
|
||||
@ -434,12 +433,11 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
|
||||
for (const output of outputs) {
|
||||
const valid = new Validator([output]);
|
||||
const raw = valid.buf('script');
|
||||
let script = null;
|
||||
|
||||
let script;
|
||||
if (valid.has('script')) {
|
||||
const raw = valid.buf('script');
|
||||
if (raw)
|
||||
script = Script.fromRaw(raw);
|
||||
}
|
||||
|
||||
options.outputs.push({
|
||||
script: script,
|
||||
@ -698,12 +696,13 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
const valid = req.valid();
|
||||
const acct = valid.str('account');
|
||||
const txs = await req.wallet.getHistory(acct);
|
||||
const result = [];
|
||||
|
||||
common.sortTX(txs);
|
||||
|
||||
const details = await req.wallet.toDetails(txs);
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const item of details)
|
||||
result.push(item.toJSON());
|
||||
|
||||
@ -715,12 +714,13 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
const valid = req.valid();
|
||||
const acct = valid.str('account');
|
||||
const txs = await req.wallet.getPending(acct);
|
||||
const result = [];
|
||||
|
||||
common.sortTX(txs);
|
||||
|
||||
const details = await req.wallet.toDetails(txs);
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const item of details)
|
||||
result.push(item.toJSON());
|
||||
|
||||
@ -731,7 +731,6 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
this.get('/:id/tx/range', async (req, res) => {
|
||||
const valid = req.valid();
|
||||
const acct = valid.str('account');
|
||||
const result = [];
|
||||
|
||||
const options = {
|
||||
start: valid.u32('start'),
|
||||
@ -744,6 +743,8 @@ HTTPServer.prototype.initRouter = function initRouter() {
|
||||
|
||||
const details = await req.wallet.toDetails(txs);
|
||||
|
||||
const result = [];
|
||||
|
||||
for (const item of details)
|
||||
result.push(item.toJSON());
|
||||
|
||||
@ -852,7 +853,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) {
|
||||
|
||||
if (!this.options.noAuth) {
|
||||
const valid = new Validator([args]);
|
||||
const key = valid.str(0);
|
||||
const key = valid.str(0, '');
|
||||
|
||||
if (key.length > 255)
|
||||
throw new Error('Invalid API key.');
|
||||
@ -943,7 +944,7 @@ function HTTPOptions(options) {
|
||||
this.logger = null;
|
||||
this.walletdb = null;
|
||||
this.apiKey = base58.encode(random.randomBytes(20));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8'));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
|
||||
this.serviceHash = this.apiHash;
|
||||
this.noAuth = false;
|
||||
this.walletAuth = false;
|
||||
@ -983,10 +984,12 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) {
|
||||
if (options.apiKey != null) {
|
||||
assert(typeof options.apiKey === 'string',
|
||||
'API key must be a string.');
|
||||
assert(options.apiKey.length <= 200,
|
||||
'API key must be under 200 bytes.');
|
||||
assert(options.apiKey.length <= 255,
|
||||
'API key must be under 255 bytes.');
|
||||
assert(util.isAscii(options.apiKey),
|
||||
'API key must be ASCII.');
|
||||
this.apiKey = options.apiKey;
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8'));
|
||||
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
|
||||
}
|
||||
|
||||
if (options.noAuth != null) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user