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.
at main 66 lines 1.7 kB view raw
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}