Google API authentication helpers: service accounts and local OAuth
0
fork

Configure Feed

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

s3, bottler: stream bottle uploads, don't hold the body in memory

New S3.Http.put_object_file takes a file path + env, computes the
SigV4 payload hash by streaming the file once through Digestif
(64 KB buffer, no heap allocation beyond the buffer), then hands
Requests.Body.of_file the same path so the HTTP layer streams the
second pass straight to the socket. A 200 MB bottle no longer has
to fit in the OCaml heap.

Sigv4.sign grows an optional ?payload_hash so callers that hash
the body out-of-band (streaming signers, aws-chunked, unsigned
payload) can override the string-based default without rewriting
the signing pipeline.

Bottler.Upload.put_both swaps Bos.OS.File.read + put_object for
put_object_file, removing the last heap-holds-entire-body
behaviour in the upload path.

+13 -13
+10 -10
lib/gauth.ml
··· 4 4 5 5 module Log = (val Logs.src_log src : Logs.LOG) 6 6 7 - (* Error helpers — keep all the [`Msg] error shapes in one place so the 7 + (* Error helpers -- keep all the [`Msg] error shapes in one place so the 8 8 surface formatting stays consistent. *) 9 9 10 10 let err_msg fmt = Fmt.kstr (fun m -> Error (`Msg m)) fmt ··· 29 29 let err_code_exchange e = 30 30 `Msg (Fmt.str "code exchange failed: %a" Oauth.pp_parse_token_error e) 31 31 32 - (* ── Token abstraction ───────────────────────────────────────────── *) 32 + (* -- Token abstraction --------------------------------------------- *) 33 33 34 34 type refresher = unit -> (string, [ `Msg of string ]) result 35 - (** Custom refresh callback — pluggable to support both user-flow tokens (which 35 + (** Custom refresh callback -- pluggable to support both user-flow tokens (which 36 36 have a refresh_token) and service-account tokens (which re-run the JWT 37 37 bearer exchange). *) 38 38 ··· 46 46 47 47 type token = 48 48 | Oauth_token of Oauth.Token.t 49 - (** User-flow token — delegates refresh to {!Oauth.Token}. *) 50 - | Sa_token of token_state (** Service-account token — custom refresher. *) 49 + (** User-flow token -- delegates refresh to {!Oauth.Token}. *) 50 + | Sa_token of token_state (** Service-account token -- custom refresher. *) 51 51 52 52 let stale ~clock ~threshold = function 53 53 | None -> false ··· 73 73 | Ok s -> s 74 74 | Error (`Msg m) -> Fmt.failwith "Gauth.access: %s" m 75 75 76 - (* ── Service accounts ────────────────────────────────────────────── *) 76 + (* -- Service accounts ---------------------------------------------- *) 77 77 78 78 module Service_account = struct 79 79 type key = { ··· 269 269 Ok (Sa_token state) 270 270 end 271 271 272 - (* ── Local OAuth flow ────────────────────────────────────────────── *) 272 + (* -- Local OAuth flow ---------------------------------------------- *) 273 273 274 274 module Local_flow = struct 275 275 (* Minimal HTTP request parser: reads just enough to extract the request 276 - line. The body is ignored — this handler only serves GET callbacks. *) 276 + line. The body is ignored -- this handler only serves GET callbacks. *) 277 277 let read_request flow = 278 278 let reader = Eio.Buf_read.of_flow flow ~max_size:16_384 in 279 279 Eio.Buf_read.line reader ··· 317 317 Fmt.epr "Open this URL in your browser to authorize:@.@. %s@." url 318 318 319 319 (* Decide how to respond to the browser and what token (if any) resulted. 320 - Returns [(status, html_body, result)] — the caller writes the HTML 320 + Returns [(status, html_body, result)] -- the caller writes the HTML 321 321 and records the result. *) 322 322 let classify_callback ~http ~clock ~client_id ~client_secret ~state ~verifier 323 323 ~redirect_uri line = ··· 424 424 !result 425 425 end 426 426 427 - (* ── Persistence ─────────────────────────────────────────────────── *) 427 + (* -- Persistence --------------------------------------------------- *) 428 428 429 429 type snapshot = { 430 430 access_token : string;
+3 -3
lib/gauth.mli
··· 3 3 Two flows are supported, mirroring the common access patterns for Google 4 4 APIs: 5 5 6 - - {b Service account} — server-to-server authentication using a JSON 6 + - {b Service account} -- server-to-server authentication using a JSON 7 7 service-account key. Implements the JWT bearer grant 8 8 ({{:https://datatracker.ietf.org/doc/html/rfc7523} RFC 7523}) as described 9 9 in 10 10 {{:https://developers.google.com/identity/protocols/oauth2/service-account} 11 11 Google's service-account OAuth guide}. 12 - - {b Local OAuth flow} — interactive sign-in for CLI tools. Spins up a 12 + - {b Local OAuth flow} -- interactive sign-in for CLI tools. Spins up a 13 13 localhost HTTP listener, redirects the user to Google's consent page, 14 14 captures the returned authorization code, and exchanges it for tokens. 15 15 ··· 97 97 98 98 - [port] defaults to [0] (let the OS assign an ephemeral port). 99 99 - [on_url] defaults to printing a clickable URL to stderr. 100 - - [timeout] defaults to 120 seconds — how long to wait for the callback 100 + - [timeout] defaults to 120 seconds -- how long to wait for the callback 101 101 before giving up. *) 102 102 end 103 103