diff options
| author | Guillermo Serrahima <dev@vylion.com> | 2026-04-11 07:40:30 +0200 |
|---|---|---|
| committer | Guillermo Serrahima <dev@vylion.com> | 2026-04-11 23:39:19 +0200 |
| commit | 9f0a9a6779f00786e4728ad5c3fad36814a293b0 (patch) | |
| tree | 4b16d535c5d41ef46980608a88b7581be3c2bfd0 /backend/auth/oauth.go | |
| parent | f6d60077b7cfc0d589fa7961e2bb59aea9f993a3 (diff) | |
| download | llostre-9f0a9a6779f00786e4728ad5c3fad36814a293b0.tar.xz llostre-9f0a9a6779f00786e4728ad5c3fad36814a293b0.zip | |
project restructure
Diffstat (limited to 'backend/auth/oauth.go')
| -rw-r--r-- | backend/auth/oauth.go | 221 |
1 files changed, 0 insertions, 221 deletions
diff --git a/backend/auth/oauth.go b/backend/auth/oauth.go deleted file mode 100644 index a1b0419..0000000 --- a/backend/auth/oauth.go +++ /dev/null @@ -1,221 +0,0 @@ -package auth - -import ( - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" -) - -type OAuthConfig struct { - ClientID string - RedirectURI string - Scopes []string -} - -type OAuthState struct { - State string - CodeVerifier string - DID string - PDS string - AccessToken string - RefreshToken string - ExpiresAt time.Time - KeyPair *DPoPKeyPair -} - -type OAuthMetadata struct { - Issuer string `json:"issuer"` - AuthorizationEndpoint string `json:"authorization_endpoint"` - TokenEndpoint string `json:"token_endpoint"` - Scopes []string `json:"scopes_supported"` -} - -type TokenResponse struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` -} - -func (s *OAuthState) Copy() *OAuthState { - return &OAuthState{ - State: s.State, - DID: s.DID, - PDS: s.PDS, - AccessToken: s.AccessToken, - RefreshToken: s.RefreshToken, - ExpiresAt: s.ExpiresAt, - KeyPair: s.KeyPair, - } -} - -func GenerateRandomToken() (string, error) { - b := make([]byte, 32) - _, err := rand.Read(b) - if err != nil { - return "", err - } - return base64.RawURLEncoding.EncodeToString(b), nil -} - -func GenerateCodeChallenge(verifier string) string { - h := sha256.Sum256([]byte(verifier)) - return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(h[:]) -} - -func FetchOAuthMetadata(pdsURL string) (*OAuthMetadata, error) { - // first get the resource metadata to find the auth server - resourceURL := fmt.Sprintf("%s/.well-known/oauth-protected-resource", pdsURL) - fmt.Printf("Fetching oauth metadata from: %s\n", resourceURL) - resp, err := http.Get(resourceURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var resource struct { - AuthorizationServers []string `json:"authorization_servers"` - } - if err := json.NewDecoder(resp.Body).Decode(&resource); err != nil { - return nil, err - } - - if len(resource.AuthorizationServers) == 0 { - return nil, fmt.Errorf("no authorization servers found") - } - - // now fetch the actual oauth metadata from the auth server - authServer := resource.AuthorizationServers[0] - metadataURL := fmt.Sprintf("%s/.well-known/oauth-authorization-server", authServer) - - resp, err = http.Get(metadataURL) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - var metadata OAuthMetadata - if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil { - return nil, err - } - - return &metadata, nil -} - -func BuildAuthorizationURL(metadata *OAuthMetadata, config *OAuthConfig, state, codeChallenge, handle string) string { - params := url.Values{ - "response_type": {"code"}, - "client_id": {config.ClientID}, - "redirect_uri": {config.RedirectURI}, - "state": {state}, - "code_challenge": {codeChallenge}, - "code_challenge_method": {"S256"}, - "scope": {"atproto"}, - "login_hint": {handle}, - } - - return metadata.AuthorizationEndpoint + "?" + params.Encode() -} - -func postWithDPoP(url string, data url.Values, keypair *DPoPKeyPair) (*TokenResponse, error) { - dpopProof, err := keypair.GenerateDPoPProof("POST", url, "") - if err != nil { - return nil, err - } - - req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("DPoP", dpopProof) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode == 400 { - body, _ := io.ReadAll(resp.Body) - bodyStr := string(body) - - if strings.Contains(bodyStr, "use_dpop_nonce") { - nonce := resp.Header.Get("DPoP-Nonce") - if nonce == "" { - return nil, fmt.Errorf("server requires nonce but didn't provide one") - } - - dpopProof, err = keypair.GenerateDPoPProof("POST", url, nonce) - if err != nil { - return nil, err - } - - retryReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) - if err != nil { - return nil, err - } - retryReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") - retryReq.Header.Set("DPoP", dpopProof) - - resp, err = http.DefaultClient.Do(retryReq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - } else { - return nil, fmt.Errorf("request failed: 400 - %s", bodyStr) - } - } - - if resp.StatusCode != 200 { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("request failed: %d - %s", resp.StatusCode, string(body)) - } - - var tokens TokenResponse - if err := json.NewDecoder(resp.Body).Decode(&tokens); err != nil { - return nil, err - } - - return &tokens, nil -} - -func ExchangeCodeForTokens(pdsURL, code, verifier, callbackURL string, keypair *DPoPKeyPair) (*TokenResponse, error) { - metadata, err := FetchOAuthMetadata(pdsURL) - if err != nil { - return nil, err - } - - data := url.Values{ - "grant_type": {"authorization_code"}, - "code": {code}, - "redirect_uri": {callbackURL}, - "code_verifier": {verifier}, - "client_id": {callbackURL}, - } - - return postWithDPoP(metadata.TokenEndpoint, data, keypair) -} - -func RefreshAccessToken(pdsURL, refreshToken, callbackURL string, keypair *DPoPKeyPair) (*TokenResponse, error) { - metadata, err := FetchOAuthMetadata(pdsURL) - if err != nil { - return nil, err - } - - data := url.Values{ - "grant_type": {"refresh_token"}, - "refresh_token": {refreshToken}, - "client_id": {callbackURL}, - } - - return postWithDPoP(metadata.TokenEndpoint, data, keypair) -} |
