Stateless auth proxy that converts AT Protocol native apps from public to confidential OAuth clients. Deploy once, get 180-day refresh tokens instead of 24-hour ones.
1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "net/http"
8 "net/url"
9 "strings"
10 "time"
11)
12
13type upstreamResponse struct {
14 statusCode int
15 headers http.Header
16 body []byte
17}
18
19var upstreamClient = newPublicHTTPClient(30 * time.Second)
20
21func PostForm(ctx context.Context, upstreamURL string, formParams url.Values, dpopHeader string) (*upstreamResponse, error) {
22 req, err := http.NewRequestWithContext(ctx, http.MethodPost, upstreamURL, strings.NewReader(formParams.Encode()))
23 if err != nil {
24 return nil, fmt.Errorf("failed to create upstream request: %w", err)
25 }
26
27 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
28 if dpopHeader != "" {
29 req.Header.Set("DPoP", dpopHeader)
30 }
31
32 resp, err := upstreamClient.Do(req)
33 if err != nil {
34 return nil, fmt.Errorf("upstream request failed: %w", err)
35 }
36 defer resp.Body.Close()
37
38 body, err := io.ReadAll(io.LimitReader(resp.Body, maxUpstreamResponseBodySize+1))
39 if err != nil {
40 return nil, fmt.Errorf("failed to read upstream response body: %w", err)
41 }
42 if len(body) > maxUpstreamResponseBodySize {
43 return nil, fmt.Errorf("upstream response body exceeded %d bytes", maxUpstreamResponseBodySize)
44 }
45
46 return &upstreamResponse{
47 statusCode: resp.StatusCode,
48 headers: resp.Header.Clone(),
49 body: body,
50 }, nil
51}
52
53func WriteProxiedResponse(w http.ResponseWriter, resp *upstreamResponse) error {
54 for key, values := range resp.headers {
55 for _, value := range values {
56 w.Header().Add(key, value)
57 }
58 }
59
60 w.WriteHeader(resp.statusCode)
61 if _, err := w.Write(resp.body); err != nil {
62 return fmt.Errorf("failed to write response body: %w", err)
63 }
64
65 return nil
66}