objective categorical abstract machine language personal data server

generate oauth code in get /oauth/authorize

futur.blue 34e13cf0 df46ef20

verified
+131 -65
+105 -65
pegasus/lib/api/oauth_/authorize.ml
··· 40 40 ~msg:"failed to parse par request" () ) 41 41 |> Result.get_ok 42 42 in 43 - let%lwt client = 43 + let%lwt _client = 44 44 try%lwt Oauth.Client.fetch_client_metadata client_id 45 45 with _ -> 46 46 Errors.internal_error 47 47 ~msg:"failed to fetch client metadata" () 48 48 in 49 + let code = 50 + "cod-" 51 + ^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ()) 52 + in 53 + let expires_at = 54 + Util.now_ms () + Oauth.Constants.code_expiry_ms 55 + in 56 + let%lwt () = 57 + Oauth.Queries.insert_auth_code ctx.db 58 + { code 59 + ; request_id 60 + ; authorized_by= None 61 + ; authorized_at= None 62 + ; expires_at 63 + ; used= false } 64 + in 49 65 match%lwt get_session_user ctx with 50 66 | None -> 51 67 Dream.redirect ctx.req ("/login?return_to=" ^ return_url) ··· 54 70 | Some hint when hint <> did -> 55 71 Dream.redirect ctx.req ("/login?return_to=" ^ return_url) 56 72 | _ -> 73 + (* 57 74 let%lwt handle = 58 75 match%lwt 59 76 Data_store.get_actor_by_identifier did ctx.db ··· 72 89 | None -> 73 90 client_id 74 91 in 75 - (* TODO: render authz page with client_name, handle, scopes, request_uri *) 92 + [ ("client_name", `String client_name) 93 + ; ("handle", `String handle) 94 + ; ( "scopes" 95 + , `List (List.map (fun s -> `String s) scopes) ) 96 + ; ("code", `String code) 97 + ("request_uri", `String request_uri) ] 98 + *) 76 99 Dream.html "" ) ) ) ) 77 100 78 101 let post_handler pool = ··· 84 107 match%lwt Dream.form ctx.req with 85 108 | `Ok fields -> ( 86 109 let action = List.assoc_opt "action" fields in 110 + let code = List.assoc_opt "code" fields in 87 111 let request_uri = List.assoc_opt "request_uri" fields in 88 - if action <> Some "allow" || request_uri = None then 89 - Errors.invalid_request "invalid request" ; 90 - let request_uri = Option.get request_uri in 91 - let prefix = Oauth.Constants.request_uri_prefix in 92 - let request_id = 93 - String.sub request_uri (String.length prefix) 94 - (String.length request_uri - String.length prefix) 95 - in 96 - let%lwt stored_request = 97 - Oauth.Queries.get_par_request pool request_id 98 - in 99 - match stored_request with 100 - | None -> 101 - Errors.invalid_request "request expired" 102 - | Some req_record -> 103 - let req = 104 - Yojson.Safe.from_string req_record.request_data 105 - |> par_request_of_yojson 106 - |> Result.map_error (fun _ -> 107 - Errors.internal_error 108 - ~msg:"failed to parse stored request" () ) 109 - |> Result.get_ok 112 + match (action, code, request_uri) with 113 + | Some "deny", _, Some request_uri -> ( 114 + let prefix = Oauth.Constants.request_uri_prefix in 115 + let request_id = 116 + String.sub request_uri (String.length prefix) 117 + (String.length request_uri - String.length prefix) 110 118 in 111 - if Util.now_ms () > req_record.expires_at then 112 - Errors.invalid_request "request expired" 113 - else 114 - let code = 115 - "cod-" 116 - ^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ()) 117 - in 118 - let expires_at = 119 - Util.now_ms () + Oauth.Constants.code_expiry_ms 120 - in 121 - let%lwt () = 122 - Oauth.Queries.insert_auth_code pool 123 - { code 124 - ; request_id 125 - ; authorized_by= Some user_did 126 - ; authorized_at= Some (Util.now_ms ()) 127 - ; expires_at 128 - ; used= false } 129 - in 130 - let params = 131 - [ ("code", code) 132 - ; ("state", req.state) 133 - ; ("iss", "https://" ^ Env.hostname) ] 134 - in 135 - let query = 136 - String.concat "&" 137 - (List.map 138 - (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v) 139 - params ) 140 - in 141 - let separator = 142 - match req.response_mode with 143 - | Some "query" -> 144 - "?" 145 - | Some "fragment" -> 146 - "#" 147 - | _ -> 148 - "?" 149 - in 150 - let redirect_uri = req.redirect_uri ^ separator ^ query in 151 - Dream.redirect ctx.req redirect_uri ) 119 + let%lwt req_record = 120 + Oauth.Queries.get_par_request pool request_id 121 + in 122 + match req_record with 123 + | Some rec_ -> 124 + let req = 125 + Yojson.Safe.from_string rec_.request_data 126 + |> par_request_of_yojson |> Result.get_ok 127 + in 128 + let params = 129 + [ ("error", "access_denied") 130 + ; ("error_description", "Unable to authorize user.") 131 + ; ("state", req.state) 132 + ; ("iss", "https://" ^ Env.hostname) ] 133 + in 134 + let query = 135 + String.concat "&" 136 + (List.map 137 + (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v) 138 + params ) 139 + in 140 + Dream.redirect ctx.req (req.redirect_uri ^ "?" ^ query) 141 + | None -> 142 + Errors.invalid_request "request expired" ) 143 + | Some "allow", Some code, Some _request_uri -> ( 144 + let%lwt code_record = Oauth.Queries.get_auth_code pool code in 145 + match code_record with 146 + | None -> 147 + Errors.invalid_request "invalid code" 148 + | Some code_rec -> ( 149 + if code_rec.authorized_by <> None then 150 + Errors.invalid_request "code already authorized" 151 + else if code_rec.used then 152 + Errors.invalid_request "code already used" 153 + else if Util.now_ms () > code_rec.expires_at then 154 + Errors.invalid_request "code expired" 155 + else 156 + let%lwt () = 157 + Oauth.Queries.activate_auth_code pool code user_did 158 + in 159 + let%lwt req_record = 160 + Oauth.Queries.get_par_request pool code_rec.request_id 161 + in 162 + match req_record with 163 + | None -> 164 + Errors.internal_error ~msg:"request not found" () 165 + | Some rec_ -> 166 + let req = 167 + Yojson.Safe.from_string rec_.request_data 168 + |> par_request_of_yojson |> Result.get_ok 169 + in 170 + let params = 171 + [ ("code", code) 172 + ; ("state", req.state) 173 + ; ("iss", "https://" ^ Env.hostname) ] 174 + in 175 + let query = 176 + String.concat "&" 177 + (List.map 178 + (fun (k, v) -> k ^ "=" ^ Uri.pct_encode v) 179 + params ) 180 + in 181 + let separator = 182 + match req.response_mode with 183 + | Some "fragment" -> 184 + "#" 185 + | _ -> 186 + "?" 187 + in 188 + Dream.redirect ctx.req 189 + (req.redirect_uri ^ separator ^ query) ) ) 190 + | _ -> 191 + Errors.invalid_request "invalid request" ) 152 192 | _ -> 153 193 Errors.invalid_request "invalid request" ) )
+26
pegasus/lib/oauth/queries.ml
··· 38 38 record_in] 39 39 code 40 40 41 + let get_auth_code conn code = 42 + Util.use_pool conn 43 + @@ [%rapper 44 + get_opt 45 + {sql| 46 + SELECT @string{code}, @string{request_id}, @string?{authorized_by}, 47 + @int?{authorized_at}, @int{expires_at}, @bool{used} 48 + FROM oauth_codes 49 + WHERE code = %string{code} 50 + |sql} 51 + record_out] 52 + ~code 53 + 54 + let activate_auth_code conn code did = 55 + let authorized_at = Util.now_ms () in 56 + Util.use_pool conn 57 + @@ [%rapper 58 + execute 59 + {sql| 60 + UPDATE oauth_codes 61 + SET authorized_by = %string{did}, 62 + authorized_at = %int{authorized_at} 63 + WHERE code = %string{code} AND authorized_by = NULL 64 + |sql}] 65 + ~did ~authorized_at ~code 66 + 41 67 let consume_auth_code conn code = 42 68 Util.use_pool conn 43 69 @@ [%rapper