package firebase import ( "context" "encoding/json" "fmt" "log/slog" "os" firebasesdk "firebase.google.com/go/v4" firebaseAuth "firebase.google.com/go/v4/auth" "google.golang.org/api/option" "github.com/food-ai/backend/internal/domain/auth" ) // Auth is the Firebase-backed implementation of auth.TokenVerifier. type Auth struct { client *firebaseAuth.Client } // noopAuth is used in local development when Firebase credentials are not available. // It rejects all tokens with a clear error message. type noopAuth struct{} func (noopAuthInstance *noopAuth) VerifyToken(_ context.Context, _ string) (uid, email, name, avatarURL string, verifyError error) { return "", "", "", "", fmt.Errorf("Firebase auth is not configured (running in noop mode)") } // NewAuthOrNoop tries to initialize Firebase auth from the credentials file. // If the file is missing, empty, or not a valid service account, it logs a warning // and returns a noop verifier that rejects all tokens. func NewAuthOrNoop(credentialsFile string) (auth.TokenVerifier, error) { data, readError := os.ReadFile(credentialsFile) if readError != nil || len(data) == 0 { slog.Warn("firebase credentials not found, running without Firebase auth", "file", credentialsFile) return &noopAuth{}, nil } var creds map[string]interface{} if unmarshalError := json.Unmarshal(data, &creds); unmarshalError != nil || creds["type"] == nil { slog.Warn("firebase credentials invalid, running without Firebase auth", "file", credentialsFile) return &noopAuth{}, nil } firebaseAuth, newAuthError := newAuth(credentialsFile) if newAuthError != nil { return nil, newAuthError } return firebaseAuth, nil } func newAuth(credentialsFile string) (*Auth, error) { opt := option.WithCredentialsFile(credentialsFile) app, appError := firebasesdk.NewApp(context.Background(), nil, opt) if appError != nil { return nil, appError } client, clientError := app.Auth(context.Background()) if clientError != nil { return nil, clientError } return &Auth{client: client}, nil } // VerifyToken verifies the given Firebase ID token and returns the user's claims. func (firebaseAuthInstance *Auth) VerifyToken(requestContext context.Context, idToken string) (uid, email, name, avatarURL string, verifyError error) { token, verifyError := firebaseAuthInstance.client.VerifyIDToken(requestContext, idToken) if verifyError != nil { return "", "", "", "", verifyError } uid = token.UID if value, ok := token.Claims["email"].(string); ok { email = value } if value, ok := token.Claims["name"].(string); ok { name = value } if value, ok := token.Claims["picture"].(string); ok { avatarURL = value } return uid, email, name, avatarURL, nil }