@@ -18,6 +18,10 @@ interface TestSigner {
1818 rawKey : Uint8Array ;
1919}
2020
21+ interface AuthOptions {
22+ authToken ?: string ;
23+ }
24+
2125function 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
116122async 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
158189function 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