OAuth 2.0 authorization and token exchange
0
fork

Configure Feed

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

Add eqaf dep to opam; make custom_provider private with HTTPS validation

Add eqaf to dune-project/opam deps. Make custom_provider record private
so callers must use the smart constructor that enforces HTTPS URLs.
Update tests to use Oauth.custom_provider and cover empty-state rejection.

+22 -12
+1
dune-project
··· 22 22 (crypto-rng (>= 0.11.0)) 23 23 (digestif (>= 1.0)) 24 24 (base64 (>= 3.0)) 25 + (eqaf (>= 0.9)) 25 26 (ohex (>= 0.2)) 26 27 (logs (>= 0.7)) 27 28 (alcotest :with-test)
+3 -2
lib/oauth.mli
··· 57 57 Userinfo: [id] (int), [username], [email], [name], [avatar_url]. *) 58 58 | Custom of custom_provider (** A custom OAuth 2.0 provider. *) 59 59 60 - and custom_provider = { 60 + and custom_provider = private { 61 61 name : string; 62 62 authorize_url : string; 63 63 token_url : string; ··· 65 65 uid_field : string; (** JSON field containing the unique user identifier. *) 66 66 } 67 67 (** Configuration for a custom OAuth provider not covered by the built-in 68 - variants. 68 + variants. The type is private — use {!custom_provider} to construct values. 69 + Fields are readable for pattern matching. 69 70 70 71 {b Security}: All URLs must use HTTPS. Per 71 72 {{:https://datatracker.ietf.org/doc/html/rfc6749#section-3.1} RFC 6749 §3.1}
+1
oauth.opam
··· 18 18 "crypto-rng" {>= "0.11.0"} 19 19 "digestif" {>= "1.0"} 20 20 "base64" {>= "3.0"} 21 + "eqaf" {>= "0.9"} 21 22 "ohex" {>= "0.2"} 22 23 "logs" {>= "0.7"} 23 24 "alcotest" {with-test}
+10 -2
test/test_github_oauth.ml
··· 38 38 "different states reject" false 39 39 (Oauth.validate_state ~expected:s1 ~actual:s2) 40 40 41 - let test_validate_state_empty () = 41 + let test_validate_state_empty_actual () = 42 42 Alcotest.(check bool) 43 43 "empty vs non-empty rejects" false 44 44 (Oauth.validate_state ~expected:(Oauth.generate_state ()) ~actual:"") 45 + 46 + let test_validate_state_both_empty () = 47 + Alcotest.(check bool) 48 + "empty vs empty rejects" false 49 + (Oauth.validate_state ~expected:"" ~actual:"") 45 50 46 51 let test_validate_state_length_mismatch () = 47 52 Alcotest.(check bool) ··· 290 295 test_validate_state_matching; 291 296 Alcotest.test_case "validate_state mismatch" `Quick 292 297 test_validate_state_mismatch; 293 - Alcotest.test_case "validate_state empty" `Quick test_validate_state_empty; 298 + Alcotest.test_case "validate_state empty actual" `Quick 299 + test_validate_state_empty_actual; 300 + Alcotest.test_case "validate_state both empty" `Quick 301 + test_validate_state_both_empty; 294 302 Alcotest.test_case "validate_state length mismatch" `Quick 295 303 test_validate_state_length_mismatch; 296 304 Alcotest.test_case "github emails verified primary" `Quick
+7 -8
test/test_regressions.ml
··· 115 115 (Oauth.parse_token_response body) 116 116 117 117 let custom name = 118 - Oauth.Custom 119 - { 120 - name; 121 - authorize_url = "https://example.com/auth"; 122 - token_url = "https://example.com/token"; 123 - userinfo_url = "https://example.com/user"; 124 - uid_field = "id"; 125 - } 118 + match 119 + Oauth.custom_provider ~name ~authorize_url:"https://example.com/auth" 120 + ~token_url:"https://example.com/token" 121 + ~userinfo_url:"https://example.com/user" ~uid_field:"id" 122 + with 123 + | Ok p -> Oauth.Custom p 124 + | Error (`Msg msg) -> failwith msg 126 125 127 126 let test_provider_name_is_raw () = 128 127 (* provider_name returns the raw name for DB identity *)