aboutsummaryrefslogtreecommitdiff
path: root/backend/auth
diff options
context:
space:
mode:
Diffstat (limited to 'backend/auth')
-rw-r--r--backend/auth/dpop.go83
-rw-r--r--backend/auth/oauth.go221
2 files changed, 0 insertions, 304 deletions
diff --git a/backend/auth/dpop.go b/backend/auth/dpop.go
deleted file mode 100644
index 2a8f6ed..0000000
--- a/backend/auth/dpop.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package auth
-
-import (
- "crypto"
- "crypto/ecdsa"
- "crypto/elliptic"
- "crypto/rand"
- "encoding/base64"
- "time"
-
- "github.com/golang-jwt/jwt/v5"
-)
-
-type DPoPKeyPair struct {
- PrivateKey *ecdsa.PrivateKey
- PublicKey *ecdsa.PublicKey
- JWK map[string]string
-}
-
-func GenerateDPoPKeyPair() (*DPoPKeyPair, error) {
- privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
- if err != nil {
- return nil, err
- }
-
- jwk := map[string]string{
- "kty": "EC",
- "crv": "P-256",
- "x": base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.X.Bytes()),
- "y": base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.Y.Bytes()),
- }
-
- return &DPoPKeyPair{
- PrivateKey: privateKey,
- PublicKey: &privateKey.PublicKey,
- JWK: jwk,
- }, nil
-}
-
-func (kp *DPoPKeyPair) GenerateDPoPProof(method, url, nonce string) (string, error) {
- // generate unique ID for this proof
- jti, err := GenerateRandomToken()
- if err != nil {
- return "", err
- }
-
- // create JWT header with JWK
- header := map[string]interface{}{
- "typ": "dpop+jwt",
- "alg": "ES256",
- "jwk": kp.JWK,
- }
-
- // create JWT claims
- claims := jwt.MapClaims{
- "jti": jti,
- "htm": method, // HTTP method (GET, POST, etc)
- "htu": url, // HTTP URL (the endpoint you're calling)
- "iat": time.Now().Unix(),
- }
-
- // add nonce if provided
- if nonce != "" {
- claims["nonce"] = nonce
- }
-
- // create token with custom header
- token := jwt.NewWithClaims(&jwt.SigningMethodECDSA{
- Name: "ES256",
- Hash: crypto.SHA256,
- KeySize: 32,
- CurveBits: 256,
- }, claims)
- token.Header = header
-
- // sign with private key
- tokenString, err := token.SignedString(kp.PrivateKey)
- if err != nil {
- return "", err
- }
-
- return tokenString, nil
-}
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)
-}