···169 Error "invalid authorization header" )
170 | None ->
171 Error "missing authorization header"
172+173+ let is_service_auth_token req =
174+ match parse_header req "Bearer" with
175+ | Error _ ->
176+ false
177+ | Ok token -> (
178+ match Jwt.decode_jwt token with
179+ | Error _ ->
180+ false
181+ | Ok (_header, payload) -> (
182+ match Yojson.Safe.Util.member "lxm" payload with
183+ | `Null ->
184+ false
185+ | _ ->
186+ true ) )
187 end
188189 let parse_basic req =
···369 | Error _ ->
370 Lwt.return_error @@ Errors.auth_required "invalid authorization header"
371372+ let user_service_auth : verifier =
373+ let check_actor_status did db =
374+ match%lwt Data_store.get_actor_by_identifier did db with
375+ | Some {deactivated_at= None; _} ->
376+ Lwt.return_ok (Access {did})
377+ | Some {deactivated_at= Some _; _} ->
378+ Lwt.return_error
379+ @@ Errors.auth_required ~name:"AccountDeactivated"
380+ "account is deactivated"
381+ | None ->
382+ Lwt.return_error @@ Errors.auth_required "invalid credentials"
383+ in
384+ let verify_with_key token pubkey_multibase did db =
385+ let pubkey =
386+ Kleidos.parse_multikey_str pubkey_multibase
387+ in
388+ match Jwt.verify_jwt token ~pubkey with
389+ | Ok _ ->
390+ check_actor_status did db
391+ | Error e ->
392+ Dream.debug (fun log -> log "service jwt verification failed: %s" e) ;
393+ Lwt.return_error
394+ @@ Errors.auth_required "jwt signature does not match jwt issuer"
395+ in
396+ fun {req; db} ->
397+ match parse_bearer req with
398+ | Error e ->
399+ Lwt.return_error @@ Errors.auth_required e
400+ | Ok token -> (
401+ match Jwt.decode_jwt token with
402+ | Error e ->
403+ Lwt.return_error @@ Errors.auth_required e
404+ | Ok (_header, payload) -> (
405+ try
406+ let open Yojson.Safe.Util in
407+ let iss = payload |> member "iss" |> to_string in
408+ let aud = payload |> member "aud" |> to_string in
409+ let exp = payload |> member "exp" |> to_int in
410+ let lxm = payload |> member "lxm" |> to_string_option in
411+ let now = int_of_float (Unix.gettimeofday ()) in
412+ if exp < now then
413+ Lwt.return_error @@ Errors.auth_required "jwt expired"
414+ else if aud <> Env.did then
415+ Lwt.return_error
416+ @@ Errors.auth_required "jwt audience does not match service did"
417+ else
418+ let nsid =
419+ (Dream.path [@warning "-3"]) req |> List.rev |> List.hd
420+ in
421+ ( match lxm with
422+ | Some l when l <> nsid && l <> "*" ->
423+ Lwt.return_error
424+ @@ Errors.auth_required
425+ ("jwt lxm " ^ l ^ " does not match " ^ nsid)
426+ | _ ->
427+ let did =
428+ match String.split_on_char '#' iss with
429+ | did :: _ ->
430+ did
431+ | [] ->
432+ iss
433+ in
434+ ( match%lwt Id_resolver.Did.resolve did with
435+ | Error e ->
436+ Dream.debug (fun log ->
437+ log "failed to resolve did %s: %s" did e ) ;
438+ Lwt.return_error
439+ @@ Errors.auth_required "could not resolve issuer did"
440+ | Ok did_doc -> (
441+ match
442+ Id_resolver.Did.Document.get_verification_key did_doc
443+ "#atproto"
444+ with
445+ | None ->
446+ Lwt.return_error
447+ @@ Errors.auth_required
448+ "missing or bad key in issuer did doc"
449+ | Some pubkey_multibase -> (
450+ match%lwt verify_with_key token pubkey_multibase did db with
451+ | Ok creds ->
452+ Lwt.return_ok creds
453+ | Error _ ->
454+ (* try again, skipping cache in case of key rotation *)
455+ ( match%lwt
456+ Id_resolver.Did.resolve ~skip_cache:true did
457+ with
458+ | Error _ ->
459+ Lwt.return_error
460+ @@ Errors.auth_required
461+ "jwt signature does not match jwt issuer"
462+ | Ok fresh_doc -> (
463+ match
464+ Id_resolver.Did.Document.get_verification_key
465+ fresh_doc "#atproto"
466+ with
467+ | None ->
468+ Lwt.return_error
469+ @@ Errors.auth_required
470+ "jwt signature does not match jwt issuer"
471+ | Some fresh_pubkey_multibase
472+ when fresh_pubkey_multibase = pubkey_multibase ->
473+ Lwt.return_error
474+ @@ Errors.auth_required
475+ "jwt signature does not match jwt issuer"
476+ | Some fresh_pubkey_multibase ->
477+ verify_with_key token fresh_pubkey_multibase did
478+ db ) ) ) ) ) )
479+ with _ ->
480+ Lwt.return_error @@ Errors.auth_required "malformed service jwt" ) )
481+482 let authorization : verifier =
483 fun ctx ->
484 match
···488 | Some ("Basic" :: _) ->
489 admin ctx
490 | Some ("Bearer" :: _) ->
491+ (* Check if it's a service auth token (has lxm claim) *)
492+ if is_service_auth_token ctx.req then user_service_auth ctx
493+ else bearer ctx
494 | Some ("DPoP" :: _) ->
495 oauth ctx
496 | _ -> (
+12
pegasus/lib/id_resolver.ml
···150 (fun (s : service) ->
151 if s.id = fragment then Some (get_service_endpoint s) else None )
152 services
000000000000153 end
154155 type document = Document.t
···150 (fun (s : service) ->
151 if s.id = fragment then Some (get_service_endpoint s) else None )
152 services
153+154+ let get_verification_key t fragment =
155+ match t.verification_method with
156+ | None ->
157+ None
158+ | Some methods ->
159+ List.find_map
160+ (fun (vm : verification_method) ->
161+ if vm.id = fragment || vm.id = t.id ^ fragment then
162+ vm.public_key_multibase
163+ else None )
164+ methods
165 end
166167 type document = Document.t