feat: 切换邮箱认证并重构前后端启动与门禁
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import '../../../../data/network/api_client.dart';
|
||||
import '../models/session_response.dart';
|
||||
|
||||
class AuthApi {
|
||||
AuthApi({required ApiClient apiClient}) : _apiClient = apiClient;
|
||||
|
||||
final ApiClient _apiClient;
|
||||
|
||||
Future<void> sendOtp({required String email}) async {
|
||||
await _apiClient.postNoContent(
|
||||
'/api/v1/auth/otp/send',
|
||||
data: {'email': email},
|
||||
);
|
||||
}
|
||||
|
||||
Future<SessionResponse> createEmailSession({
|
||||
required String email,
|
||||
required String token,
|
||||
}) async {
|
||||
final json = await _apiClient.postJson(
|
||||
'/api/v1/auth/email-session',
|
||||
data: {'email': email, 'token': token},
|
||||
);
|
||||
return SessionResponse.fromJson(json);
|
||||
}
|
||||
|
||||
Future<void> deleteSession({required String refreshToken}) async {
|
||||
await _apiClient.deleteNoContent(
|
||||
'/api/v1/auth/sessions',
|
||||
data: {'refresh_token': refreshToken},
|
||||
);
|
||||
}
|
||||
|
||||
Future<SessionResponse> refreshSession({required String refreshToken}) async {
|
||||
final json = await _apiClient.postJson(
|
||||
'/api/v1/auth/sessions/refresh',
|
||||
data: {'refresh_token': refreshToken},
|
||||
);
|
||||
return SessionResponse.fromJson(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
class AuthUser {
|
||||
const AuthUser({required this.id, required this.email});
|
||||
|
||||
final String id;
|
||||
final String email;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
class SessionResponse {
|
||||
SessionResponse({
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
required this.expiresIn,
|
||||
required this.tokenType,
|
||||
required this.userId,
|
||||
required this.userEmail,
|
||||
});
|
||||
|
||||
final String accessToken;
|
||||
final String refreshToken;
|
||||
final int expiresIn;
|
||||
final String tokenType;
|
||||
final String userId;
|
||||
final String userEmail;
|
||||
|
||||
factory SessionResponse.fromJson(Map<String, dynamic> json) {
|
||||
final user = (json['user'] as Map<String, dynamic>?) ?? <String, dynamic>{};
|
||||
final accessToken = json['access_token'] as String?;
|
||||
final refreshToken = json['refresh_token'] as String?;
|
||||
final expiresIn = json['expires_in'] as int?;
|
||||
final tokenType = json['token_type'] as String?;
|
||||
final userId = user['id'] as String?;
|
||||
final userEmail = user['email'] as String?;
|
||||
|
||||
if (accessToken == null ||
|
||||
refreshToken == null ||
|
||||
expiresIn == null ||
|
||||
tokenType == null ||
|
||||
userId == null ||
|
||||
userEmail == null) {
|
||||
throw const FormatException('Invalid session response payload');
|
||||
}
|
||||
|
||||
return SessionResponse(
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expiresIn: expiresIn,
|
||||
tokenType: tokenType,
|
||||
userId: userId,
|
||||
userEmail: userEmail,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import '../../../../core/auth/session_store.dart';
|
||||
import '../apis/auth_api.dart';
|
||||
import '../models/auth_user.dart';
|
||||
|
||||
abstract class AuthRepository {
|
||||
Future<void> sendOtp(String email);
|
||||
|
||||
Future<AuthUser> loginWithEmailOtp({
|
||||
required String email,
|
||||
required String otp,
|
||||
});
|
||||
|
||||
Future<AuthUser?> recoverSession();
|
||||
|
||||
Future<void> logout();
|
||||
|
||||
Future<void> clearLocalSession();
|
||||
}
|
||||
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
AuthRepositoryImpl({
|
||||
required AuthApi authApi,
|
||||
required SessionStore sessionStore,
|
||||
}) : _authApi = authApi,
|
||||
_sessionStore = sessionStore;
|
||||
|
||||
final AuthApi _authApi;
|
||||
final SessionStore _sessionStore;
|
||||
|
||||
@override
|
||||
Future<void> sendOtp(String email) async {
|
||||
await _authApi.sendOtp(email: email);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthUser> loginWithEmailOtp({
|
||||
required String email,
|
||||
required String otp,
|
||||
}) async {
|
||||
final session = await _authApi.createEmailSession(email: email, token: otp);
|
||||
await _sessionStore.saveToken(session.accessToken);
|
||||
await _sessionStore.saveRefreshToken(session.refreshToken);
|
||||
await _sessionStore.saveEmail(email);
|
||||
return AuthUser(id: session.userId, email: email);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthUser?> recoverSession() async {
|
||||
final refreshToken = await _sessionStore.getRefreshToken();
|
||||
if (refreshToken == null || refreshToken.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final session = await _authApi.refreshSession(refreshToken: refreshToken);
|
||||
await _sessionStore.saveToken(session.accessToken);
|
||||
await _sessionStore.saveRefreshToken(session.refreshToken);
|
||||
|
||||
final savedEmail = await _sessionStore.getEmail();
|
||||
final email = savedEmail?.isNotEmpty == true
|
||||
? savedEmail!
|
||||
: session.userEmail;
|
||||
if (email.isNotEmpty) {
|
||||
await _sessionStore.saveEmail(email);
|
||||
}
|
||||
return AuthUser(id: session.userId, email: email);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> logout() async {
|
||||
final refreshToken = await _sessionStore.getRefreshToken();
|
||||
try {
|
||||
if (refreshToken != null && refreshToken.isNotEmpty) {
|
||||
await _authApi.deleteSession(refreshToken: refreshToken);
|
||||
}
|
||||
} finally {
|
||||
await clearLocalSession();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clearLocalSession() async {
|
||||
await _sessionStore.clearToken();
|
||||
await _sessionStore.clearRefreshToken();
|
||||
await _sessionStore.clearEmail();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user