1
0
forked from baron/baron-sso

Merge branch 'dev' into fix/rebac-env-sync-issue

This commit is contained in:
2026-04-10 13:52:07 +09:00
79 changed files with 9316 additions and 1606 deletions

View File

@@ -241,6 +241,64 @@ class AuthProxyService {
}
}
static Future<void> revokeSession(String sessionId) async {
final url = Uri.parse('$_baseUrl/api/v1/user/sessions/$sessionId');
final useCookie = AuthTokenStore.usesCookie();
final token = AuthTokenStore.getToken();
final client = createHttpClient(withCredentials: useCookie);
try {
final headers = <String, String>{'Content-Type': 'application/json'};
if (!useCookie && token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
final response = await client.delete(url, headers: headers);
if (response.statusCode != 200) {
throw _error(
'err.userfront.dashboard.sessions.revoke',
'세션 종료에 실패했습니다: {{error}}',
detail: response.body,
);
}
} finally {
client.close();
}
}
static Future<String?> fetchCurrentSessionId() async {
final url = Uri.parse('$_baseUrl/api/v1/user/sessions');
final useCookie = AuthTokenStore.usesCookie();
final token = AuthTokenStore.getToken();
final client = createHttpClient(withCredentials: useCookie);
try {
final headers = <String, String>{'Content-Type': 'application/json'};
if (!useCookie && token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
final response = await client.get(url, headers: headers);
if (response.statusCode != 200) {
throw _error(
'err.userfront.dashboard.sessions.load',
'활성 세션을 불러오지 못했습니다: {{error}}',
detail: response.body,
);
}
final body = jsonDecode(response.body) as Map<String, dynamic>;
final items = (body['items'] as List?) ?? const [];
for (final item in items.whereType<Map<String, dynamic>>()) {
if (item['is_current'] == true) {
final sessionId = item['session_id']?.toString().trim() ?? '';
if (sessionId.isNotEmpty) {
return sessionId;
}
}
}
return null;
} finally {
client.close();
}
}
static Future<Map<String, dynamic>> verifyLoginShortCode(
String shortCode, {
bool verifyOnly = false,

View File

@@ -0,0 +1,39 @@
import '../notifiers/auth_notifier.dart';
import 'auth_proxy_service.dart';
import 'auth_token_store.dart';
typedef CurrentSessionLoader = Future<String?> Function();
typedef SessionRevoker = Future<void> Function(String sessionId);
typedef LogoutCallback = void Function();
class LogoutService {
LogoutService({
CurrentSessionLoader? loadCurrentSessionId,
SessionRevoker? revokeSession,
LogoutCallback? clearAuth,
LogoutCallback? notifyAuthChanged,
}) : _loadCurrentSessionId =
loadCurrentSessionId ?? AuthProxyService.fetchCurrentSessionId,
_revokeSession = revokeSession ?? AuthProxyService.revokeSession,
_clearAuth = clearAuth ?? AuthTokenStore.clear,
_notifyAuthChanged = notifyAuthChanged ?? AuthNotifier.instance.notify;
final CurrentSessionLoader _loadCurrentSessionId;
final SessionRevoker _revokeSession;
final LogoutCallback _clearAuth;
final LogoutCallback _notifyAuthChanged;
Future<void> logout() async {
try {
final currentSessionId = await _loadCurrentSessionId();
if (currentSessionId != null && currentSessionId.isNotEmpty) {
await _revokeSession(currentSessionId);
}
} catch (_) {
// 서버 세션 종료는 best-effort로 처리하고, 로컬 로그아웃은 계속 진행합니다.
} finally {
_clearAuth();
_notifyAuthChanged();
}
}
}