···169169 Error "invalid authorization header" )
170170 | None ->
171171 Error "missing authorization header"
172172+173173+ let is_service_auth_token req =
174174+ match parse_header req "Bearer" with
175175+ | Error _ ->
176176+ false
177177+ | Ok token -> (
178178+ match Jwt.decode_jwt token with
179179+ | Error _ ->
180180+ false
181181+ | Ok (_header, payload) -> (
182182+ match Yojson.Safe.Util.member "lxm" payload with
183183+ | `Null ->
184184+ false
185185+ | _ ->
186186+ true ) )
172187 end
173188174189 let parse_basic req =
···354369 | Error _ ->
355370 Lwt.return_error @@ Errors.auth_required "invalid authorization header"
356371372372+ let user_service_auth : verifier =
373373+ let check_actor_status did db =
374374+ match%lwt Data_store.get_actor_by_identifier did db with
375375+ | Some {deactivated_at= None; _} ->
376376+ Lwt.return_ok (Access {did})
377377+ | Some {deactivated_at= Some _; _} ->
378378+ Lwt.return_error
379379+ @@ Errors.auth_required ~name:"AccountDeactivated"
380380+ "account is deactivated"
381381+ | None ->
382382+ Lwt.return_error @@ Errors.auth_required "invalid credentials"
383383+ in
384384+ let verify_with_key token pubkey_multibase did db =
385385+ let pubkey =
386386+ Kleidos.parse_multikey_str pubkey_multibase
387387+ in
388388+ match Jwt.verify_jwt token ~pubkey with
389389+ | Ok _ ->
390390+ check_actor_status did db
391391+ | Error e ->
392392+ Dream.debug (fun log -> log "service jwt verification failed: %s" e) ;
393393+ Lwt.return_error
394394+ @@ Errors.auth_required "jwt signature does not match jwt issuer"
395395+ in
396396+ fun {req; db} ->
397397+ match parse_bearer req with
398398+ | Error e ->
399399+ Lwt.return_error @@ Errors.auth_required e
400400+ | Ok token -> (
401401+ match Jwt.decode_jwt token with
402402+ | Error e ->
403403+ Lwt.return_error @@ Errors.auth_required e
404404+ | Ok (_header, payload) -> (
405405+ try
406406+ let open Yojson.Safe.Util in
407407+ let iss = payload |> member "iss" |> to_string in
408408+ let aud = payload |> member "aud" |> to_string in
409409+ let exp = payload |> member "exp" |> to_int in
410410+ let lxm = payload |> member "lxm" |> to_string_option in
411411+ let now = int_of_float (Unix.gettimeofday ()) in
412412+ if exp < now then
413413+ Lwt.return_error @@ Errors.auth_required "jwt expired"
414414+ else if aud <> Env.did then
415415+ Lwt.return_error
416416+ @@ Errors.auth_required "jwt audience does not match service did"
417417+ else
418418+ let nsid =
419419+ (Dream.path [@warning "-3"]) req |> List.rev |> List.hd
420420+ in
421421+ ( match lxm with
422422+ | Some l when l <> nsid && l <> "*" ->
423423+ Lwt.return_error
424424+ @@ Errors.auth_required
425425+ ("jwt lxm " ^ l ^ " does not match " ^ nsid)
426426+ | _ ->
427427+ let did =
428428+ match String.split_on_char '#' iss with
429429+ | did :: _ ->
430430+ did
431431+ | [] ->
432432+ iss
433433+ in
434434+ ( match%lwt Id_resolver.Did.resolve did with
435435+ | Error e ->
436436+ Dream.debug (fun log ->
437437+ log "failed to resolve did %s: %s" did e ) ;
438438+ Lwt.return_error
439439+ @@ Errors.auth_required "could not resolve issuer did"
440440+ | Ok did_doc -> (
441441+ match
442442+ Id_resolver.Did.Document.get_verification_key did_doc
443443+ "#atproto"
444444+ with
445445+ | None ->
446446+ Lwt.return_error
447447+ @@ Errors.auth_required
448448+ "missing or bad key in issuer did doc"
449449+ | Some pubkey_multibase -> (
450450+ match%lwt verify_with_key token pubkey_multibase did db with
451451+ | Ok creds ->
452452+ Lwt.return_ok creds
453453+ | Error _ ->
454454+ (* try again, skipping cache in case of key rotation *)
455455+ ( match%lwt
456456+ Id_resolver.Did.resolve ~skip_cache:true did
457457+ with
458458+ | Error _ ->
459459+ Lwt.return_error
460460+ @@ Errors.auth_required
461461+ "jwt signature does not match jwt issuer"
462462+ | Ok fresh_doc -> (
463463+ match
464464+ Id_resolver.Did.Document.get_verification_key
465465+ fresh_doc "#atproto"
466466+ with
467467+ | None ->
468468+ Lwt.return_error
469469+ @@ Errors.auth_required
470470+ "jwt signature does not match jwt issuer"
471471+ | Some fresh_pubkey_multibase
472472+ when fresh_pubkey_multibase = pubkey_multibase ->
473473+ Lwt.return_error
474474+ @@ Errors.auth_required
475475+ "jwt signature does not match jwt issuer"
476476+ | Some fresh_pubkey_multibase ->
477477+ verify_with_key token fresh_pubkey_multibase did
478478+ db ) ) ) ) ) )
479479+ with _ ->
480480+ Lwt.return_error @@ Errors.auth_required "malformed service jwt" ) )
481481+357482 let authorization : verifier =
358483 fun ctx ->
359484 match
···363488 | Some ("Basic" :: _) ->
364489 admin ctx
365490 | Some ("Bearer" :: _) ->
366366- bearer ctx
491491+ (* Check if it's a service auth token (has lxm claim) *)
492492+ if is_service_auth_token ctx.req then user_service_auth ctx
493493+ else bearer ctx
367494 | Some ("DPoP" :: _) ->
368495 oauth ctx
369496 | _ -> (
+12
pegasus/lib/id_resolver.ml
···150150 (fun (s : service) ->
151151 if s.id = fragment then Some (get_service_endpoint s) else None )
152152 services
153153+154154+ let get_verification_key t fragment =
155155+ match t.verification_method with
156156+ | None ->
157157+ None
158158+ | Some methods ->
159159+ List.find_map
160160+ (fun (vm : verification_method) ->
161161+ if vm.id = fragment || vm.id = t.id ^ fragment then
162162+ vm.public_key_multibase
163163+ else None )
164164+ methods
153165 end
154166155167 type document = Document.t