-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contract): expose all endpoints required for secure hmackey auth
- Loading branch information
1 parent
752750a
commit f613b63
Showing
25 changed files
with
1,005 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"cSpell.words": [ | ||
"millis", | ||
"Unauthable" | ||
] | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// methods | ||
export { assertRequestSignatureAuthenticity } from '../logic/signatures/assertRequestSignatureAuthenticity'; | ||
export { createSecureRequestSignature } from '../logic/signatures/createSecureRequestSignature'; | ||
export { getRequestSignatureFromHeaders } from '../logic/headers/getRequestSignatureFromHeaders'; | ||
|
||
// errors | ||
export { UnauthenticRequestSignatureError } from '../utils/errors/UnauthenticRequestSignatureError'; | ||
export { UnauthableRequestSignatureError } from '../utils/errors/UnauthableRequestSignatureError'; | ||
export { SimpleHmacKeyAuthError } from '../utils/errors/SimpleHmacKeyAuthError'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export interface SimpleSignableRequest { | ||
host: string; | ||
endpoint: string; | ||
headers: Record<string, string | string[]>; | ||
payload: Record<string, any>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { DomainObject } from 'domain-objects'; | ||
import Joi from 'joi'; | ||
|
||
const schema = Joi.object().keys({ | ||
clientPublicKey: Joi.string().required(), | ||
millisSinceEpoch: Joi.number().required(), | ||
nonce: Joi.string().required(), | ||
}); | ||
|
||
/** | ||
* the readable, public metadata included in the signature which is used to authenticate the signature digest | ||
*/ | ||
export interface SimpleSignatureMetadata { | ||
/** | ||
* the client public key is used by the authorizer to lookup the clientPrivateKeyHash to authenticate the signature - and to identify the client after authentication | ||
*/ | ||
clientPublicKey: string; | ||
|
||
/** | ||
* the millisSinceEpoch is used by the authorizer to check that the request is not being replayed, another way of eliminating replay attack vulnerabilities | ||
*/ | ||
millisSinceEpoch: number; | ||
|
||
/** | ||
* the nonce is used by the authorizer to check that the request is not being replayed, eliminating replay attack vulnerabilities | ||
*/ | ||
nonce: string; | ||
} | ||
|
||
export class SimpleSignatureMetadata | ||
extends DomainObject<SimpleSignatureMetadata> | ||
implements SimpleSignatureMetadata | ||
{ | ||
public static schema = schema; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './contract/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import crypto from 'crypto'; | ||
|
||
/** | ||
* a simple function which converts a string into an sha256 hash | ||
* | ||
* note | ||
* - this can only be run on node | ||
*/ | ||
export const toHashSha256 = async (message: string) => | ||
crypto.createHash('sha256').update(message).digest('hex'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { uuid } from '../../deps'; | ||
import { SimpleSignatureMetadata } from '../../domain/SimpleSignatureMetadata'; | ||
import { toHashSha256 } from '../hash/toHashSha256'; | ||
import { encodeRequestSignatureMetadata } from '../signatures/encodeRequestSignatureMetadata'; | ||
import { getRequestSignatureFromHeaders } from './getRequestSignatureFromHeaders'; | ||
|
||
const getExampleSignature = async () => | ||
[ | ||
encodeRequestSignatureMetadata( | ||
new SimpleSignatureMetadata({ | ||
clientPublicKey: 'pub_test', | ||
millisSinceEpoch: 821, | ||
nonce: uuid(), | ||
}), | ||
), | ||
await toHashSha256('b'), | ||
].join('.'); | ||
|
||
describe('getRequestSignatureFromHeaders', () => { | ||
it('should return null if there is no authorization header', () => { | ||
const signature = getRequestSignatureFromHeaders({ | ||
headers: {}, | ||
}); | ||
expect(signature).toEqual(null); | ||
}); | ||
it('should return null the authorization header contains something that does not look like a request signature', () => { | ||
const signature = getRequestSignatureFromHeaders({ | ||
headers: { | ||
authorization: 'not_a_signature', | ||
}, | ||
}); | ||
expect(signature).toEqual(null); | ||
}); | ||
it('should return the signature if it looks like a signature', async () => { | ||
const exampleSignature = await getExampleSignature(); | ||
const signature = getRequestSignatureFromHeaders({ | ||
headers: { | ||
authorization: exampleSignature, | ||
}, | ||
}); | ||
expect(signature).toEqual(exampleSignature); | ||
}); | ||
it('should return the signature if it looks like a signature, even if its prefixed by something else', async () => { | ||
const exampleSignature = await getExampleSignature(); | ||
const signature = getRequestSignatureFromHeaders({ | ||
headers: { | ||
authorization: ['Bearer', exampleSignature].join(' '), | ||
}, | ||
}); | ||
expect(signature).toEqual(exampleSignature); | ||
}); | ||
it('should return the signature if it looks like a signature, even if its prefixed by something else', async () => { | ||
const exampleSignature = await getExampleSignature(); | ||
const signature = getRequestSignatureFromHeaders({ | ||
headers: { | ||
authorization: ['HMAC', exampleSignature].join(' '), | ||
}, | ||
}); | ||
expect(signature).toEqual(exampleSignature); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { isRequestSignature } from '../signatures/isRequestSignature'; | ||
|
||
export const getRequestSignatureFromHeaders = ({ | ||
headers, | ||
}: { | ||
headers: Record<string, any>; | ||
}): string | null => { | ||
// grab the authorization header field | ||
const authorization = headers.authorization ?? headers.Authorization ?? null; // headers are case-insensitive, by spec: https://stackoverflow.com/a/5259004/3068233 | ||
if (!authorization) return null; | ||
const potentiallyARequestSignature = authorization.split(' ').slice(-1)[0]; // the last part of the header is probably the signature | ||
if (!isRequestSignature(potentiallyARequestSignature)) return null; // check that it looks like a signature, since other strings can be passed here | ||
return potentiallyARequestSignature; | ||
}; |
Oops, something went wrong.