objective categorical abstract machine language personal data server
65
fork

Configure Feed

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

/oauth/token

futurGH 3966a6d3 34e13cf0

+212 -31
+3
bin/main.ml
··· 15 15 , Api.Well_known.oauth_authorization_server ) 16 16 ; (* oauth *) 17 17 (get, "/oauth/par", Api.Oauth_.Par.handler) 18 + ; (get, "/oauth/authorize", Api.Oauth_.Authorize.get_handler) 19 + ; (post, "/oauth/authorize", Api.Oauth_.Authorize.post_handler) 20 + ; (post, "/oauth/token", Api.Oauth_.Token.handler) 18 21 ; (* unauthed *) 19 22 ( get 20 23 , "/xrpc/com.atproto.server.describeServer"
+8 -5
pegasus/lib/api/oauth_/authorize.ml
··· 98 98 *) 99 99 Dream.html "" ) ) ) ) 100 100 101 - let post_handler pool = 101 + let post_handler = 102 102 Xrpc.handler (fun ctx -> 103 103 match%lwt get_session_user ctx with 104 104 | None -> ··· 117 117 (String.length request_uri - String.length prefix) 118 118 in 119 119 let%lwt req_record = 120 - Oauth.Queries.get_par_request pool request_id 120 + Oauth.Queries.get_par_request ctx.db request_id 121 121 in 122 122 match req_record with 123 123 | Some rec_ -> ··· 141 141 | None -> 142 142 Errors.invalid_request "request expired" ) 143 143 | Some "allow", Some code, Some _request_uri -> ( 144 - let%lwt code_record = Oauth.Queries.get_auth_code pool code in 144 + let%lwt code_record = 145 + Oauth.Queries.get_auth_code ctx.db code 146 + in 145 147 match code_record with 146 148 | None -> 147 149 Errors.invalid_request "invalid code" ··· 154 156 Errors.invalid_request "code expired" 155 157 else 156 158 let%lwt () = 157 - Oauth.Queries.activate_auth_code pool code user_did 159 + Oauth.Queries.activate_auth_code ctx.db code user_did 158 160 in 159 161 let%lwt req_record = 160 - Oauth.Queries.get_par_request pool code_rec.request_id 162 + Oauth.Queries.get_par_request ctx.db 163 + code_rec.request_id 161 164 in 162 165 match req_record with 163 166 | None ->
+1 -1
pegasus/lib/api/oauth_/par.ml
··· 41 41 Errors.invalid_request "invalid redirect_uri" 42 42 else 43 43 let request_id = 44 - "req-" ^ (Uuidm.v4_gen (Random.get_state ()) () |> Uuidm.to_string) 44 + "req-" ^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ()) 45 45 in 46 46 let request_uri = Oauth.Constants.request_uri_prefix ^ request_id in 47 47 let expires_at =
+186
pegasus/lib/api/oauth_/token.ml
··· 1 + open Oauth 2 + 3 + let handler = 4 + Xrpc.handler (fun ctx -> 5 + let%lwt req = Xrpc.parse_body ctx.req Types.token_request_of_yojson in 6 + let dpop_header = Dream.header ctx.req "DPoP" in 7 + let full_url = "https://" ^ Env.hostname ^ Dream.target ctx.req in 8 + let dpop_result = 9 + Dpop.verify_dpop_proof 10 + ~mthd:(Dream.method_to_string @@ Dream.method_ ctx.req) 11 + ~url:full_url ~dpop_header () 12 + in 13 + match dpop_result with 14 + | Error "use_dpop_nonce" -> 15 + Dream.json ~status:`Bad_Request 16 + @@ Yojson.Safe.to_string 17 + @@ `Assoc [("error", `String "use_dpop_nonce")] 18 + | Error e -> 19 + Errors.invalid_request ("DPoP error: " ^ e) 20 + | Ok proof -> ( 21 + match req.grant_type with 22 + | "authorization_code" -> ( 23 + match req.code with 24 + | None -> 25 + Errors.invalid_request "code required" 26 + | Some code -> ( 27 + let%lwt code_record = Queries.consume_auth_code ctx.db code in 28 + match code_record with 29 + | None -> 30 + Errors.invalid_request "invalid code" 31 + | Some code_rec -> ( 32 + if Util.now_ms () > code_rec.expires_at then 33 + Errors.invalid_request "code expired" 34 + else 35 + match code_rec.authorized_by with 36 + | None -> 37 + Errors.invalid_request "code not authorized" 38 + | Some did -> ( 39 + let%lwt par_req = 40 + Queries.get_par_request ctx.db code_rec.request_id 41 + in 42 + match par_req with 43 + | None -> 44 + Errors.internal_error ~msg:"request not found" () 45 + | Some par_record -> 46 + let orig_req = 47 + Yojson.Safe.from_string par_record.request_data 48 + |> Types.par_request_of_yojson |> Result.get_ok 49 + in 50 + ( match req.redirect_uri with 51 + | None -> 52 + Errors.invalid_request "redirect_uri required" 53 + | Some uri when uri <> orig_req.redirect_uri -> 54 + Errors.invalid_request "redirect_uri mismatch" 55 + | _ -> 56 + () ) ; 57 + ( match req.code_verifier with 58 + | None -> 59 + Errors.invalid_request "code_verifier required" 60 + | Some verifier -> 61 + let computed = 62 + Digestif.SHA256.digest_string verifier 63 + |> Digestif.SHA256.to_raw_string 64 + |> Base64.encode_exn ~pad:false 65 + in 66 + if orig_req.code_challenge <> computed then 67 + Errors.invalid_request "invalid code_verifier" 68 + ) ; 69 + ( match par_record.dpop_jkt with 70 + | Some stored when stored <> proof.jkt -> 71 + Errors.invalid_request "DPoP key mismatch" 72 + | _ -> 73 + () ) ; 74 + let token_id = 75 + "tok-" 76 + ^ Uuidm.to_string 77 + (Uuidm.v4_gen (Random.get_state ()) ()) 78 + in 79 + let refresh_token = 80 + "ref-" 81 + ^ Uuidm.to_string 82 + (Uuidm.v4_gen (Random.get_state ()) ()) 83 + in 84 + let now_sec = int_of_float (Unix.gettimeofday ()) in 85 + let expires_in = 86 + Constants.access_token_expiry_ms / 1000 87 + in 88 + let exp_sec = now_sec + expires_in in 89 + let expires_at = exp_sec * 1000 in 90 + let claims = 91 + `Assoc 92 + [ ("jti", `String token_id) 93 + ; ("sub", `String did) 94 + ; ("iat", `Int now_sec) 95 + ; ("exp", `Int exp_sec) 96 + ; ("scope", `String orig_req.scope) 97 + ; ("aud", `String ("https://" ^ Env.hostname)) 98 + ; ("cnf", `Assoc [("jkt", `String proof.jkt)]) 99 + ] 100 + in 101 + let access_token = 102 + Jwt.sign_jwt claims ~typ:"at+jwt" Env.jwt_key 103 + in 104 + let%lwt () = 105 + Queries.insert_oauth_token ctx.db 106 + { refresh_token 107 + ; client_id= req.client_id 108 + ; did 109 + ; dpop_jkt= proof.jkt 110 + ; scope= orig_req.scope 111 + ; expires_at } 112 + in 113 + let nonce = Dpop.next_nonce () in 114 + Dream.json 115 + ~headers: 116 + [ ("DPoP-Nonce", nonce) 117 + ; ("Access-Control-Expose-Headers", "DPoP-Nonce") 118 + ; ("Cache-Control", "no-store") ] 119 + @@ Yojson.Safe.to_string 120 + @@ `Assoc 121 + [ ("access_token", `String access_token) 122 + ; ("token_type", `String "DPoP") 123 + ; ("refresh_token", `String refresh_token) 124 + ; ("expires_in", `Int expires_in) 125 + ; ("scope", `String orig_req.scope) 126 + ; ("sub", `String did) ] ) ) ) ) 127 + | "refresh_token" -> ( 128 + match req.refresh_token with 129 + | None -> 130 + Errors.invalid_request "refresh_token required" 131 + | Some refresh_token -> ( 132 + let%lwt token_record = 133 + Queries.get_oauth_token_by_refresh ctx.db refresh_token 134 + in 135 + match token_record with 136 + | None -> 137 + Errors.invalid_request "invalid refresh token" 138 + | Some session -> 139 + if session.client_id <> req.client_id then 140 + Errors.invalid_request "client_id mismatch" 141 + else if session.dpop_jkt <> proof.jkt then 142 + Errors.invalid_request "DPoP key mismatch" 143 + else 144 + let new_token_id = 145 + "tok-" 146 + ^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ()) 147 + in 148 + let new_refresh = 149 + "ref-" 150 + ^ Uuidm.to_string (Uuidm.v4_gen (Random.get_state ()) ()) 151 + in 152 + let now_sec = int_of_float (Unix.gettimeofday ()) in 153 + let expires_in = Constants.access_token_expiry_ms / 1000 in 154 + let exp_sec = now_sec + expires_in in 155 + let new_expires_at = exp_sec * 1000 in 156 + let claims = 157 + `Assoc 158 + [ ("jti", `String new_token_id) 159 + ; ("sub", `String session.did) 160 + ; ("iat", `Int now_sec) 161 + ; ("exp", `Int exp_sec) 162 + ; ("scope", `String session.scope) 163 + ; ("aud", `String ("https://" ^ Env.hostname)) 164 + ; ("cnf", `Assoc [("jkt", `String proof.jkt)]) ] 165 + in 166 + let new_access_token = 167 + Jwt.sign_jwt claims ~typ:"at+jwt" Env.jwt_key 168 + in 169 + let%lwt () = 170 + Queries.update_oauth_token ctx.db 171 + ~old_refresh_token:refresh_token ~new_token_id 172 + ~new_refresh_token:new_refresh 173 + ~expires_at:new_expires_at 174 + in 175 + Dream.json ~headers:[("Cache-Control", "no-store")] 176 + @@ Yojson.Safe.to_string 177 + @@ `Assoc 178 + [ ("access_token", `String new_access_token) 179 + ; ("token_type", `String "DPoP") 180 + ; ("refresh_token", `String new_refresh) 181 + ; ("expires_in", `Int expires_in) 182 + ; ("scope", `String session.scope) 183 + ; ("sub", `String session.did) ] ) ) 184 + | _ -> 185 + Errors.invalid_request ("unsupported grant_type: " ^ req.grant_type) 186 + ) )
+1 -5
pegasus/lib/data_store.ml
··· 116 116 [%rapper 117 117 execute 118 118 {sql| CREATE TABLE IF NOT EXISTS oauth_tokens ( 119 - id INTEGER PRIMARY KEY, 120 - token_id TEXT UNIQUE NOT NULL, 121 119 refresh_token TEXT UNIQUE NOT NULL, 122 120 client_id TEXT NOT NULL, 123 121 did TEXT NOT NULL, 124 122 dpop_jkt TEXT, 125 123 scope TEXT NOT NULL, 126 - created_at INTEGER NOT NULL, 127 - expires_at INTEGER NOT NULL, 128 - last_refreshed_at INTEGER NOT NULL 124 + expires_at INTEGER NOT NULL 129 125 ) 130 126 |sql}] 131 127 () conn
+2
pegasus/lib/oauth/constants.ml
··· 10 10 11 11 let code_expiry_ms = 300_000 12 12 13 + let access_token_expiry_ms = 60 * 60 * 1000 14 + 13 15 let request_uri_prefix = "urn:ietf:params:oauth:request_uri:"
+9 -14
pegasus/lib/oauth/queries.ml
··· 83 83 @@ [%rapper 84 84 execute 85 85 {sql| 86 - INSERT INTO oauth_tokens (token_id, refresh_token, client_id, did, dpop_jkt, scope, created_at, expires_at, last_refreshed_at) 87 - VALUES (%string{token_id}, %string{refresh_token}, %string{client_id}, %string{did}, %string{dpop_jkt}, %string{scope}, %int{created_at}, %int{expires_at}, %int{last_refreshed_at}) 86 + INSERT INTO oauth_tokens (refresh_token, client_id, did, dpop_jkt, scope, expires_at) 87 + VALUES (%string{refresh_token}, %string{client_id}, %string{did}, %string{dpop_jkt}, %string{scope}, %int{expires_at}) 88 88 |sql} 89 89 record_in] 90 90 token ··· 94 94 @@ [%rapper 95 95 get_opt 96 96 {sql| 97 - SELECT @int{id}, @string{token_id}, @string{refresh_token}, @string{client_id}, 98 - @string{did}, @string{dpop_jkt}, @string{scope}, @int{created_at}, 99 - @int{expires_at}, @int{last_refreshed_at} 97 + SELECT @string{refresh_token}, @string{client_id}, @string{did}, 98 + @string{dpop_jkt}, @string{scope}, @int{expires_at} 100 99 FROM oauth_tokens 101 100 WHERE refresh_token = %string{refresh_token} 102 101 |sql} ··· 105 104 106 105 let update_oauth_token conn ~old_refresh_token ~new_token_id ~new_refresh_token 107 106 ~expires_at = 108 - let last_refreshed_at = Util.now_ms () in 109 107 Util.use_pool conn 110 108 @@ [%rapper 111 109 execute ··· 113 111 UPDATE oauth_tokens 114 112 SET token_id = %string{new_token_id}, 115 113 refresh_token = %string{new_refresh_token}, 116 - expires_at = %int{expires_at}, 117 - last_refreshed_at = %int{last_refreshed_at} 114 + expires_at = %int{expires_at} 118 115 WHERE refresh_token = %string{old_refresh_token} 119 116 |sql}] 120 - ~new_token_id ~new_refresh_token ~expires_at ~last_refreshed_at 121 - ~old_refresh_token 117 + ~new_token_id ~new_refresh_token ~expires_at ~old_refresh_token 122 118 123 119 let delete_oauth_token_by_refresh conn refresh_token = 124 120 Util.use_pool conn ··· 134 130 @@ [%rapper 135 131 get_many 136 132 {sql| 137 - SELECT @int{id}, @string{token_id}, @string{refresh_token}, @string{client_id}, 138 - @string{did}, @string{dpop_jkt}, @string{scope}, @int{created_at}, 139 - @int{expires_at}, @int{last_refreshed_at} 133 + SELECT @string{refresh_token}, @string{client_id}, @string{did}, 134 + @string{dpop_jkt}, @string{scope}, @int{expires_at} 140 135 FROM oauth_tokens 141 136 WHERE did = %string{did} 142 - ORDER BY created_at DESC 137 + ORDER BY expires_at ASC 143 138 |sql} 144 139 record_out] 145 140 ~did
+2 -6
pegasus/lib/oauth/types.ml
··· 62 62 [@@deriving yojson {strict= false}] 63 63 64 64 type oauth_token = 65 - { id: int 66 - ; token_id: string 67 - ; refresh_token: string 65 + { refresh_token: string 68 66 ; client_id: string 69 67 ; did: string 70 68 ; dpop_jkt: string 71 69 ; scope: string 72 - ; created_at: int 73 - ; expires_at: int 74 - ; last_refreshed_at: int } 70 + ; expires_at: int } 75 71 [@@deriving yojson {strict= false}]