Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is tests/parseRequest.js still valid? #38

Open
davidlehn opened this issue Feb 23, 2021 · 3 comments
Open

Is tests/parseRequest.js still valid? #38

davidlehn opened this issue Feb 23, 2021 · 3 comments
Assignees

Comments

@davidlehn
Copy link
Member

Is tests/parseRequest.js still valid? The testing setup only runs *.spec.js files so it hasn't been run and has failures. Should the test in it be run and fixed or is it old and can be removed?

@mattcollier Looks like you added this file, do you know?

Related is that uuid dependency could be updated or removed depending on the above.

@aljones15
Copy link
Contributor

aljones15 commented Feb 25, 2021

Just so it is known there are tests for the parseRequest api here:

describe('parseRequest API', function() {
// takes the result of parseRequest and tests it
const shouldBeParsed = parsed => {
parsed.should.have.property('params');
parsed.params.should.be.an('object');
parsed.should.have.property('scheme');
parsed.scheme.should.be.a('string');
parsed.should.have.property('signingString');
parsed.signingString.should.be.a('string');
parsed.should.have.property('algorithm');
parsed.should.have.property('keyId');
parsed.keyId.should.be.a('string');
};
// tests occur 3 seconds after the epoch
const now = 3;
it('should use Date.now() when now is not specified', () => {
const created = Math.floor(Date.now() / 1000);
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date().toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(created));
parsed.signingString.should.contain(`(created): ${created}`);
});
it('properly encodes `(created)` with a timestamp', () => {
const created = 1;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(created).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(created));
parsed.signingString.should.contain(`(created): ${created}`);
});
it('properly encodes `(created)` as a string', () => {
const created = 1;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(created).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(created));
parsed.signingString.should.contain(`(created): ${created}`);
});
it('rejects `(created)` in the future', () => {
const created = 4;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(created).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal(
'Invalid signature; the signature creation time is in the future.');
});
it('rejects `(created)` that is not a unix timestamp', () => {
const created = 'not a date';
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(created).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal(
'"created" must be a UNIX timestamp or JavaScript Date.');
});
it('convert (created) Date objects to unix timestamps', () => {
const date = new Date(1000);
const timestamp = Math.floor(date.getTime() / 1000);
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${timestamp}"`;
const request = {
headers: {
host: 'example.com:18443',
date,
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(timestamp));
parsed.signingString.should.contain(`(created): ${timestamp}`);
});
it('properly encodes `(expires)` with a timestamp', () => {
const expires = 3;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (expires)",' +
`signature="mockSignature",expires="${expires}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(now * 1000),
// convert the unix time to milliseconds for the header
expires: new Date(Number(expires) * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(expires)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.expires, 'expected created to be a string').
to.be.a('string');
parsed.params.expires.should.equal(String(expires));
parsed.signingString.should.contain(`(expires): ${expires}`);
});
it('properly encodes `(expires)` with a timestamp & x-date', () => {
const expires = 3;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="x-date host (request-target) (expires)",' +
`signature="mockSignature",expires="${expires}"`;
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(now * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(expires)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.expires, 'expected created to be a string').
to.be.a('string');
parsed.params.expires.should.equal(String(expires));
parsed.signingString.should.contain(`(expires): ${expires}`);
});
it('properly encodes `(expires)` as a string', () => {
const expires = String(3);
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (expires)",' +
`signature="mockSignature",expires="${expires}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(now * 1000),
// convert the unix time to milliseconds for the header
expires: new Date(Number(expires) * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(expires)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.expires, 'expected created to be a string').
to.be.a('string');
parsed.params.expires.should.equal(expires);
parsed.signingString.should.contain(`(expires): ${expires}`);
});
it('rejects `(expires)` that is not a unix timestamp', () => {
const expires = 'not a date';
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (expires)",' +
`signature="mockSignature",expires="${expires}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(expires),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(expires)', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal(
'"expires" must be a UNIX timestamp or JavaScript Date.');
});
it('rejects `(expires)` that is in the past', () => {
const expires = 1;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (expires)",' +
`signature="mockSignature",expires="${expires}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(expires),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(expires)', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('The signature has expired.');
});
it('rejects expires header that is in the past', () => {
const expires = 1;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target)",' +
`signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(now * 1000),
expires: new Date(expires * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('The request has expired.');
});
it('rejects if skew is greater then clockSkew', () => {
const _now = 1603386114;
const date = _now - 350;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="x-date host (request-target)",' +
`signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(date * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now: _now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('Clock skew of 350s was greater than 300s');
});
it('rejects if signature is missing from AuthzHeader', () => {
const _now = 1000;
const date = _now - 300;
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="x-date host (request-target)",';
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(date * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now: _now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('signature was not specified');
});
it('rejects if schema is not Signature', () => {
const date = now - 1;
const authorization = 'NotSignature keyId="https://example.com/key/1",' +
'headers="x-date host (request-target)",' +
`signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(date * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('scheme was not "Signature"');
});
it('rejects if no keyId', () => {
const date = now - 1;
const authorization = 'Signature ' +
'headers="x-date host (request-target)",' +
`signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(date * 1000),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(request-target)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('keyId was not specified');
});
it('should ignore unrecognized signature parameters', () => {
const created = Math.floor(Date.now() / 1000);
// note: this test adds a single unrecognized signature parameter
const unrecognized = 'unrecognized';
const authorization = 'Signature keyId="https://example.com/key/1",' +
'headers="date host (request-target) (created)",' +
`signature="mockSignature",created="${created}",` +
`unrecognized="${unrecognized}"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date().toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['host', '(created)', '(request-target)'];
const parsed = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(created));
parsed.signingString.should.contain(`(created): ${created}`);
parsed.signingString.should.not.contain(unrecognized);
});
// this is for covered content
it('default headers should be (created)', () => {
const created = 1;
const authorization = 'Signature keyId="https://example.com/key/1",' +
`signature="mockSignature",created="${created}"`;
const request = {
headers: {
host: 'example.com:18443',
'x-date': new Date(created * 1000).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const parsed = httpSignatureHeader.parseRequest(
request, {now});
expect(parsed, 'expected the parsing result to be an object').
to.be.an('object');
shouldBeParsed(parsed);
expect(parsed.params.created, 'expected created to be a string').
to.be.a('string');
parsed.params.created.should.equal(String(created));
parsed.signingString.should.contain(`(created): ${created}`);
});
it('should error if headers parameter is zero-length', () => {
const authorization = 'Signature keyId="https://example.com/key/1",' +
`headers="",signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal(
'The "headers" parameter must not be a zero-length string.');
});
it('should error if both created and headers are not set', () => {
const authorization = 'Signature keyId="https://example.com/key/1",' +
`signature="mockSignature"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(now * 1000).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('created was not in the request');
});
it('should error if expires is in options.headers, but not request', () => {
const authorization = 'Signature keyId="https://example.com/key/1",' +
`signature="mockSignature",created="1"`;
const request = {
headers: {
host: 'example.com:18443',
date: new Date(now * 1000).toUTCString(),
authorization
},
method: 'GET',
url: 'https://example.com:18443/1/2/3',
};
const expectedHeaders = ['(expires)'];
let error = null;
let result = null;
try {
result = httpSignatureHeader.parseRequest(
request, {headers: expectedHeaders, now});
} catch(e) {
error = e;
}
expect(result, 'result should not exist').to.be.null;
expect(error, 'error should exist').to.not.be.null;
error.message.should.equal('(expires) was not a signed header');
});
});

It looks like the parseRequest.js tests were integration tests before.

@mattcollier I don't think anyone wants to remove this file until the file's author oks it. Are you ok with tests/parseRequest.js being removed from the project?

@aljones15 aljones15 self-assigned this Feb 25, 2021
@mattcollier
Copy link
Contributor

The tests in the file were largely or entirely from the joyent implementation (per the header). If we have good test coverage for the API elsewhere then I suspect they are no longer needed. @aljones15 You've been maintaining this package for the past year, I leave it to you to make a decision here.

@aljones15
Copy link
Contributor

aljones15 commented Feb 25, 2021

ok before it gets removed need to check to make sure all tests from that file are now unit tests:

  • no auth header on request
  • scheme was not Signature
  • keyId was not specified
  • key id no value
  • key id no quotes
  • key id param quotes
  • param name with space
  • no signature
  • no date header
  • valid default headers
  • valid expires header
  • explicit headers missing
  • valid explicit headers request-target
  • valid custom headers with multiple values
  • expired via clock skew
  • expired via "Expires" header
  • missing required header
  • valid mixed case headers
  • valid custom authorizationHeaderName

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants