User authentication and session management for web applications
0
fork

Configure Feed

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

Validate redirect_uri with abstract type to prevent open redirects

Introduce Oauth.redirect_uri abstract type with a smart constructor
that enforces HTTPS (or http://localhost per RFC 8252 §7.3) and
rejects fragments (RFC 6749 §3.1.2). The type system now prevents
passing unvalidated, user-controlled strings as the redirect target
(RFC 6749 §10.15).

authorization_url and exchange_form_body now take redirect_uri instead
of string. All callers updated. 6 new tests cover the validation rules.

+14 -5
+12 -4
lib/auth.ml
··· 307 307 (* ── Routes ──────────────────────────────────────────────────────── *) 308 308 309 309 (** GET /auth/signin — redirect to OAuth provider *) 310 + let oauth_redirect_uri (cfg : config) = 311 + let slug = Oauth.provider_slug cfg.session.oauth_provider in 312 + let s = cfg.session.base_url ^ "/auth/" ^ slug ^ "/callback" in 313 + match Oauth.redirect_uri s with 314 + | Ok uri -> uri 315 + | Error (`Msg msg) -> 316 + (* base_url is already validated as HTTPS in config, so this cannot 317 + fail in normal use. *) 318 + invalid_arg ("Auth: invalid redirect_uri: " ^ msg) 319 + 310 320 let signin_route (cfg : config) (_req : Respond.get_request) = 311 321 let state = Oauth.generate_state () in 312 322 let signed_state = Csrf.sign_state ~secret:cfg.session.cookie_secret state in 313 - let slug = Oauth.provider_slug cfg.session.oauth_provider in 314 - let redirect_uri = cfg.session.base_url ^ "/auth/" ^ slug ^ "/callback" in 323 + let redirect_uri = oauth_redirect_uri cfg in 315 324 let scope = Oauth.default_scope cfg.session.oauth_provider in 316 325 let url = 317 326 Oauth.authorization_url cfg.session.oauth_provider ~client_id:cfg.client_id ··· 320 329 Respond.Response.redirect url 321 330 322 331 let exchange_code (cfg : config) ~code = 323 - let slug = Oauth.provider_slug cfg.session.oauth_provider in 324 - let redirect_uri = cfg.session.base_url ^ "/auth/" ^ slug ^ "/callback" in 332 + let redirect_uri = oauth_redirect_uri cfg in 325 333 let token_url = Oauth.token_url cfg.session.oauth_provider in 326 334 let headers = 327 335 Headers.of_list
+2 -1
test/test_auth.ml
··· 329 329 let test_exchange_form_includes_grant_type () = 330 330 let body = 331 331 Oauth.exchange_form_body ~client_id:"cid" ~client_secret:"sec" ~code:"abc" 332 - ~redirect_uri:"https://app.com/cb" () 332 + ~redirect_uri:(Oauth.redirect_uri "https://app.com/cb" |> Result.get_ok) 333 + () 333 334 in 334 335 Alcotest.(check bool) 335 336 "has grant_type" true