objective categorical abstract machine language personal data server
65
fork

Configure Feed

Select the types of activity you want to include in your feed.

Verify OAuth scopes in user auth

futurGH 3bcc7b36 d00ea876

+119 -4
+2 -1
pegasus/lib/api/identity/updateHandle.ml
··· 2 2 3 3 let handler = 4 4 Xrpc.handler ~auth:Authorization (fun {req; auth; db; _} -> 5 + Auth.assert_identity_scope auth ~attr:Oauth.Scopes.Handle ; 5 6 let did = Auth.get_authed_did_exn auth in 6 7 let%lwt body = Dream.body req in 7 8 let handle = ··· 13 14 in 14 15 match Util.validate_handle handle with 15 16 | Error e -> 16 - Errors.invalid_request ~name:"InvalidHandle" e 17 + Errors.invalid_request ~name:"InvalidHandle" e 17 18 | Ok () -> ( 18 19 match%lwt Data_store.get_actor_by_identifier handle db with 19 20 | Some _ ->
+14
pegasus/lib/api/repo/applyWrites.ml
··· 15 15 Xrpc.handler ~auth:Authorization (fun ctx -> 16 16 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in 17 17 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in 18 + (* check oauth scopes for each write operation *) 19 + List.iter 20 + (fun (write : Repository.repo_write) -> 21 + match write with 22 + | Create {collection; _} -> 23 + Auth.assert_repo_scope ctx.auth ~collection 24 + ~action:Oauth.Scopes.Create 25 + | Update {collection; _} -> 26 + Auth.assert_repo_scope ctx.auth ~collection 27 + ~action:Oauth.Scopes.Update 28 + | Delete {collection; _} -> 29 + Auth.assert_repo_scope ctx.auth ~collection 30 + ~action:Oauth.Scopes.Delete ) 31 + input.writes ; 18 32 let%lwt repo = Repository.load did in 19 33 let%lwt {commit= commit_cid, {rev; _}; results} = 20 34 Repository.apply_writes repo input.writes
+2
pegasus/lib/api/repo/createRecord.ml
··· 21 21 Xrpc.handler ~auth:Authorization (fun ctx -> 22 22 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in 23 23 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in 24 + Auth.assert_repo_scope ctx.auth ~collection:input.collection 25 + ~action:Oauth.Scopes.Create ; 24 26 let%lwt repo = Repository.load did in 25 27 let write : Repository.repo_write = 26 28 match input.swap_record with
+2
pegasus/lib/api/repo/deleteRecord.ml
··· 13 13 let handler = 14 14 Xrpc.handler ~auth:Authorization (fun ctx -> 15 15 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in 16 + Auth.assert_repo_scope ctx.auth ~collection:input.collection 17 + ~action:Oauth.Scopes.Delete ; 16 18 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in 17 19 let%lwt repo = Repository.load did in 18 20 let write : Repository.repo_write =
+5
pegasus/lib/api/repo/putRecord.ml
··· 20 20 let handler = 21 21 Xrpc.handler ~auth:Authorization (fun ctx -> 22 22 let%lwt input = Xrpc.parse_body ctx.req request_of_yojson in 23 + (* assert create and update because we don't know which will end up happening *) 24 + Auth.assert_repo_scope ctx.auth ~collection:input.collection 25 + ~action:Oauth.Scopes.Create ; 26 + Auth.assert_repo_scope ctx.auth ~collection:input.collection 27 + ~action:Oauth.Scopes.Update ; 23 28 let%lwt did = Xrpc.resolve_repo_did_authed ctx input.repo in 24 29 let%lwt repo = Repository.load did in 25 30 let write : Repository.repo_write =
+1
pegasus/lib/api/repo/uploadBlob.ml
··· 7 7 Option.value ~default:"application/octet-stream" 8 8 (Dream.header ctx.req "Content-Type") 9 9 in 10 + Auth.assert_blob_scope ctx.auth ~mime:mime_type ; 10 11 let%lwt data = Dream.body ctx.req |> Lwt.map Bytes.of_string in 11 12 let size = Int64.of_int @@ Bytes.length data in 12 13 let cid = Cid.create Raw data in
+4 -1
pegasus/lib/api/server/getServiceAuth.ml
··· 7 7 match (Dream.query req "aud", Dream.query req "lxm") with 8 8 | Some aud, Some lxm -> 9 9 (aud, lxm) 10 + | Some aud, None -> 11 + (aud, "*") 10 12 | _ -> 11 - Errors.invalid_request "missing aud or lxm" 13 + Errors.invalid_request "missing aud" 12 14 in 15 + Auth.assert_rpc_scope auth ~lxm ~aud ; 13 16 let%lwt signing_multikey = 14 17 match%lwt Data_store.get_actor_by_identifier did db with 15 18 | Some {signing_key; _} ->
+9
pegasus/lib/api/server/getSession.ml
··· 4 4 Xrpc.handler ~auth:Authorization (fun {db; auth; _} -> 5 5 let did = Auth.get_authed_did_exn auth in 6 6 let%lwt session = Auth.get_session_info did db in 7 + (* strip email fields if oauth token doesn't have email read permission *) 8 + let session = 9 + if Auth.allows_email_read auth then session 10 + else 11 + { session with 12 + email= "" 13 + ; email_confirmed= false 14 + ; email_auth_factor= false } 15 + in 7 16 Dream.json @@ Yojson.Safe.to_string @@ Auth.session_info_to_yojson session )
+71 -2
pegasus/lib/auth.ml
··· 15 15 | Admin 16 16 | Access of {did: string} 17 17 | Refresh of {did: string; jti: string} 18 - | OAuth of {did: string; proof: Oauth.Dpop.proof} 18 + | OAuth of {did: string; proof: Oauth.Dpop.proof; scopes: Oauth.Scopes.t} 19 19 | DPoP of {proof: Oauth.Dpop.proof} 20 20 21 21 let verify_bearer_jwt t token expected_scope = ··· 50 50 true 51 51 | _ -> 52 52 false 53 + 54 + let get_scopes = function OAuth {scopes; _} -> Some scopes | _ -> None 55 + 56 + let is_oauth = function OAuth _ -> true | _ -> false 57 + 58 + let assert_repo_scope credentials ~collection ~action = 59 + match credentials with 60 + | OAuth {scopes; _} -> 61 + if not (Oauth.Scopes.Transition.allows_repo scopes {collection; action}) 62 + then 63 + Errors.auth_required ~name:"InsufficientScope" 64 + ( "scope doesn't allow " 65 + ^ Oauth.Scopes.show_repo_action action 66 + ^ " on " ^ collection ) 67 + | _ -> 68 + () 69 + 70 + let assert_blob_scope credentials ~mime = 71 + match credentials with 72 + | OAuth {scopes; _} -> 73 + if not (Oauth.Scopes.Transition.allows_blob scopes {mime}) then 74 + Errors.auth_required ~name:"InsufficientScope" 75 + ("scope doesn't allow blob upload for " ^ mime) 76 + | _ -> 77 + () 78 + 79 + let assert_identity_scope credentials ~attr = 80 + match credentials with 81 + | OAuth {scopes; _} -> 82 + if not (Oauth.Scopes.allows_identity scopes {attr}) then 83 + Errors.auth_required ~name:"InsufficientScope" 84 + "scope doesn't allow identity operations" 85 + | _ -> 86 + () 87 + 88 + let assert_account_scope credentials ~attr ~action = 89 + match credentials with 90 + | OAuth {scopes; _} -> 91 + if not (Oauth.Scopes.Transition.allows_account scopes {attr; action}) then 92 + Errors.auth_required ~name:"InsufficientScope" 93 + "scope doesn't allow account operations" 94 + | _ -> 95 + () 96 + 97 + let allows_email_read credentials = 98 + match credentials with 99 + | OAuth {scopes; _} -> 100 + Oauth.Scopes.Transition.allows_account scopes 101 + {attr= Oauth.Scopes.Email; action= Oauth.Scopes.Read} 102 + | _ -> 103 + true 104 + 105 + let assert_rpc_scope credentials ~lxm ~aud = 106 + match credentials with 107 + | OAuth {scopes; _} -> 108 + if not (Oauth.Scopes.Transition.allows_rpc scopes {lxm; aud}) then 109 + Errors.auth_required ~name:"InsufficientScope" 110 + ("scope doesn't allow rpc call to " ^ lxm) 111 + | _ -> 112 + () 53 113 54 114 let get_authed_did_exn = function 55 115 | Access {did} | OAuth {did; _} -> ··· 211 271 let jkt_claim = 212 272 claims |> member "cnf" |> member "jkt" |> to_string 213 273 in 274 + let scope_str = 275 + claims |> member "scope" |> to_string_option 276 + |> Option.value ~default:"" 277 + in 278 + let scopes = Oauth.Scopes.of_string scope_str in 214 279 let now = int_of_float (Unix.gettimeofday ()) in 215 280 if jkt_claim <> proof.jkt then 216 281 Lwt.return_error @@ Errors.auth_required "dpop key mismatch" 217 282 else if exp < now then 218 283 Lwt.return_error @@ Errors.auth_required "token expired" 284 + else if not (Oauth.Scopes.has_atproto scopes) then 285 + Lwt.return_error 286 + @@ Errors.auth_required ~name:"InvalidToken" 287 + "oauth token missing 'atproto' scope" 219 288 else 220 289 let%lwt session = 221 290 try%lwt ··· 227 296 in 228 297 match session with 229 298 | Ok {active= Some true; _} -> 230 - Lwt.return_ok (OAuth {did; proof}) 299 + Lwt.return_ok (OAuth {did; proof; scopes}) 231 300 | Ok _ -> 232 301 Lwt.return_error 233 302 @@ Errors.auth_required ~name:"AccountDeactivated"
+8
pegasus/lib/oauth/scopes.ml
··· 10 10 11 11 type repo_action = Create | Update | Delete 12 12 13 + let show_repo_action = function 14 + | Create -> 15 + "create" 16 + | Update -> 17 + "update" 18 + | Delete -> 19 + "delete" 20 + 13 21 type repo_collection = All | Collection of string 14 22 15 23 type repo_permission =
+1
pegasus/lib/xrpc.ml
··· 57 57 | _ -> 58 58 Errors.invalid_request "invalid proxy header" 59 59 in 60 + Auth.assert_rpc_scope ctx.auth ~lxm:nsid ~aud:service_did ; 60 61 let fragment = "#" ^ service_type in 61 62 match%lwt Id_resolver.Did.resolve service_did with 62 63 | Ok did_doc -> (