package auth import ( "crypto/ecdh" "crypto/ecdsa" "crypto/x509" "fmt" "time" "github.com/bluesky-social/indigo/atproto/atcrypto" "github.com/golang-jwt/jwt/v5" ) // CreateAppviewServiceToken creates a short-lived ES256 JWT for appview→hold communication. // The token authenticates the appview when calling hold XRPC endpoints like updateCrewTier. // // Claims: // - iss: appview DID (e.g. did:web:atcr.io) // - aud: hold DID (e.g. did:web:hold01.atcr.io) // - sub: user DID being acted upon // - exp: now + 60s // - iat: now func CreateAppviewServiceToken(privateKey *atcrypto.PrivateKeyP256, appviewDID, holdDID, userDID string) (string, error) { now := time.Now() claims := jwt.RegisteredClaims{ Issuer: appviewDID, Audience: jwt.ClaimStrings{holdDID}, Subject: userDID, ExpiresAt: jwt.NewNumericDate(now.Add(60 * time.Second)), IssuedAt: jwt.NewNumericDate(now), } token := jwt.NewWithClaims(jwt.SigningMethodES256, claims) ecKey, err := P256ToECDSA(privateKey) if err != nil { return "", fmt.Errorf("failed to extract ECDSA key: %w", err) } signed, err := token.SignedString(ecKey) if err != nil { return "", fmt.Errorf("failed to sign token: %w", err) } return signed, nil } // P256ToECDSA converts an atcrypto P-256 private key to a stdlib *ecdsa.PrivateKey. // This is needed because golang-jwt requires stdlib crypto types, while atcrypto // wraps them in its own types. We re-parse via PKCS8 encoding round-trip. func P256ToECDSA(key *atcrypto.PrivateKeyP256) (*ecdsa.PrivateKey, error) { rawBytes := key.Bytes() // 32-byte raw scalar // Parse raw bytes as ecdh key, then convert via PKCS8 round-trip (same as atcrypto does) ecdhKey, err := ecdh.P256().NewPrivateKey(rawBytes) if err != nil { return nil, fmt.Errorf("failed to parse P-256 raw bytes: %w", err) } pkcs8, err := x509.MarshalPKCS8PrivateKey(ecdhKey) if err != nil { return nil, fmt.Errorf("failed to marshal PKCS8: %w", err) } parsed, err := x509.ParsePKCS8PrivateKey(pkcs8) if err != nil { return nil, fmt.Errorf("failed to parse PKCS8: %w", err) } ecdsaKey, ok := parsed.(*ecdsa.PrivateKey) if !ok { return nil, fmt.Errorf("parsed key is not ECDSA") } return ecdsaKey, nil }