-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): secure admin api with hmac signatures (#2709)
* feat(auth): secure admin api with hmac signatures * feat: refactor api signature script into two separate functions * feat: rename env var, replay attack resistance * feat(wip): test signature validation util * feat: sanity tests for signature utility * fix: allow explicit any * chore: formatting * feat: new test, context type, proper test expiry * chore: remove log
- Loading branch information
Showing
17 changed files
with
330 additions
and
8 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
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
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
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
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
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,148 @@ | ||
import { IocContract } from '@adonisjs/fold' | ||
import { Redis } from 'ioredis' | ||
|
||
import { AppContext, AppServices } from '../app' | ||
import { Config } from '../config/app' | ||
import { createContext } from '../tests/context' | ||
import { generateApiSignature } from '../tests/apiSignature' | ||
import { initIocContainer } from '..' | ||
import { verifyApiSignature } from './utils' | ||
import { TestContainer, createTestApp } from '../tests/app' | ||
|
||
describe('utils', (): void => { | ||
let deps: IocContract<AppServices> | ||
let appContainer: TestContainer | ||
let redis: Redis | ||
|
||
describe('admin api signatures', (): void => { | ||
beforeAll(async (): Promise<void> => { | ||
deps = initIocContainer({ | ||
...Config, | ||
adminApiSecret: 'test-secret' | ||
}) | ||
appContainer = await createTestApp(deps) | ||
redis = await deps.use('redis') | ||
}) | ||
|
||
afterEach(async (): Promise<void> => { | ||
jest.useRealTimers() | ||
await redis.flushall() | ||
}) | ||
|
||
afterAll(async (): Promise<void> => { | ||
await redis.quit() | ||
await appContainer.shutdown() | ||
}) | ||
|
||
test('Can verify a signature', async (): Promise<void> => { | ||
const requestBody = { test: 'value' } | ||
const signature = generateApiSignature( | ||
'test-secret', | ||
Config.adminApiSignatureVersion, | ||
requestBody | ||
) | ||
const ctx = createContext<AppContext>( | ||
{ | ||
headers: { | ||
Accept: 'application/json', | ||
signature | ||
}, | ||
url: '/graphql' | ||
}, | ||
{}, | ||
appContainer.container | ||
) | ||
ctx.request.body = requestBody | ||
|
||
const verified = await verifyApiSignature(ctx, { | ||
...Config, | ||
adminApiSecret: 'test-secret' | ||
}) | ||
expect(verified).toBe(true) | ||
}) | ||
|
||
test('verification fails if header is not present', async (): Promise<void> => { | ||
const requestBody = { test: 'value' } | ||
const ctx = createContext<AppContext>( | ||
{ | ||
headers: { | ||
Accept: 'application/json' | ||
}, | ||
url: '/graphql' | ||
}, | ||
{}, | ||
appContainer.container | ||
) | ||
ctx.request.body = requestBody | ||
|
||
const verified = await verifyApiSignature(ctx, { | ||
...Config, | ||
adminApiSecret: 'test-secret' | ||
}) | ||
expect(verified).toBe(false) | ||
}) | ||
|
||
test('Cannot verify signature that is too old', async (): Promise<void> => { | ||
const requestBody = { test: 'value' } | ||
const signature = generateApiSignature( | ||
'test-secret', | ||
Config.adminApiSignatureVersion, | ||
requestBody | ||
) | ||
|
||
const timestamp = signature.split(', ')[0].split('=')[1] | ||
const now = new Date((Number(timestamp) + 60) * 1000) | ||
jest.useFakeTimers({ now }) | ||
const ctx = createContext<AppContext>( | ||
{ | ||
headers: { | ||
Accept: 'application/json', | ||
signature | ||
}, | ||
url: '/graphql' | ||
}, | ||
{}, | ||
appContainer.container | ||
) | ||
ctx.request.body = requestBody | ||
|
||
const verified = await verifyApiSignature(ctx, { | ||
...Config, | ||
adminApiSecret: 'test-secret' | ||
}) | ||
expect(verified).toBe(false) | ||
}) | ||
|
||
test('Cannot verify signature that has already been processed', async (): Promise<void> => { | ||
const requestBody = { test: 'value' } | ||
const signature = generateApiSignature( | ||
'test-secret', | ||
Config.adminApiSignatureVersion, | ||
requestBody | ||
) | ||
const key = `signature:${signature}` | ||
const op = redis.multi() | ||
op.set(key, signature) | ||
op.expire(key, Config.adminApiSignatureTtl * 1000) | ||
await op.exec() | ||
const ctx = createContext<AppContext>( | ||
{ | ||
headers: { | ||
Accept: 'application/json', | ||
signature | ||
}, | ||
url: '/graphql' | ||
}, | ||
{}, | ||
appContainer.container | ||
) | ||
ctx.request.body = requestBody | ||
|
||
const verified = await verifyApiSignature(ctx, { | ||
...Config, | ||
adminApiSecret: 'test-secret' | ||
}) | ||
expect(verified).toBe(false) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.