@@ -154,6 +154,7 @@ test("quote handler verifies compatible instruments without fetching local targe
154154 "wrong target" ,
155155 "wrong author" ,
156156 "cross-origin instrument" ,
157+ "missing author" ,
157158 ] ) {
158159 const requester = {
159160 id : "requester" ,
@@ -231,9 +232,11 @@ test("quote handler verifies compatible instruments without fetching local targe
231232 : "https://remote.example/quote" ,
232233 ) ,
233234 attributions :
234- variant === "multiple authors"
235- ? [ other , new URL ( requester . iri ) ]
236- : [ variant === "wrong author" ? other : new URL ( requester . iri ) ] ,
235+ variant === "missing author"
236+ ? [ ]
237+ : variant === "multiple authors"
238+ ? [ other , new URL ( requester . iri ) ]
239+ : [ variant === "wrong author" ? other : new URL ( requester . iri ) ] ,
237240 quote :
238241 variant === "quoteUrl"
239242 ? null
@@ -254,18 +257,40 @@ test("quote handler verifies compatible instruments without fetching local targe
254257 } ,
255258 } ) ;
256259 }
260+ const request = new QuoteRequest ( {
261+ id : new URL ( "https://remote.example/request" ) ,
262+ actor : new URL ( requester . iri ) ,
263+ object : new URL ( target . iri ) ,
264+ instrument :
265+ variant === "redirected instrument"
266+ ? new URL ( "https://remote.example/alias" )
267+ : instrument ,
268+ } ) ;
257269 await onQuoteRequested (
258270 raw as unknown as InboxContext < ContextData > ,
259- new QuoteRequest ( {
260- id : new URL ( "https://remote.example/request" ) ,
261- actor : new URL ( requester . iri ) ,
262- object : new URL ( target . iri ) ,
263- instrument :
264- variant === "redirected instrument"
265- ? new URL ( "https://remote.example/alias" )
266- : instrument ,
267- } ) ,
271+ request ,
272+ ) ;
273+ assert . equal ( request . id ?. href , "https://remote.example/request" ) ;
274+ assert . equal ( request . actorId ?. href , requester . iri ) ;
275+ assert . equal ( request . objectId ?. href , target . iri ) ;
276+ assert . equal (
277+ instrument . quoteUrl ?. href ,
278+ variant === "wrong target" ? other . href : target . iri ,
268279 ) ;
280+ assert . equal (
281+ instrument . quoteId ?. href ,
282+ variant === "quoteUrl"
283+ ? undefined
284+ : variant === "conflicting quote" || variant === "wrong target"
285+ ? other . href
286+ : target . iri ,
287+ ) ;
288+ if ( variant === "multiple authors" ) {
289+ assert . deepEqual (
290+ instrument . attributionIds . map ( ( id ) => id . href ) ,
291+ [ other . href , requester . iri ] ,
292+ ) ;
293+ }
269294 const allowed = [
270295 "quoteUrl" ,
271296 "conflicting quote" ,
@@ -284,3 +309,103 @@ test("quote handler verifies compatible instruments without fetching local targe
284309 }
285310 }
286311} ) ;
312+
313+ test ( "stored authorization helper requires DB evidence and exact bindings for aliases" , async ( ) => {
314+ const ctx = createContext ( ) ;
315+ const id = new URL ( "https://alias.example/authorization" ) ;
316+ const author = new URL ( "https://author.example/actor" ) ;
317+ const quote = new URL ( "https://quoter.example/quote" ) ;
318+ const target = new URL ( "https://author.example/post" ) ;
319+ const other = new URL ( "https://other.example/object" ) ;
320+ for ( const variant of [
321+ "valid" ,
322+ "no evidence" ,
323+ "idless" ,
324+ "wrong ID" ,
325+ "wrong quote" ,
326+ "wrong target" ,
327+ "wrong author" ,
328+ ] ) {
329+ const authorization = new QuoteAuthorization ( {
330+ id : variant === "idless" ? null : variant === "wrong ID" ? other : id ,
331+ attribution : variant === "wrong author" ? other : author ,
332+ interactingObject : variant === "wrong quote" ? other : quote ,
333+ interactionTarget : variant === "wrong target" ? other : target ,
334+ } ) ;
335+ assert . equal (
336+ await federationServices . verifyStoredQuoteAuthorization (
337+ ctx ,
338+ authorization ,
339+ {
340+ authorizationId : id ,
341+ interactingObject : quote ,
342+ interactionTarget : target ,
343+ attributedTo : author ,
344+ authentic : variant !== "no evidence" ,
345+ } ,
346+ ) ,
347+ variant === "valid" ,
348+ variant ,
349+ ) ;
350+ }
351+ } ) ;
352+
353+ test ( "authorization verification separates document/context loaders with a default context fallback" , async ( ) => {
354+ const ctx = createContext ( ) ;
355+ const id = new URL ( "https://author.example/authorization" ) ;
356+ const author = new URL ( "https://author.example/actor" ) ;
357+ const quote = new URL ( "https://quoter.example/quote" ) ;
358+ const target = new URL ( "https://author.example/post" ) ;
359+ const authorization = new QuoteAuthorization ( {
360+ id,
361+ attribution : author ,
362+ interactingObject : quote ,
363+ interactionTarget : target ,
364+ } ) ;
365+ const document = await authorization . toJsonLd ( ) ;
366+ assert . ok (
367+ document != null && typeof document === "object" && "@context" in document ,
368+ ) ;
369+ for ( const separate of [ false , true ] ) {
370+ const contextUrl = "https://contexts.example/custom" ;
371+ const contexts : string [ ] = [ ] ;
372+ const documents : string [ ] = [ ] ;
373+ assert . equal (
374+ await federationServices . verifyQuoteAuthorization (
375+ ctx ,
376+ id ,
377+ quote ,
378+ target ,
379+ author ,
380+ async ( url ) => {
381+ documents . push ( url ) ;
382+ assert . equal ( url , id . href ) ;
383+ return {
384+ document : separate
385+ ? { ...document , "@context" : contextUrl }
386+ : document ,
387+ documentUrl : url ,
388+ contextUrl : null ,
389+ } ;
390+ } ,
391+ separate
392+ ? async ( url ) => {
393+ contexts . push ( url ) ;
394+ if ( url === contextUrl ) {
395+ return {
396+ document : { "@context" : document [ "@context" ] } ,
397+ documentUrl : url ,
398+ contextUrl : null ,
399+ } ;
400+ }
401+ return ctx . contextLoader ( url ) ;
402+ }
403+ : undefined ,
404+ ) ,
405+ true ,
406+ ) ;
407+ assert . deepEqual ( documents , [ id . href ] ) ;
408+ if ( separate ) assert . ok ( contexts . includes ( contextUrl ) ) ;
409+ else assert . deepEqual ( contexts , [ ] ) ;
410+ }
411+ } ) ;
0 commit comments