Move all business-logic packages from internal/ root into internal/domain/: auth, cuisine, diary, dish, home, ingredient, language, menu, product, recipe, recognition, recommendation, savedrecipe, tag, units, user Rename model.go → entity.go in packages that hold domain entities: diary, dish, home, ingredient, menu, product, recipe, savedrecipe, user Update all import paths accordingly (adapters, infra/server, cmd/server, tests). No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
2.7 KiB
Go
89 lines
2.7 KiB
Go
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
|
|
}
|