Files
superport/lib/data/repositories/auth_repository_impl.dart
JiWoong Sul 655d473413
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
web: migrate health notifications to js_interop; add browser hook
- Replace dart:js with package:js in health_check_service_web.dart\n- Implement showHealthCheckNotification in web/index.html\n- Pin js dependency to ^0.6.7 for flutter_secure_storage_web compatibility

auth: harden AuthInterceptor + tests

- Allow overrideAuthRepository injection for testing\n- Normalize imports to package: paths\n- Add unit test covering token attach, 401→refresh→retry, and failure path\n- Add integration test skeleton gated by env vars

ui/data: map User.companyName to list column

- Add companyName to domain User\n- Map UserDto.company?.name\n- Render companyName in user_list

cleanup: remove legacy equipment table + unused code; minor warnings

- Remove _buildFlexibleTable and unused helpers\n- Remove unused zipcode details and cache retry constant\n- Fix null-aware and non-null assertions\n- Address child-last warnings in administrator dialog

docs: update AGENTS.md session context
2025-09-08 17:39:00 +09:00

251 lines
8.0 KiB
Dart

import 'package:dartz/dartz.dart';
import 'package:injectable/injectable.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../core/errors/failures.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/remote/auth_remote_datasource.dart';
import '../models/auth/auth_user.dart';
import '../models/auth/login_request.dart';
import '../models/auth/login_response.dart';
import '../models/auth/logout_request.dart';
import '../models/auth/refresh_token_request.dart';
import '../models/auth/token_response.dart';
/// 인증 Repository 구현체
/// JWT 토큰 기반 인증 시스템을 관리하며 SharedPreferences를 사용해 토큰을 저장
@Injectable(as: AuthRepository)
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
final SharedPreferences sharedPreferences;
// SharedPreferences 키 상수
static const String _keyAccessToken = 'access_token';
static const String _keyRefreshToken = 'refresh_token';
static const String _keyUserData = 'user_data';
AuthRepositoryImpl({
required this.remoteDataSource,
required this.sharedPreferences,
});
@override
Future<Either<Failure, LoginResponse>> login(LoginRequest loginRequest) async {
try {
final result = await remoteDataSource.login(loginRequest);
return result.fold(
(failure) => Left(failure),
(loginResponse) async {
// 로그인 성공 시 토큰과 사용자 정보를 로컬에 저장
await _saveTokens(loginResponse.accessToken, loginResponse.refreshToken);
await _saveUserData(loginResponse.user);
return Right(loginResponse);
},
);
} catch (e) {
return Left(ServerFailure(
message: '로그인 처리 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, void>> logout() async {
try {
// 로컬에 저장된 리프레시 토큰으로 로그아웃 요청 생성
final refreshToken = await _getRefreshToken();
if (refreshToken == null) {
// 토큰이 없으면 로컬 데이터만 삭제하고 성공 처리
await _clearLocalData();
return const Right(null);
}
final logoutRequest = LogoutRequest(refreshToken: refreshToken);
final result = await remoteDataSource.logout(logoutRequest);
return result.fold(
(failure) async {
// 서버 로그아웃 실패해도 로컬 데이터는 삭제
await _clearLocalData();
return Left(failure);
},
(_) async {
// 성공 시 로컬 데이터 삭제
await _clearLocalData();
return const Right(null);
},
);
} catch (e) {
// 오류 발생해도 로컬 데이터는 삭제
await _clearLocalData();
return Left(ServerFailure(
message: '로그아웃 처리 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, TokenResponse>> refreshToken(RefreshTokenRequest refreshRequest) async {
try {
final result = await remoteDataSource.refreshToken(refreshRequest);
return result.fold(
(failure) => Left(failure),
(tokenResponse) async {
// 새 토큰 저장
await _saveTokens(tokenResponse.accessToken, tokenResponse.refreshToken);
return Right(tokenResponse);
},
);
} catch (e) {
return Left(ServerFailure(
message: '토큰 갱신 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, AuthUser>> getCurrentUser() async {
try {
final userData = sharedPreferences.getString(_keyUserData);
if (userData == null) {
return const Left(AuthenticationFailure(
message: '저장된 사용자 정보가 없습니다.',
));
}
// JSON 문자열을 AuthUser 객체로 변환
final user = AuthUser.fromJson(
Map<String, dynamic>.from(
// JSON 디코딩 처리 필요 시 여기에 추가
{} // TODO: JSON 디코딩 로직 추가
)
);
return Right(user);
} catch (e) {
return Left(ServerFailure(
message: '사용자 정보 조회 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, bool>> isAuthenticated() async {
try {
final accessToken = await _getAccessToken();
final refreshToken = await _getRefreshToken();
// 액세스 토큰과 리프레시 토큰이 모두 있으면 인증된 것으로 간주
final isAuth = accessToken != null && refreshToken != null;
return Right(isAuth);
} catch (e) {
return Left(ServerFailure(
message: '인증 상태 확인 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, void>> changePassword(String currentPassword, String newPassword) async {
// TODO: 비밀번호 변경 API가 구현되면 추가
return const Left(ServerFailure(
message: '비밀번호 변경 기능은 아직 구현되지 않았습니다.',
));
}
@override
Future<Either<Failure, void>> requestPasswordReset(String email) async {
// TODO: 비밀번호 재설정 API가 구현되면 추가
return const Left(ServerFailure(
message: '비밀번호 재설정 기능은 아직 구현되지 않았습니다.',
));
}
@override
Future<Either<Failure, bool>> validateSession() async {
try {
final accessToken = await _getAccessToken();
if (accessToken == null) {
return const Right(false);
}
// TODO: 서버에서 세션 유효성 검증 API가 있으면 호출
// 현재는 토큰 존재 여부만 확인
return const Right(true);
} catch (e) {
return Left(ServerFailure(
message: '세션 유효성 검증 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, String?>> getStoredRefreshToken() async {
try {
final token = await _getRefreshToken();
return Right(token);
} catch (e) {
return Left(ServerFailure(
message: '리프레시 토큰 조회 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, String?>> getStoredAccessToken() async {
try {
final token = await _getAccessToken();
return Right(token);
} catch (e) {
return Left(ServerFailure(
message: '액세스 토큰 조회 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
@override
Future<Either<Failure, void>> clearLocalSession() async {
try {
await _clearLocalData();
return const Right(null);
} catch (e) {
return Left(ServerFailure(
message: '로컬 세션 정리 중 오류가 발생했습니다: ${e.toString()}',
));
}
}
// Private 헬퍼 메서드들
/// 액세스 토큰과 리프레시 토큰을 로컬에 저장
Future<void> _saveTokens(String accessToken, String refreshToken) async {
await sharedPreferences.setString(_keyAccessToken, accessToken);
await sharedPreferences.setString(_keyRefreshToken, refreshToken);
}
/// 사용자 데이터를 로컬에 저장
Future<void> _saveUserData(AuthUser user) async {
// TODO: JSON 인코딩 로직 구현
await sharedPreferences.setString(_keyUserData, user.toJson().toString());
}
/// 액세스 토큰 조회
Future<String?> _getAccessToken() async {
return sharedPreferences.getString(_keyAccessToken);
}
/// 리프레시 토큰 조회
Future<String?> _getRefreshToken() async {
return sharedPreferences.getString(_keyRefreshToken);
}
/// 로컬 데이터 전체 삭제
Future<void> _clearLocalData() async {
await sharedPreferences.remove(_keyAccessToken);
await sharedPreferences.remove(_keyRefreshToken);
await sharedPreferences.remove(_keyUserData);
}
}