Skip to content

Commit 0ed796f

Browse files
committed
test(e2e): add auth token permission matrix for signature/github verify
1 parent 92b92ac commit 0ed796f

1 file changed

Lines changed: 134 additions & 7 deletions

File tree

tests/e2e_github_signature_permissions_test.ts

Lines changed: 134 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ interface TestSigner {
1818
rawKey: Uint8Array;
1919
}
2020

21+
interface AuthOptions {
22+
authToken?: string;
23+
}
24+
2125
function envFrom(entries: Record<string, string | undefined>): (key: string) => string | undefined {
2226
return (key: string) => entries[key];
2327
}
@@ -79,6 +83,7 @@ async function signedPublish(baseUrl: string, args: {
7983
topic?: string;
8084
room?: string;
8185
payload: unknown;
86+
authToken?: string;
8287
}): Promise<Response> {
8388
const topic = args.topic ?? 'notify';
8489
const room = args.room ?? 'main';
@@ -104,6 +109,7 @@ async function signedPublish(baseUrl: string, args: {
104109
method: 'POST',
105110
headers: {
106111
'content-type': 'application/json',
112+
...(args.authToken ? { authorization: `Bearer ${args.authToken}` } : {}),
107113
'x-relay-public-key': args.signer.publicKey,
108114
'x-relay-signature': signature,
109115
'x-relay-timestamp': String(ts),
@@ -115,7 +121,14 @@ async function signedPublish(baseUrl: string, args: {
115121

116122
async function unsignedPublish(
117123
baseUrl: string,
118-
args: { sender: string; id: string; payload: unknown; topic?: string; room?: string },
124+
args: {
125+
sender: string;
126+
id: string;
127+
payload: unknown;
128+
topic?: string;
129+
room?: string;
130+
authToken?: string;
131+
},
119132
): Promise<Response> {
120133
const topic = args.topic ?? 'notify';
121134
const room = args.room ?? 'main';
@@ -126,28 +139,46 @@ async function unsignedPublish(
126139
url.searchParams.set('id', args.id);
127140
return fetch(url, {
128141
method: 'POST',
129-
headers: { 'content-type': 'application/json' },
142+
headers: {
143+
'content-type': 'application/json',
144+
...(args.authToken ? { authorization: `Bearer ${args.authToken}` } : {}),
145+
},
130146
body: JSON.stringify(args.payload),
131147
});
132148
}
133149

134-
async function verifyGitHub(baseUrl: string, sender: string): Promise<Response> {
150+
async function verifyGitHub(
151+
baseUrl: string,
152+
sender: string,
153+
auth?: AuthOptions,
154+
): Promise<Response> {
135155
return fetch(`${baseUrl}/api/v1/key/verify-github`, {
136156
method: 'POST',
137-
headers: { 'content-type': 'application/json' },
157+
headers: {
158+
'content-type': 'application/json',
159+
...(auth?.authToken ? { authorization: `Bearer ${auth.authToken}` } : {}),
160+
},
138161
body: JSON.stringify({ sender, github_username: sender }),
139162
});
140163
}
141164

142-
async function registerServe(baseUrl: string, sender: string, repo: string): Promise<{
165+
async function registerServe(
166+
baseUrl: string,
167+
sender: string,
168+
repo: string,
169+
auth?: AuthOptions,
170+
): Promise<{
143171
status: number;
144172
body: Record<string, unknown>;
145173
}> {
146174
const response = await fetch(
147175
`${baseUrl}/api/v1/serve/register?sender=${encodeURIComponent(sender)}&repo=${
148176
encodeURIComponent(repo)
149177
}`,
150-
{ method: 'POST' },
178+
{
179+
method: 'POST',
180+
headers: auth?.authToken ? { authorization: `Bearer ${auth.authToken}` } : undefined,
181+
},
151182
);
152183
return {
153184
status: response.status,
@@ -157,6 +188,7 @@ async function registerServe(baseUrl: string, sender: string, repo: string): Pro
157188

158189
function createTestRelayServer(args: {
159190
requireSignatureFlag: 'true' | 'false';
191+
authToken?: string;
160192
fetchFn?: typeof globalThis.fetch;
161193
}): {
162194
baseUrl: string;
@@ -165,6 +197,7 @@ function createTestRelayServer(args: {
165197
const runtimeConfig = parseRelayRuntimeConfigFromEnv(
166198
envFrom({
167199
RELAY_REQUIRE_SIGNATURE: args.requireSignatureFlag,
200+
BIT_RELAY_AUTH_TOKEN: args.authToken,
168201
}),
169202
);
170203
const service = createMemoryRelayService({
@@ -232,7 +265,11 @@ function createTestRelayServer(args: {
232265
let sessionId = generateSessionId();
233266
if (sender && repo) {
234267
const keyInfoRes = await service.fetch(
235-
new Request(`http://relay.local/api/v1/key/info?sender=${encodeURIComponent(sender)}`),
268+
new Request(`http://relay.local/api/v1/key/info?sender=${encodeURIComponent(sender)}`, {
269+
headers: runtimeConfig.relay.authToken
270+
? { authorization: `Bearer ${runtimeConfig.relay.authToken}` }
271+
: undefined,
272+
}),
236273
);
237274
const keyInfo = await keyInfoRes.json() as Record<string, unknown>;
238275
const keyRecord = keyInfo.key as Record<string, unknown> | undefined;
@@ -404,3 +441,93 @@ Deno.test('e2e: RELAY_REQUIRE_SIGNATURE=false still requires GitHub verification
404441
await relay.shutdown();
405442
}
406443
});
444+
445+
Deno.test('e2e: BIT_RELAY_AUTH_TOKEN gates publish/verify while named session uses verified state', async () => {
446+
const authToken = 'relay-secret-token';
447+
const aliceSigner = await createSignerWithRawKey();
448+
const relay = createTestRelayServer({
449+
requireSignatureFlag: 'true',
450+
authToken,
451+
fetchFn: createMockGitHubFetchByUser({ alice: [aliceSigner.rawKey] }),
452+
});
453+
454+
try {
455+
const signedWithoutAuth = await signedPublish(relay.baseUrl, {
456+
signer: aliceSigner,
457+
sender: 'alice',
458+
id: 'auth-signed-ng',
459+
payload: { body: 'auth required' },
460+
});
461+
assertEquals(signedWithoutAuth.status, 401);
462+
await signedWithoutAuth.json();
463+
464+
const signedWithAuth = await signedPublish(relay.baseUrl, {
465+
signer: aliceSigner,
466+
sender: 'alice',
467+
id: 'auth-signed-ok',
468+
payload: { body: 'authorized publish' },
469+
authToken,
470+
});
471+
assertEquals(signedWithAuth.status, 200);
472+
await signedWithAuth.json();
473+
474+
const verifyWithoutAuth = await verifyGitHub(relay.baseUrl, 'alice');
475+
assertEquals(verifyWithoutAuth.status, 401);
476+
await verifyWithoutAuth.json();
477+
478+
const verifyWithWrongAuth = await verifyGitHub(relay.baseUrl, 'alice', {
479+
authToken: 'wrong-token',
480+
});
481+
assertEquals(verifyWithWrongAuth.status, 401);
482+
await verifyWithWrongAuth.json();
483+
484+
const beforeVerify = await registerServe(relay.baseUrl, 'alice', 'bit-relay');
485+
assertEquals(beforeVerify.status, 200);
486+
assertMatch(String(beforeVerify.body.session_id), RANDOM_SESSION_PATTERN);
487+
488+
const verifyWithAuth = await verifyGitHub(relay.baseUrl, 'alice', { authToken });
489+
assertEquals(verifyWithAuth.status, 200);
490+
const verifyWithAuthBody = await verifyWithAuth.json() as Record<string, unknown>;
491+
assertEquals(verifyWithAuthBody.verified, true);
492+
493+
const afterVerifyNoAuth = await registerServe(relay.baseUrl, 'alice', 'bit-relay');
494+
assertEquals(afterVerifyNoAuth.status, 200);
495+
assertEquals(afterVerifyNoAuth.body.session_id, 'alice/bit-relay');
496+
497+
const bobRegister = await registerServe(relay.baseUrl, 'bob', 'bit-relay');
498+
assertEquals(bobRegister.status, 200);
499+
assertMatch(String(bobRegister.body.session_id), RANDOM_SESSION_PATTERN);
500+
} finally {
501+
await relay.shutdown();
502+
}
503+
});
504+
505+
Deno.test('e2e: BIT_RELAY_AUTH_TOKEN with RELAY_REQUIRE_SIGNATURE=false still blocks unauthorized publish', async () => {
506+
const authToken = 'relay-secret-token';
507+
const relay = createTestRelayServer({
508+
requireSignatureFlag: 'false',
509+
authToken,
510+
fetchFn: createMockGitHubFetchByUser({}),
511+
});
512+
513+
try {
514+
const unsignedWithoutAuth = await unsignedPublish(relay.baseUrl, {
515+
sender: 'bob',
516+
id: 'auth-unsigned-ng',
517+
payload: { body: 'auth required even without signature requirement' },
518+
});
519+
assertEquals(unsignedWithoutAuth.status, 401);
520+
await unsignedWithoutAuth.json();
521+
522+
const unsignedWithAuth = await unsignedPublish(relay.baseUrl, {
523+
sender: 'bob',
524+
id: 'auth-unsigned-ok',
525+
payload: { body: 'authorized unsigned publish' },
526+
authToken,
527+
});
528+
assertEquals(unsignedWithAuth.status, 200);
529+
await unsignedWithAuth.json();
530+
} finally {
531+
await relay.shutdown();
532+
}
533+
});

0 commit comments

Comments
 (0)