package auth import ( "context" "errors" "fmt" firebase "firebase.google.com/go/v4" fbauth "firebase.google.com/go/v4/auth" "google.golang.org/api/option" "watch-party-backend/internal/config" ) // TokenVerifier hides Firebase client behind a small interface for testing. type TokenVerifier interface { Verify(ctx context.Context, token string) (*fbauth.Token, error) } // AuthClient can verify tokens and mutate Firebase custom claims. type AuthClient interface { TokenVerifier SetAdminClaim(ctx context.Context, uid string) (string, map[string]interface{}, error) } // FirebaseAuth verifies Firebase ID tokens. type FirebaseAuth struct { client *fbauth.Client } // NewFirebaseAuth builds the Firebase client from config. func NewFirebaseAuth(ctx context.Context, cfg config.FirebaseConfig) (*FirebaseAuth, error) { creds, err := cfg.CredentialsBytes() if err != nil { return nil, err } if len(creds) == 0 { return nil, errors.New("firebase credentials empty") } app, err := firebase.NewApp(ctx, &firebase.Config{ProjectID: cfg.ProjectID}, option.WithCredentialsJSON(creds)) if err != nil { return nil, fmt.Errorf("init firebase app: %w", err) } client, err := app.Auth(ctx) if err != nil { return nil, fmt.Errorf("init firebase auth client: %w", err) } return &FirebaseAuth{client: client}, nil } // Verify checks a Firebase ID token and returns its claims. func (f *FirebaseAuth) Verify(ctx context.Context, token string) (*fbauth.Token, error) { return f.client.VerifyIDToken(ctx, token) } // SetAdminClaim sets the "admin" custom claim while preserving existing claims. func (f *FirebaseAuth) SetAdminClaim(ctx context.Context, uid string) (string, map[string]interface{}, error) { user, err := f.client.GetUser(ctx, uid) if err != nil { return "", nil, fmt.Errorf("get user: %w", err) } claims := make(map[string]interface{}, len(user.CustomClaims)+1) for k, v := range user.CustomClaims { claims[k] = v } claims["admin"] = true if err := f.client.SetCustomUserClaims(ctx, uid, claims); err != nil { return "", nil, fmt.Errorf("set custom claims: %w", err) } return user.Email, claims, nil }