OAuth 2.0 authorization and token exchange
0
fork

Configure Feed

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

Sanitize custom OAuth provider names for URL path safety

provider_name now slugifies Custom names to [a-z0-9-] per RFC 3986,
preventing broken routes from names like "corp/sso" or "Acme SSO".
Non-ASCII bytes are treated as separators; empty results fall back
to "custom".

+63 -3
+27 -1
lib/oauth.ml
··· 16 16 uid_field : string; 17 17 } 18 18 19 + (* Sanitize a string for use as a URL path segment per RFC 3986 §3.3: 20 + lowercase, keep only unreserved chars [a-z0-9-], collapse runs of 21 + dashes, strip leading/trailing dashes. Non-ASCII bytes (UTF-8) are 22 + treated as separators (slugified), not percent-encoded, to keep 23 + routes readable. Falls back to "custom" if the result is empty. *) 24 + let path_safe s = 25 + let len = String.length s in 26 + let buf = Buffer.create len in 27 + let prev_dash = ref true in 28 + for i = 0 to len - 1 do 29 + match Char.lowercase_ascii s.[i] with 30 + | ('a' .. 'z' | '0' .. '9') as c -> 31 + Buffer.add_char buf c; 32 + prev_dash := false 33 + | _ -> 34 + if not !prev_dash then Buffer.add_char buf '-'; 35 + prev_dash := true 36 + done; 37 + let result = Buffer.contents buf in 38 + let rlen = String.length result in 39 + let result = 40 + if rlen > 0 && result.[rlen - 1] = '-' then String.sub result 0 (rlen - 1) 41 + else result 42 + in 43 + if result = "" then "custom" else result 44 + 19 45 let provider_name = function 20 46 | Github -> "github" 21 47 | Google -> "google" 22 48 | Gitlab -> "gitlab" 23 - | Custom c -> c.name 49 + | Custom c -> path_safe c.name 24 50 25 51 let authorize_url = function 26 52 | Github -> "https://github.com/login/oauth/authorize"
+3 -2
lib/oauth.mli
··· 56 56 variants. *) 57 57 58 58 val provider_name : provider -> string 59 - (** [provider_name p] is the lowercase name (["github"], ["google"], ["gitlab"], 60 - or the custom name). *) 59 + (** [provider_name p] is a path-safe lowercase slug (["github"], ["google"], 60 + ["gitlab"], or the custom name sanitized to [[a-z0-9-]]). Safe for use as a 61 + URL path segment. *) 61 62 62 63 val authorize_url : provider -> string 63 64 (** [authorize_url p] is the authorization endpoint URL. *)
+33
test/test_regressions.ml
··· 105 105 "access_token wrong type" (Error Oauth.Invalid_token_format) 106 106 (Oauth.parse_token_response body) 107 107 108 + let custom name = 109 + Oauth.Custom 110 + { 111 + name; 112 + authorize_url = "https://example.com/auth"; 113 + token_url = "https://example.com/token"; 114 + userinfo_url = "https://example.com/user"; 115 + uid_field = "id"; 116 + } 117 + 118 + let test_provider_name_path_safe () = 119 + Alcotest.(check string) "builtin" "github" (Oauth.provider_name Oauth.Github); 120 + Alcotest.(check string) 121 + "slash" "corp-sso" 122 + (Oauth.provider_name (custom "corp/sso")); 123 + Alcotest.(check string) 124 + "spaces" "acme-sso" 125 + (Oauth.provider_name (custom "Acme SSO")); 126 + Alcotest.(check string) 127 + "already clean" "myidp" 128 + (Oauth.provider_name (custom "myidp")); 129 + Alcotest.(check string) 130 + "special chars" "my-cool-provider" 131 + (Oauth.provider_name (custom "My Cool_Provider!")); 132 + Alcotest.(check string) 133 + "non-ascii fallback" "custom" 134 + (Oauth.provider_name (custom "\xe4\xbc\x81\xe6\xa5\xad")); 135 + Alcotest.(check string) 136 + "mixed utf8" "sso" 137 + (Oauth.provider_name (custom "\xe4\xbc\x81\xe6\xa5\xadSSO")) 138 + 108 139 let test_readme_uses_current_api_names () = 109 140 let readme = read_file [ "README.md"; "ocaml-oauth/README.md" ] in 110 141 Alcotest.(check bool) ··· 150 181 test_parse_token_response_missing_access_token; 151 182 Alcotest.test_case "parse_token_response invalid format" `Quick 152 183 test_parse_token_response_invalid_format; 184 + Alcotest.test_case "provider_name is path-safe" `Quick 185 + test_provider_name_path_safe; 153 186 ] )