Skip to content

Commit 3b0488a

Browse files
committed
Add an optional auth token to further control API access
Only applies to the HTK server for now, not Mockttp, but that's the most risky attack surface by a mile. No immediate need for this - the CORS & CSRF protections are rock-solid AFAIK, but as the API gets more powerful it would be good to have defence in depth.
1 parent fe2808f commit 3b0488a

File tree

4 files changed

+28
-3
lines changed

4 files changed

+28
-3
lines changed

src/commands/start.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,17 @@ class HttpToolkitServer extends Command {
3333
version: flags.version({char: 'v'}),
3434
help: flags.help({char: 'h'}),
3535

36-
config: flags.string({char: 'c', description: 'path in which to store config files'}),
36+
config: flags.string({char: 'c', description: 'optional path in which to store config files'}),
37+
token: flags.string({char: 't', description: 'optional token to authenticate local server access'}),
3738
}
3839

3940
async run() {
4041
const { flags } = this.parse(HttpToolkitServer);
4142

42-
await runHTK({ configPath: flags.config }).catch(async (error) => {
43+
await runHTK({
44+
configPath: flags.config,
45+
authToken: flags.token
46+
}).catch(async (error) => {
4347
await reportError(error);
4448
throw error;
4549
});

src/config.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface HtkConfig {
22
configPath: string;
3+
authToken?: string;
34
https: {
45
keyPath: string;
56
certPath: string;

src/httptoolkit-server.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import corsGate = require('cors-gate');
55
import { GraphQLServer } from 'graphql-yoga';
66
import { GraphQLScalarType } from 'graphql';
77
import { generateSPKIFingerprint } from 'mockttp';
8+
import { Request, Response } from 'express';
89

910
import { HtkConfig } from './config';
1011
import { reportError, addBreadcrumb } from './error-tracking';
@@ -184,6 +185,23 @@ export class HttpToolkitServer extends events.EventEmitter {
184185
allowSafe: false, // Even for HEAD/GET requests (should be none anyway)
185186
origin: '' // No origin - we accept *no* same-origin requests
186187
}));
188+
189+
if (config.authToken) {
190+
// Optional auth token. This allows us to lock down UI/server communication further
191+
// when started together. The desktop generates a token every run and passes it to both.
192+
this.graphql.use((req: Request, res: Response, next: () => void) => {
193+
const authHeader = req.headers['authorization'] || '';
194+
195+
const tokenMatch = authHeader.match(/Bearer (\S+)/) || [];
196+
const token = tokenMatch[1];
197+
198+
if (token !== config.authToken) {
199+
res.status(403).send('Valid token required');
200+
} else {
201+
next();
202+
}
203+
});
204+
}
187205
}
188206

189207
async start() {

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ function checkCertExpiry(contents: string): void {
6666

6767
export async function runHTK(options: {
6868
configPath?: string
69+
authToken?: string
6970
} = {}) {
7071
const startTime = Date.now();
7172
registerShutdownHandler();
@@ -83,7 +84,7 @@ export async function runHTK(options: {
8384
const certSetupTime = Date.now();
8485
console.log('Certificates setup in', certSetupTime - configCheckTime, 'ms');
8586

86-
// Start a standalone server
87+
// Start a Mockttp standalone server
8788
const standalone = getStandalone({
8889
serverDefaults: {
8990
cors: false,
@@ -103,6 +104,7 @@ export async function runHTK(options: {
103104
// Start a HTK server
104105
const htkServer = new HttpToolkitServer({
105106
configPath,
107+
authToken: options.authToken,
106108
https: httpsConfig
107109
});
108110

0 commit comments

Comments
 (0)