···117117 let request_uri = List.assoc_opt "request_uri" fields in
118118 let did = List.assoc_opt "did" fields in
119119 match (action, code, request_uri, did) with
120120- | Some action, Some code, Some request_uri, Some did ->
120120+ | Some action, Some code, Some request_uri, Some did -> (
121121 let prefix = Constants.request_uri_prefix in
122122 let request_id =
123123 String.sub request_uri (String.length prefix)
···128128 Errors.invalid_request "request not found"
129129 else
130130 let req_record = Option.get req_record in
131131- let req =
131131+ match
132132 Yojson.Safe.from_string req_record.request_data
133133- |> par_request_of_yojson |> Result.get_ok
134134- in
135135- if action = "allow" then
136136- let%lwt is_logged_in = Session.is_logged_in ctx.req did in
137137- if is_logged_in then
138138- let%lwt code_record = Queries.get_auth_code ctx.db code in
139139- match code_record with
140140- | None ->
141141- Errors.invalid_request "invalid code"
142142- | Some code_rec ->
143143- if code_rec.authorized_by <> None then
144144- Errors.invalid_request "code already authorized"
145145- else if code_rec.used then
146146- Errors.invalid_request "code already used"
147147- else if Util.now_ms () > code_rec.expires_at then
148148- Errors.invalid_request "code expired"
149149- else if code_rec.request_id <> request_id then
150150- Errors.invalid_request "code not for this request"
151151- else
152152- let%lwt () =
153153- Queries.activate_auth_code ctx.db code did
154154- in
155155- let params =
156156- [ ("code", code)
157157- ; ("state", req.state)
158158- ; ("iss", Env.host_endpoint) ]
159159- in
160160- let query =
161161- String.concat "&"
162162- (List.map
163163- (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
164164- params )
165165- in
166166- let separator =
167167- match req.response_mode with
168168- | Some "fragment" ->
169169- "#"
170170- | _ ->
171171- "?"
172172- in
173173- Dream.redirect ctx.req
174174- (req.redirect_uri ^ separator ^ query)
175175- else
176176- Uri.make ~path:"/account/login"
177177- ~query:
178178- [ ("client_id", [req_record.client_id])
179179- ; ("request_uri", [request_uri]) ]
180180- ()
181181- |> Uri.to_string |> Dream.redirect ctx.req
182182- else
183183- let params =
184184- [ ("error", "access_denied")
185185- ; ("error_description", "Unable to authorize user.")
186186- ; ("state", req.state)
187187- ; ("iss", Env.host_endpoint) ]
188188- in
189189- let query =
190190- String.concat "&"
191191- (List.map
192192- (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
193193- params )
194194- in
195195- Dream.redirect ctx.req (req.redirect_uri ^ "?" ^ query)
133133+ |> par_request_of_yojson
134134+ with
135135+ | Error _ ->
136136+ Errors.invalid_request "invalid par request"
137137+ | Ok req ->
138138+ if action = "allow" then
139139+ let%lwt is_logged_in = Session.is_logged_in ctx.req did in
140140+ if is_logged_in then
141141+ let%lwt code_record =
142142+ Queries.get_auth_code ctx.db code
143143+ in
144144+ match code_record with
145145+ | None ->
146146+ Errors.invalid_request "invalid code"
147147+ | Some code_rec ->
148148+ if code_rec.authorized_by <> None then
149149+ Errors.invalid_request "code already authorized"
150150+ else if code_rec.used then
151151+ Errors.invalid_request "code already used"
152152+ else if Util.now_ms () > code_rec.expires_at then
153153+ Errors.invalid_request "code expired"
154154+ else if code_rec.request_id <> request_id then
155155+ Errors.invalid_request "code not for this request"
156156+ else
157157+ let%lwt () =
158158+ Queries.activate_auth_code ctx.db code did
159159+ in
160160+ let params =
161161+ [ ("code", code)
162162+ ; ("state", req.state)
163163+ ; ("iss", Env.host_endpoint) ]
164164+ in
165165+ let query =
166166+ String.concat "&"
167167+ (List.map
168168+ (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
169169+ params )
170170+ in
171171+ let separator =
172172+ match req.response_mode with
173173+ | Some "fragment" ->
174174+ "#"
175175+ | _ ->
176176+ "?"
177177+ in
178178+ Dream.redirect ctx.req
179179+ (req.redirect_uri ^ separator ^ query)
180180+ else
181181+ Uri.make ~path:"/account/login"
182182+ ~query:
183183+ [ ("client_id", [req_record.client_id])
184184+ ; ("request_uri", [request_uri]) ]
185185+ ()
186186+ |> Uri.to_string |> Dream.redirect ctx.req
187187+ else
188188+ let params =
189189+ [ ("error", "access_denied")
190190+ ; ("error_description", "Unable to authorize user.")
191191+ ; ("state", req.state)
192192+ ; ("iss", Env.host_endpoint) ]
193193+ in
194194+ let query =
195195+ String.concat "&"
196196+ (List.map
197197+ (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v)
198198+ params )
199199+ in
200200+ Dream.redirect ctx.req (req.redirect_uri ^ "?" ^ query) )
196201 | _ ->
197202 Errors.invalid_request "invalid request" )
198203 | _ ->
+98-93
pegasus/lib/api/oauth_/token.ml
···3030 match par_req with
3131 | None ->
3232 Errors.internal_error ~msg:"request not found" ()
3333- | Some par_record ->
3434- let orig_req =
3535- Yojson.Safe.from_string par_record.request_data
3636- |> Types.par_request_of_yojson |> Result.get_ok
3737- in
3838- ( match req.redirect_uri with
3939- | None ->
4040- Errors.invalid_request "redirect_uri required"
4141- | Some uri when uri <> orig_req.redirect_uri ->
4242- Errors.invalid_request "redirect_uri mismatch"
4343- | _ ->
4444- () ) ;
4545- ( match req.code_verifier with
4646- | None ->
4747- Errors.invalid_request "code_verifier required"
4848- | Some verifier ->
4949- let computed =
5050- Digestif.SHA256.digest_string verifier
5151- |> Digestif.SHA256.to_raw_string
5252- |> Base64.(
5353- encode_exn ~pad:false
5454- ~alphabet:uri_safe_alphabet )
5555- in
5656- if orig_req.code_challenge <> computed then
5757- Errors.invalid_request "invalid code_verifier"
5858- ) ;
5959- ( match par_record.dpop_jkt with
6060- | Some stored when stored <> proof.jkt ->
6161- Errors.invalid_request "DPoP key mismatch"
6262- | _ ->
6363- () ) ;
6464- let token_id =
6565- "tok-"
6666- ^ Uuidm.to_string
6767- (Uuidm.v4_gen
6868- (Random.State.make_self_init ())
6969- () )
7070- in
7171- let refresh_token =
7272- "ref-"
7373- ^ Uuidm.to_string
7474- (Uuidm.v4_gen
7575- (Random.State.make_self_init ())
7676- () )
7777- in
7878- let now_sec = int_of_float (Unix.gettimeofday ()) in
7979- let now_ms = Util.now_ms () in
8080- let expires_in =
8181- Constants.access_token_expiry_ms / 1000
8282- in
8383- let exp_sec = now_sec + expires_in in
8484- let expires_at = exp_sec * 1000 in
8585- let claims =
8686- `Assoc
8787- [ ("jti", `String token_id)
8888- ; ("sub", `String did)
8989- ; ("iat", `Int now_sec)
9090- ; ("exp", `Int exp_sec)
9191- ; ("scope", `String orig_req.scope)
9292- ; ("aud", `String Env.host_endpoint)
9393- ; ("cnf", `Assoc [("jkt", `String proof.jkt)]) ]
9494- in
9595- let access_token =
9696- Jwt.sign_jwt claims ~typ:"at+jwt"
9797- ~signing_key:Env.jwt_key
9898- in
9999- let%lwt () =
100100- Queries.insert_oauth_token ctx.db
101101- { refresh_token
102102- ; client_id= req.client_id
103103- ; did
104104- ; dpop_jkt= proof.jkt
105105- ; scope= orig_req.scope
106106- ; created_at= now_ms
107107- ; last_refreshed_at= now_ms
108108- ; expires_at
109109- ; last_ip= ip
110110- ; last_user_agent= user_agent }
111111- in
112112- let nonce = Dpop.next_nonce () in
113113- Dream.json
114114- ~headers:
115115- [ ("DPoP-Nonce", nonce)
116116- ; ("Access-Control-Expose-Headers", "DPoP-Nonce")
117117- ; ("Cache-Control", "no-store") ]
118118- @@ Yojson.Safe.to_string
119119- @@ `Assoc
120120- [ ("access_token", `String access_token)
121121- ; ("token_type", `String "DPoP")
122122- ; ("refresh_token", `String refresh_token)
123123- ; ("expires_in", `Int expires_in)
124124- ; ("scope", `String orig_req.scope)
125125- ; ("sub", `String did) ] ) ) ) )
3333+ | Some par_record -> (
3434+ match
3535+ Yojson.Safe.from_string par_record.request_data
3636+ |> Types.par_request_of_yojson
3737+ with
3838+ | Error _ ->
3939+ Errors.invalid_request
4040+ "stored par request formatted incorrectly"
4141+ | Ok orig_req ->
4242+ ( match req.redirect_uri with
4343+ | None ->
4444+ Errors.invalid_request "redirect_uri required"
4545+ | Some uri when uri <> orig_req.redirect_uri ->
4646+ Errors.invalid_request "redirect_uri mismatch"
4747+ | _ ->
4848+ () ) ;
4949+ ( match req.code_verifier with
5050+ | None ->
5151+ Errors.invalid_request "code_verifier required"
5252+ | Some verifier ->
5353+ let computed =
5454+ Digestif.SHA256.digest_string verifier
5555+ |> Digestif.SHA256.to_raw_string
5656+ |> Base64.(
5757+ encode_exn ~pad:false
5858+ ~alphabet:uri_safe_alphabet )
5959+ in
6060+ if orig_req.code_challenge <> computed then
6161+ Errors.invalid_request "invalid code_verifier"
6262+ ) ;
6363+ ( match par_record.dpop_jkt with
6464+ | Some stored when stored <> proof.jkt ->
6565+ Errors.invalid_request "DPoP key mismatch"
6666+ | _ ->
6767+ () ) ;
6868+ let token_id =
6969+ "tok-"
7070+ ^ Uuidm.to_string
7171+ (Uuidm.v4_gen
7272+ (Random.State.make_self_init ())
7373+ () )
7474+ in
7575+ let refresh_token =
7676+ "ref-"
7777+ ^ Uuidm.to_string
7878+ (Uuidm.v4_gen
7979+ (Random.State.make_self_init ())
8080+ () )
8181+ in
8282+ let now_sec = int_of_float (Unix.gettimeofday ()) in
8383+ let now_ms = Util.now_ms () in
8484+ let expires_in =
8585+ Constants.access_token_expiry_ms / 1000
8686+ in
8787+ let exp_sec = now_sec + expires_in in
8888+ let expires_at = exp_sec * 1000 in
8989+ let claims =
9090+ `Assoc
9191+ [ ("jti", `String token_id)
9292+ ; ("sub", `String did)
9393+ ; ("iat", `Int now_sec)
9494+ ; ("exp", `Int exp_sec)
9595+ ; ("scope", `String orig_req.scope)
9696+ ; ("aud", `String Env.host_endpoint)
9797+ ; ("cnf", `Assoc [("jkt", `String proof.jkt)])
9898+ ]
9999+ in
100100+ let access_token =
101101+ Jwt.sign_jwt claims ~typ:"at+jwt"
102102+ ~signing_key:Env.jwt_key
103103+ in
104104+ let%lwt () =
105105+ Queries.insert_oauth_token ctx.db
106106+ { refresh_token
107107+ ; client_id= req.client_id
108108+ ; did
109109+ ; dpop_jkt= proof.jkt
110110+ ; scope= orig_req.scope
111111+ ; created_at= now_ms
112112+ ; last_refreshed_at= now_ms
113113+ ; expires_at
114114+ ; last_ip= ip
115115+ ; last_user_agent= user_agent }
116116+ in
117117+ let nonce = Dpop.next_nonce () in
118118+ Dream.json
119119+ ~headers:
120120+ [ ("DPoP-Nonce", nonce)
121121+ ; ("Access-Control-Expose-Headers", "DPoP-Nonce")
122122+ ; ("Cache-Control", "no-store") ]
123123+ @@ Yojson.Safe.to_string
124124+ @@ `Assoc
125125+ [ ("access_token", `String access_token)
126126+ ; ("token_type", `String "DPoP")
127127+ ; ("refresh_token", `String refresh_token)
128128+ ; ("expires_in", `Int expires_in)
129129+ ; ("scope", `String orig_req.scope)
130130+ ; ("sub", `String did) ] ) ) ) ) )
126131 | "refresh_token" -> (
127132 match req.refresh_token with
128133 | None ->
+19-13
pegasus/lib/auth.ml
···2525 | Ok (_, payload) -> (
2626 try
2727 let now_s = int_of_float (Unix.gettimeofday ()) in
2828- let jwt = Jwt.symmetric_jwt_of_yojson payload |> Result.get_ok in
2929- if jwt.aud <> Env.did then Lwt.return_error "invalid aud"
3030- else if jwt.sub = "" then Lwt.return_error "missing sub"
3131- else if now_s < jwt.iat then Lwt.return_error "token issued in the future"
3232- else if now_s > jwt.exp then Lwt.return_error "expired token"
3333- else if jwt.scope <> expected_scope then Lwt.return_error "invalid scope"
3434- else if jwt.jti = "" then Lwt.return_error "missing jti"
3535- else
3636- let%lwt revoked_at =
3737- Data_store.is_token_revoked t ~did:jwt.sub ~jti:jwt.jti
3838- in
3939- if revoked_at <> None then Lwt.return_error "token revoked"
4040- else Lwt.return_ok jwt
2828+ match Jwt.symmetric_jwt_of_yojson payload with
2929+ | Error e ->
3030+ Dream.debug (fun log -> log "bearer jwt decode error: %s" e) ;
3131+ Lwt.return_error "invalid token format"
3232+ | Ok jwt ->
3333+ if jwt.aud <> Env.did then Lwt.return_error "invalid aud"
3434+ else if jwt.sub = "" then Lwt.return_error "missing sub"
3535+ else if now_s < jwt.iat then
3636+ Lwt.return_error "token issued in the future"
3737+ else if now_s > jwt.exp then Lwt.return_error "expired token"
3838+ else if jwt.scope <> expected_scope then
3939+ Lwt.return_error "invalid scope"
4040+ else if jwt.jti = "" then Lwt.return_error "missing jti"
4141+ else
4242+ let%lwt revoked_at =
4343+ Data_store.is_token_revoked t ~did:jwt.sub ~jti:jwt.jti
4444+ in
4545+ if revoked_at <> None then Lwt.return_error "token revoked"
4646+ else Lwt.return_ok jwt
4147 with _ -> Lwt.return_error "invalid token format" )
42484349let verify_auth ?(refresh = false) credentials did =
+13
pegasus/lib/env.ml
···22 try Sys.getenv name
33 with Not_found -> failwith ("Missing environment variable " ^ name)
4455+let log_level =
66+ match Sys.getenv_opt "PDS_LOG_LEVEL" |> Option.map String.lowercase_ascii with
77+ | Some "debug" ->
88+ `Debug
99+ | Some "info" ->
1010+ `Info
1111+ | Some "warn" | Some "warning" ->
1212+ `Warning
1313+ | Some "error" ->
1414+ `Error
1515+ | _ ->
1616+ `Info
1717+518let data_dir = Option.value ~default:"./data" @@ Sys.getenv_opt "PDS_DATA_DIR"
619720let hostname = getenv "PDS_HOSTNAME"
+76-70
pegasus/lib/oauth/dpop.ml
···77 ; rotation_interval_ms: int64 }
8899type ec_jwk = {crv: string; kty: string; x: string; y: string}
1010-[@@deriving yojson]
1010+[@@deriving yojson {strict= false}]
11111212type proof = {jti: string; jkt: string; htm: string; htu: string}
1313-[@@deriving yojson]
1313+[@@deriving yojson {strict= false}]
14141515let jti_cache : (string, int) Hashtbl.t =
1616 Hashtbl.create Constants.jti_cache_size
···133133 if alg <> "ES256" && alg <> "ES256K" then
134134 Error "only es256 and es256k supported for dpop"
135135 else
136136- let jwk =
137137- header |> member "jwk" |> ec_jwk_of_yojson |> Result.get_ok
138138- in
139139- if
140140- not
141141- ( match (alg, jwk.crv) with
142142- | "ES256", "P-256" ->
143143- true
144144- | "ES256K", "secp256k1" ->
145145- true
146146- | _ ->
147147- false )
148148- then
149149- Error
150150- (Printf.sprintf "algorithm %s doesn't match curve %s" alg
151151- jwk.crv )
152152- else
153153- let jti = payload |> member "jti" |> to_string in
154154- let htm = payload |> member "htm" |> to_string in
155155- let htu = payload |> member "htu" |> to_string in
156156- let iat = payload |> member "iat" |> to_int in
157157- let nonce_claim =
158158- payload |> member "nonce" |> to_string_option
159159- in
160160- match nonce_claim with
161161- (* error must be this string; see https://datatracker.ietf.org/doc/html/rfc9449#section-8 *)
162162- | None ->
163163- Error "use_dpop_nonce"
164164- | Some n when not (verify_nonce n) ->
165165- Error "use_dpop_nonce"
166166- | Some _ -> (
167167- if htm <> mthd then Error "htm mismatch"
168168- else if
169169- not (String.equal (normalize_url htu) (normalize_url url))
170170- then Error "htu mismatch"
171171- else
172172- let now = int_of_float (Unix.gettimeofday ()) in
173173- if now - iat > Constants.max_dpop_age_s then
174174- Error "dpop proof too old"
175175- else if iat - now > 5 then Error "dpop proof in future"
176176- else if not (add_jti jti) then
177177- Error "dpop proof replay detected"
178178- else if
179179- not (try verify_signature jwt jwk with _ -> false)
180180- then Error "invalid dpop signature"
181181- else
182182- let jkt = compute_jwk_thumbprint jwk in
183183- (* verify ath if access token is provided *)
184184- match access_token with
185185- | Some token ->
186186- let ath_claim =
187187- payload |> member "ath" |> to_string_option
188188- in
189189- let expected_ath =
190190- Digestif.SHA256.(
191191- digest_string token |> to_raw_string
192192- |> Jwt.b64_encode )
193193- in
194194- if Some expected_ath <> ath_claim then
195195- Error "ath mismatch"
196196- else Ok {jti; jkt; htm; htu}
197197- | None ->
198198- let ath_claim =
199199- payload |> member "ath" |> to_string_option
200200- in
201201- if ath_claim <> None then
202202- Error "ath claim not allowed without access token"
203203- else Ok {jti; jkt; htm; htu} ) )
136136+ match header |> member "jwk" |> ec_jwk_of_yojson with
137137+ | Error e ->
138138+ Dream.debug (fun log -> log "error parsing jwk: %s" e) ;
139139+ Errors.internal_error ()
140140+ | Ok jwk -> (
141141+ if
142142+ not
143143+ ( match (alg, jwk.crv) with
144144+ | "ES256", "P-256" ->
145145+ true
146146+ | "ES256K", "secp256k1" ->
147147+ true
148148+ | _ ->
149149+ false )
150150+ then
151151+ Error
152152+ (Printf.sprintf "algorithm %s doesn't match curve %s" alg
153153+ jwk.crv )
154154+ else
155155+ let jti = payload |> member "jti" |> to_string in
156156+ let htm = payload |> member "htm" |> to_string in
157157+ let htu = payload |> member "htu" |> to_string in
158158+ let iat = payload |> member "iat" |> to_int in
159159+ let nonce_claim =
160160+ payload |> member "nonce" |> to_string_option
161161+ in
162162+ match nonce_claim with
163163+ (* error must be this string; see https://datatracker.ietf.org/doc/html/rfc9449#section-8 *)
164164+ | None ->
165165+ Error "use_dpop_nonce"
166166+ | Some n when not (verify_nonce n) ->
167167+ Error "use_dpop_nonce"
168168+ | Some _ -> (
169169+ if htm <> mthd then Error "htm mismatch"
170170+ else if
171171+ not
172172+ (String.equal (normalize_url htu)
173173+ (normalize_url url) )
174174+ then Error "htu mismatch"
175175+ else
176176+ let now = int_of_float (Unix.gettimeofday ()) in
177177+ if now - iat > Constants.max_dpop_age_s then
178178+ Error "dpop proof too old"
179179+ else if iat - now > 5 then
180180+ Error "dpop proof in future"
181181+ else if not (add_jti jti) then
182182+ Error "dpop proof replay detected"
183183+ else if
184184+ not (try verify_signature jwt jwk with _ -> false)
185185+ then Error "invalid dpop signature"
186186+ else
187187+ let jkt = compute_jwk_thumbprint jwk in
188188+ (* verify ath if access token is provided *)
189189+ match access_token with
190190+ | Some token ->
191191+ let ath_claim =
192192+ payload |> member "ath" |> to_string_option
193193+ in
194194+ let expected_ath =
195195+ Digestif.SHA256.(
196196+ digest_string token |> to_raw_string
197197+ |> Jwt.b64_encode )
198198+ in
199199+ if Some expected_ath <> ath_claim then
200200+ Error "ath mismatch"
201201+ else Ok {jti; jkt; htm; htu}
202202+ | None ->
203203+ let ath_claim =
204204+ payload |> member "ath" |> to_string_option
205205+ in
206206+ if ath_claim <> None then
207207+ Error
208208+ "ath claim not allowed without access token"
209209+ else Ok {jti; jkt; htm; htu} ) ) )
204210 | _ ->
205211 Error "invalid dpop jwt" )
+43-38
pegasus/lib/repository.ml
···3838 let type' = member "$type" json |> to_string in
3939 let collection = member "collection" json |> to_string in
4040 let rkey = match member "rkey" json with `String s -> Some s | _ -> None in
4141- let swap_record =
4242- match member "swapRecord" json with
4343- | `String s ->
4444- s |> Cid.of_string |> Result.get_ok |> Option.some
4141+ try
4242+ let swap_record =
4343+ match member "swapRecord" json with
4444+ | `String s ->
4545+ s |> Cid.of_string |> Result.get_ok |> Option.some
4646+ | _ ->
4747+ None
4848+ in
4949+ match type' with
5050+ | "com.atproto.repo.applyWrites#create" ->
5151+ let value =
5252+ member "value" json |> Lex.repo_record_of_yojson |> Result.get_ok
5353+ in
5454+ Ok (Create {type'; collection; rkey; value})
5555+ | "com.atproto.repo.applyWrites#update" ->
5656+ let value =
5757+ member "value" json |> Lex.repo_record_of_yojson |> Result.get_ok
5858+ in
5959+ Ok
6060+ (Update {type'; collection; rkey= Option.get rkey; value; swap_record})
6161+ | "com.atproto.repo.applyWrites#delete" ->
6262+ Ok (Delete {type'; collection; rkey= Option.get rkey; swap_record})
4563 | _ ->
4646- None
4747- in
4848- match type' with
4949- | "com.atproto.repo.applyWrites#create" ->
5050- let value =
5151- member "value" json |> Lex.repo_record_of_yojson |> Result.get_ok
5252- in
5353- Ok (Create {type'; collection; rkey; value})
5454- | "com.atproto.repo.applyWrites#update" ->
5555- let value =
5656- member "value" json |> Lex.repo_record_of_yojson |> Result.get_ok
5757- in
5858- Ok (Update {type'; collection; rkey= Option.get rkey; value; swap_record})
5959- | "com.atproto.repo.applyWrites#delete" ->
6060- Ok (Delete {type'; collection; rkey= Option.get rkey; swap_record})
6161- | _ ->
6262- Error "invalid applyWrites write $type"
6464+ Error "invalid applyWrites write $type"
6565+ with Invalid_argument e -> Error ("invalid property " ^ e)
63666467let repo_write_to_yojson = function
6568 | Create {type'; collection; rkey; value} ->
···101104let apply_writes_result_of_yojson (json : Yojson.Safe.t) =
102105 let open Yojson.Safe.Util in
103106 let type' = member "$type" json |> to_string in
104104- match type' with
105105- | "com.atproto.repo.applyWrites#createResult" ->
106106- let uri = member "uri" json |> to_string in
107107- let cid =
108108- member "cid" json |> to_string |> Cid.of_string |> Result.get_ok
109109- in
110110- Ok (Create {type'; uri; cid})
111111- | "com.atproto.repo.applyWrites#updateResult" ->
112112- let uri = member "uri" json |> to_string in
113113- let cid =
114114- member "cid" json |> to_string |> Cid.of_string |> Result.get_ok
115115- in
116116- Ok (Update {type'; uri; cid})
117117- | "com.atproto.repo.applyWrites#deleteResult" ->
118118- Ok (Delete {type'})
119119- | _ ->
120120- Error "invalid applyWrites result $type"
107107+ try
108108+ match type' with
109109+ | "com.atproto.repo.applyWrites#createResult" ->
110110+ let uri = member "uri" json |> to_string in
111111+ let cid =
112112+ member "cid" json |> to_string |> Cid.of_string |> Result.get_ok
113113+ in
114114+ Ok (Create {type'; uri; cid})
115115+ | "com.atproto.repo.applyWrites#updateResult" ->
116116+ let uri = member "uri" json |> to_string in
117117+ let cid =
118118+ member "cid" json |> to_string |> Cid.of_string |> Result.get_ok
119119+ in
120120+ Ok (Update {type'; uri; cid})
121121+ | "com.atproto.repo.applyWrites#deleteResult" ->
122122+ Ok (Delete {type'})
123123+ | _ ->
124124+ Error "invalid applyWrites result $type"
125125+ with Invalid_argument e -> Error ("invalid property " ^ e)
121126122127let apply_writes_result_to_yojson = function
123128 | Create {type'; uri; cid} ->
+12-2
pegasus/lib/xrpc.ml
···139139 (k, try Yojson.Safe.from_string v with _ -> `String v) )
140140 queries )
141141 in
142142- query_json |> of_yojson |> Result.get_ok
142142+ match query_json |> of_yojson with
143143+ | Error e ->
144144+ Dream.debug (fun log -> log "error parsing query: %s" e) ;
145145+ Errors.internal_error ()
146146+ | Ok query ->
147147+ query
143148 with _ -> Errors.invalid_request "invalid query string"
144149145150let parse_body (req : Dream.request)
146151 (of_yojson : Yojson.Safe.t -> ('a, string) result) : 'a Lwt.t =
147152 try%lwt
148153 let%lwt body = Dream.body req in
149149- body |> Yojson.Safe.from_string |> of_yojson |> Result.get_ok |> Lwt.return
154154+ match body |> Yojson.Safe.from_string |> of_yojson with
155155+ | Error e ->
156156+ Dream.debug (fun log -> log "error parsing body: %s" e) ;
157157+ Errors.internal_error ()
158158+ | Ok body ->
159159+ Lwt.return body
150160 with _ -> Errors.invalid_request "invalid request body"
151161152162let parse_proxy_header req =