forked from baron/baron-sso
연동 ㅎ해지 UI 및 서비스 로직 구현
This commit is contained in:
@@ -594,6 +594,44 @@ class AuthProxyService {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<dynamic>> fetchLinkedRps() async {
|
||||
final url = Uri.parse('$_baseUrl/api/v1/user/rp/linked');
|
||||
final client = createHttpClient(withCredentials: true);
|
||||
try {
|
||||
final response = await client.get(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
return data['items'] ?? [];
|
||||
} else {
|
||||
throw Exception('연동된 앱 목록을 불러오지 못했습니다.');
|
||||
}
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> revokeLinkedRp(String clientId) async {
|
||||
final url = Uri.parse('$_baseUrl/api/v1/user/rp/linked/$clientId');
|
||||
final client = createHttpClient(withCredentials: true);
|
||||
try {
|
||||
final response = await client.delete(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
final errorBody = jsonDecode(response.body);
|
||||
throw Exception(errorBody['error'] ?? '연동 해지에 실패했습니다.');
|
||||
}
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> sendLog(String level, String message, {Map<String, dynamic>? data}) async {
|
||||
if (!_canSendClientLog()) {
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import '../../../../core/notifiers/auth_notifier.dart';
|
||||
import '../../../../core/services/auth_token_store.dart';
|
||||
import '../../../../core/services/http_client.dart';
|
||||
import '../../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../../core/ui/layout_breakpoints.dart';
|
||||
import '../../profile/domain/notifiers/profile_notifier.dart';
|
||||
|
||||
@@ -159,6 +160,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
bool _auditLoading = false;
|
||||
bool _auditLoadingMore = false;
|
||||
String? _auditError;
|
||||
bool _isRevoking = false;
|
||||
|
||||
Future<List<LinkedRp>>? _linkedRpsFuture;
|
||||
bool _showAllActivities = false;
|
||||
@@ -182,6 +184,50 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
AuthNotifier.instance.notify();
|
||||
}
|
||||
|
||||
Future<void> _onRevokeLink(String clientId, String appName) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('연동 해지'),
|
||||
content: Text('$appName 앱과의 연동을 해지하시겠습니까?\n해지하면 다음 로그인 시 다시 동의가 필요합니다.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
child: const Text('해지하기'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (confirmed != true) return;
|
||||
|
||||
setState(() => _isRevoking = true);
|
||||
try {
|
||||
await AuthProxyService.revokeLinkedRp(clientId);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('$appName 연동이 해지되었습니다.')),
|
||||
);
|
||||
_refreshAll();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('해지 실패: $e')),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isRevoking = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onScanQR() {
|
||||
context.push('/scan');
|
||||
}
|
||||
@@ -828,10 +874,12 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
final name = rp.name.isNotEmpty ? rp.name : rp.id;
|
||||
items.add(
|
||||
_ActivityItem(
|
||||
clientId: rp.id,
|
||||
appName: name,
|
||||
lastAuthAt: lastAuthLabel,
|
||||
status: statusLabel,
|
||||
canLogout: false,
|
||||
onRevoke: () => _onRevokeLink(rp.id, name),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -929,16 +977,39 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: _ink),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
onPressed: item.canLogout ? item.onLogout : null,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _ink,
|
||||
side: const BorderSide(color: _border),
|
||||
Row(
|
||||
children: [
|
||||
if (item.canLogout)
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: item.onLogout,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: _ink,
|
||||
side: const BorderSide(color: _border),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
child: const Text('로그아웃', style: TextStyle(fontSize: 13)),
|
||||
),
|
||||
),
|
||||
if (item.canLogout) const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: _isRevoking ? null : item.onRevoke,
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.redAccent,
|
||||
side: const BorderSide(color: Colors.redAccent, width: 0.5),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
),
|
||||
child: _isRevoking
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.redAccent),
|
||||
)
|
||||
: const Text('연동 해지', style: TextStyle(fontSize: 13)),
|
||||
),
|
||||
),
|
||||
child: const Text('로그아웃'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1136,17 +1207,21 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
}
|
||||
|
||||
class _ActivityItem {
|
||||
final String clientId;
|
||||
final String appName;
|
||||
final String lastAuthAt;
|
||||
final String status;
|
||||
final bool canLogout;
|
||||
final VoidCallback? onLogout;
|
||||
final VoidCallback? onRevoke;
|
||||
|
||||
_ActivityItem({
|
||||
required this.clientId,
|
||||
required this.appName,
|
||||
required this.lastAuthAt,
|
||||
required this.status,
|
||||
required this.canLogout,
|
||||
this.onLogout,
|
||||
this.onRevoke,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user