Files
food-ai/client/lib/core/auth/auth_service.dart
dbastrikin 24219b611e feat: implement Iteration 0 foundation (backend + Flutter client)
Backend (Go):
- Project structure with chi router, pgxpool, goose migrations
- JWT auth (access/refresh tokens) with Firebase token verification
- NoopTokenVerifier for local dev without Firebase credentials
- PostgreSQL user repository with atomic profile updates (transactions)
- Mifflin-St Jeor calorie calculation based on profile data
- REST API: POST /auth/login, /auth/refresh, /auth/logout, GET/PUT /profile, GET /health
- Middleware: auth, CORS (localhost wildcard), logging, recovery, request_id
- Unit tests (51 passing) and integration tests (testcontainers)
- Docker Compose setup with postgres healthcheck and graceful shutdown

Flutter client:
- Riverpod state management with GoRouter navigation
- Firebase Auth (email/password + Google sign-in with web popup support)
- Platform-aware API URLs (web/Android/iOS)
- Dio HTTP client with JWT auth interceptor and concurrent refresh handling
- Secure token storage
- Screens: Login, Register, Home (tabs: Menu, Recipes, Products, Profile)
- Unit tests (17 passing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 13:14:58 +02:00

93 lines
2.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:firebase_auth/firebase_auth.dart' as fb;
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:google_sign_in/google_sign_in.dart';
import '../../shared/models/user.dart';
import '../api/api_client.dart';
import 'secure_storage.dart';
class AuthService {
final fb.FirebaseAuth _firebaseAuth;
final ApiClient _apiClient;
final SecureStorageService _storage;
AuthService({
required fb.FirebaseAuth firebaseAuth,
required ApiClient apiClient,
required SecureStorageService storage,
}) : _firebaseAuth = firebaseAuth,
_apiClient = apiClient,
_storage = storage;
Future<User> signInWithGoogle() async {
if (kIsWeb) {
// На вебе используем signInWithPopup — открывает Google popup в браузере
final provider = fb.GoogleAuthProvider();
final userCredential = await _firebaseAuth.signInWithPopup(provider);
return _authenticateWithBackend(userCredential);
} else {
// На мобильных используем google_sign_in
final googleUser = await GoogleSignIn().signIn();
if (googleUser == null) {
throw Exception('Google sign-in cancelled');
}
final googleAuth = await googleUser.authentication;
final credential = fb.GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final userCredential =
await _firebaseAuth.signInWithCredential(credential);
return _authenticateWithBackend(userCredential);
}
}
Future<User> signInWithEmail(String email, String password) async {
final userCredential = await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
return _authenticateWithBackend(userCredential);
}
Future<User> registerWithEmail(
String email, String password, String name) async {
final userCredential = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await userCredential.user!.updateDisplayName(name);
return _authenticateWithBackend(userCredential);
}
Future<User> _authenticateWithBackend(fb.UserCredential credential) async {
final idToken = await credential.user!.getIdToken();
final response = await _apiClient.post('/auth/login', data: {
'firebase_token': idToken,
});
await _storage.saveTokens(
accessToken: response['access_token'],
refreshToken: response['refresh_token'],
);
return User.fromJson(response['user']);
}
Future<void> signOut() async {
try {
await _apiClient.post('/auth/logout');
} catch (_) {
// Don't block logout on backend failure
}
await _firebaseAuth.signOut();
await _storage.clearTokens();
}
Future<bool> isAuthenticated() async {
final token = await _storage.getAccessToken();
return token != null;
}
}