···2233let handler =
44 Xrpc.handler ~auth:Authorization (fun {req; auth; db; _} ->
55+ Auth.assert_identity_scope auth ~attr:Oauth.Scopes.Handle ;
56 let did = Auth.get_authed_did_exn auth in
67 let%lwt body = Dream.body req in
78 let handle =
···1314 in
1415 match Util.validate_handle handle with
1516 | Error e ->
1616- Errors.invalid_request ~name:"InvalidHandle" e
1717+ Errors.invalid_request ~name:"InvalidHandle" e
1718 | Ok () -> (
1819 match%lwt Data_store.get_actor_by_identifier handle db with
1920 | Some _ ->
···2121 Xrpc.handler ~auth:Authorization (fun ctx ->
2222 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in
2323 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in
2424+ Auth.assert_repo_scope ctx.auth ~collection:input.collection
2525+ ~action:Oauth.Scopes.Create ;
2426 let%lwt repo = Repository.load did in
2527 let write : Repository.repo_write =
2628 match input.swap_record with
+2
pegasus/lib/api/repo/deleteRecord.ml
···1313let handler =
1414 Xrpc.handler ~auth:Authorization (fun ctx ->
1515 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in
1616+ Auth.assert_repo_scope ctx.auth ~collection:input.collection
1717+ ~action:Oauth.Scopes.Delete ;
1618 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in
1719 let%lwt repo = Repository.load did in
1820 let write : Repository.repo_write =
+5
pegasus/lib/api/repo/putRecord.ml
···2020let handler =
2121 Xrpc.handler ~auth:Authorization (fun ctx ->
2222 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in
2323+ (* assert create and update because we don't know which will end up happening *)
2424+ Auth.assert_repo_scope ctx.auth ~collection:input.collection
2525+ ~action:Oauth.Scopes.Create ;
2626+ Auth.assert_repo_scope ctx.auth ~collection:input.collection
2727+ ~action:Oauth.Scopes.Update ;
2328 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in
2429 let%lwt repo = Repository.load did in
2530 let write : Repository.repo_write =
+1
pegasus/lib/api/repo/uploadBlob.ml
···77 Option.value ~default:"application/octet-stream"
88 (Dream.header ctx.req "Content-Type")
99 in
1010+ Auth.assert_blob_scope ctx.auth ~mime:mime_type ;
1011 let%lwt data = Dream.body ctx.req |> Lwt.map Bytes.of_string in
1112 let size = Int64.of_int @@ Bytes.length data in
1213 let cid = Cid.create Raw data in
+4-1
pegasus/lib/api/server/getServiceAuth.ml
···77 match (Dream.query req "aud", Dream.query req "lxm") with
88 | Some aud, Some lxm ->
99 (aud, lxm)
1010+ | Some aud, None ->
1111+ (aud, "*")
1012 | _ ->
1111- Errors.invalid_request "missing aud or lxm"
1313+ Errors.invalid_request "missing aud"
1214 in
1515+ Auth.assert_rpc_scope auth ~lxm ~aud ;
1316 let%lwt signing_multikey =
1417 match%lwt Data_store.get_actor_by_identifier did db with
1518 | Some {signing_key; _} ->
+9
pegasus/lib/api/server/getSession.ml
···44 Xrpc.handler ~auth:Authorization (fun {db; auth; _} ->
55 let did = Auth.get_authed_did_exn auth in
66 let%lwt session = Auth.get_session_info did db in
77+ (* strip email fields if oauth token doesn't have email read permission *)
88+ let session =
99+ if Auth.allows_email_read auth then session
1010+ else
1111+ { session with
1212+ email= ""
1313+ ; email_confirmed= false
1414+ ; email_auth_factor= false }
1515+ in
716 Dream.json @@ Yojson.Safe.to_string @@ Auth.session_info_to_yojson session )
+71-2
pegasus/lib/auth.ml
···1515 | Admin
1616 | Access of {did: string}
1717 | Refresh of {did: string; jti: string}
1818- | OAuth of {did: string; proof: Oauth.Dpop.proof}
1818+ | OAuth of {did: string; proof: Oauth.Dpop.proof; scopes: Oauth.Scopes.t}
1919 | DPoP of {proof: Oauth.Dpop.proof}
20202121let verify_bearer_jwt t token expected_scope =
···5050 true
5151 | _ ->
5252 false
5353+5454+let get_scopes = function OAuth {scopes; _} -> Some scopes | _ -> None
5555+5656+let is_oauth = function OAuth _ -> true | _ -> false
5757+5858+let assert_repo_scope credentials ~collection ~action =
5959+ match credentials with
6060+ | OAuth {scopes; _} ->
6161+ if not (Oauth.Scopes.Transition.allows_repo scopes {collection; action})
6262+ then
6363+ Errors.auth_required ~name:"InsufficientScope"
6464+ ( "scope doesn't allow "
6565+ ^ Oauth.Scopes.show_repo_action action
6666+ ^ " on " ^ collection )
6767+ | _ ->
6868+ ()
6969+7070+let assert_blob_scope credentials ~mime =
7171+ match credentials with
7272+ | OAuth {scopes; _} ->
7373+ if not (Oauth.Scopes.Transition.allows_blob scopes {mime}) then
7474+ Errors.auth_required ~name:"InsufficientScope"
7575+ ("scope doesn't allow blob upload for " ^ mime)
7676+ | _ ->
7777+ ()
7878+7979+let assert_identity_scope credentials ~attr =
8080+ match credentials with
8181+ | OAuth {scopes; _} ->
8282+ if not (Oauth.Scopes.allows_identity scopes {attr}) then
8383+ Errors.auth_required ~name:"InsufficientScope"
8484+ "scope doesn't allow identity operations"
8585+ | _ ->
8686+ ()
8787+8888+let assert_account_scope credentials ~attr ~action =
8989+ match credentials with
9090+ | OAuth {scopes; _} ->
9191+ if not (Oauth.Scopes.Transition.allows_account scopes {attr; action}) then
9292+ Errors.auth_required ~name:"InsufficientScope"
9393+ "scope doesn't allow account operations"
9494+ | _ ->
9595+ ()
9696+9797+let allows_email_read credentials =
9898+ match credentials with
9999+ | OAuth {scopes; _} ->
100100+ Oauth.Scopes.Transition.allows_account scopes
101101+ {attr= Oauth.Scopes.Email; action= Oauth.Scopes.Read}
102102+ | _ ->
103103+ true
104104+105105+let assert_rpc_scope credentials ~lxm ~aud =
106106+ match credentials with
107107+ | OAuth {scopes; _} ->
108108+ if not (Oauth.Scopes.Transition.allows_rpc scopes {lxm; aud}) then
109109+ Errors.auth_required ~name:"InsufficientScope"
110110+ ("scope doesn't allow rpc call to " ^ lxm)
111111+ | _ ->
112112+ ()
5311354114let get_authed_did_exn = function
55115 | Access {did} | OAuth {did; _} ->
···211271 let jkt_claim =
212272 claims |> member "cnf" |> member "jkt" |> to_string
213273 in
274274+ let scope_str =
275275+ claims |> member "scope" |> to_string_option
276276+ |> Option.value ~default:""
277277+ in
278278+ let scopes = Oauth.Scopes.of_string scope_str in
214279 let now = int_of_float (Unix.gettimeofday ()) in
215280 if jkt_claim <> proof.jkt then
216281 Lwt.return_error @@ Errors.auth_required "dpop key mismatch"
217282 else if exp < now then
218283 Lwt.return_error @@ Errors.auth_required "token expired"
284284+ else if not (Oauth.Scopes.has_atproto scopes) then
285285+ Lwt.return_error
286286+ @@ Errors.auth_required ~name:"InvalidToken"
287287+ "oauth token missing 'atproto' scope"
219288 else
220289 let%lwt session =
221290 try%lwt
···227296 in
228297 match session with
229298 | Ok {active= Some true; _} ->
230230- Lwt.return_ok (OAuth {did; proof})
299299+ Lwt.return_ok (OAuth {did; proof; scopes})
231300 | Ok _ ->
232301 Lwt.return_error
233302 @@ Errors.auth_required ~name:"AccountDeactivated"