import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:injectable/injectable.dart'; import 'package:jwt_decoder/jwt_decoder.dart'; import 'package:pocketbase/pocketbase.dart'; import 'package:uae_stat/domain/entities/preferences_entity.dart'; import 'package:uae_stat/domain/entities/session_entity.dart'; import 'package:uae_stat/domain/repos/t_auth_repo.dart'; import 'package:uae_stat/domain/use_cases/auth_use_case.dart'; import 'package:uae_stat/infrastructure/services/pocketbase_service.dart'; @dev @prod @Injectable(as: TAuthRepo) class PAuthRepo implements TAuthRepo { @override Future fetchLocallyStored() async { final se = await _LocalAuthRepo.fetchLocallyStored(); if (se == null) return null; PocketBaseService.authStore.save( se.token, RecordModel.fromJson(se.modelJson), ); return se; } SessionEntity _sessionEntityFromPocketbaseRecord(RecordAuth ra) { final decodedToken = JwtDecoder.decode(ra.token); final expInSeconds = decodedToken['exp']; final expInMilliseconds = expInSeconds * 1000; final expiresOn = DateTime.fromMillisecondsSinceEpoch(expInMilliseconds); final se = SessionEntity( id: ra.record!.id, expiresOn: expiresOn, token: ra.token, modelJson: ra.record!.toJson(), ); return se; } @override Future fetchRefreshedSession() async { final user = await PocketBaseService.users.authRefresh().timeout(Duration(seconds: 5)); final se = _sessionEntityFromPocketbaseRecord(user); await _LocalAuthRepo.storeLocally(se); return se; } @override Future login(String email, String pw) async { late final RecordAuth user; try { user = await PocketBaseService.users.authWithPassword( email, pw, ); } on ClientException catch (err) { final message = err.response['message']; final isWrongEmailPw = message == 'Failed to authenticate.'; if (isWrongEmailPw) throw LoginError.invalidEmailPw; rethrow; } final isVerified = user.record!.getBoolValue('verified'); if (!isVerified) throw LoginError.emailAddressNotVerified; final se = _sessionEntityFromPocketbaseRecord(user); await _LocalAuthRepo.storeLocally(se); return se; } @override Future googleAndAppleAuthencation(token) async { late final RecordAuth user; try { user = token; } on ClientException catch (err) { final message = err.response['message']; final isWrongEmailPw = message == 'Failed to authenticate.'; if (isWrongEmailPw) throw LoginError.invalidEmailPw; rethrow; } final isVerified = user.record!.getBoolValue('verified'); if (!isVerified) throw LoginError.emailAddressNotVerified; final se = _sessionEntityFromPocketbaseRecord(user); await _LocalAuthRepo.storeLocally(se); return se; } @override Future logout() { PocketBaseService.authStore.clear(); return _LocalAuthRepo.clear(); } /// throws [RegisterError] @override Future register({ required String name, required String email, required String pw, required PreferencesEntity preferences, }) async { try { await PocketBaseService.users.create( body: { 'email': email, 'password': pw, 'passwordConfirm': pw, 'name': name, ...preferences.toJson(), }, ); } catch (err) { if (err is ClientException) { if (err.response['data']['email']['message'] == 'The email is invalid or already in use.') { throw RegisterError.emailAlreadyInUse; } } } PocketBaseService.users.requestVerification(email); } @override Future requestEmailVerification( String email, ) => PocketBaseService.users.requestVerification(email); @override Future requestPwReset( String email, ) => PocketBaseService.users.requestPasswordReset( email, ); @override bool didSessionExpire() => PocketBaseService.authStore.isValid; } abstract class _LocalAuthRepo { static const _key = 'session'; static const _storage = FlutterSecureStorage(); static Future fetchLocallyStored() async { final e = await _storage.read(key: _key); if (e == null) return null; final se = SessionEntity.fromJson(e); if (se.freshness == SessionFreshness.expired) { await clear(); return null; } return se; } static Future storeLocally( SessionEntity se, ) => _storage.write( key: _key, value: se.toJson(), ); static Future clear() => _storage.delete(key: _key); }