forked from baron/baron-sso
fix(audit): stop default read logging and dedupe dashboard timeline
- skip read audit logging unless a path is explicitly allowlisted - exclude audit-facing endpoints from backend audit collection - remove duplicate auth timeline fetch logic from dashboard screen - add regression tests for default GET skip and dashboard timeline dedup Co-Authored-By: First Fluke <our.first.fluke@gmail.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
@@ -7,7 +6,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import '../domain/linked_rp_launch.dart';
|
||||
import '../domain/session_time_resolver.dart';
|
||||
import '../domain/providers/linked_rps_provider.dart';
|
||||
@@ -16,7 +14,6 @@ import '../../../../core/notifiers/auth_notifier.dart';
|
||||
import '../../../../core/services/auth_proxy_service.dart';
|
||||
import '../../../../core/services/logout_service.dart';
|
||||
import '../../../../core/services/auth_token_store.dart';
|
||||
import '../../../../core/services/http_client.dart';
|
||||
import '../../../../core/i18n/locale_utils.dart';
|
||||
import '../../../../core/widgets/language_selector.dart';
|
||||
import '../../../../core/widgets/theme_toggle_button.dart';
|
||||
@@ -54,10 +51,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
|
||||
final ScrollController _pageScrollController = ScrollController();
|
||||
final ScrollController _rpScrollController = ScrollController();
|
||||
final List<AuditLogEntry> _auditLogs = [];
|
||||
String? _auditNextCursor;
|
||||
bool _auditLoading = false;
|
||||
bool _auditLoadingMore = false;
|
||||
bool _isRevoking = false;
|
||||
String? _revokingSessionId;
|
||||
bool _redirectingToSignin = false;
|
||||
@@ -559,95 +552,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
ref.read(authTimelineProvider.notifier).refresh(),
|
||||
]);
|
||||
|
||||
await _loadAuditLogs(reset: true);
|
||||
await ref.read(linkedRpsProvider.notifier).refresh();
|
||||
}
|
||||
|
||||
static String _envOrDefault(String key, String fallback) {
|
||||
if (!dotenv.isInitialized) {
|
||||
return fallback;
|
||||
}
|
||||
return dotenv.env[key] ?? fallback;
|
||||
}
|
||||
|
||||
Future<AuditPage> _fetchAuditLogs({String? cursor}) async {
|
||||
final baseUrl = _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr');
|
||||
final queryParameters = <String, String>{'limit': '20'};
|
||||
if (cursor != null && cursor.isNotEmpty) {
|
||||
queryParameters['cursor'] = cursor;
|
||||
}
|
||||
final url = Uri.parse(
|
||||
'$baseUrl/api/v1/audit/auth/timeline',
|
||||
).replace(queryParameters: queryParameters);
|
||||
final useCookie = AuthTokenStore.usesCookie();
|
||||
final token = AuthTokenStore.getToken();
|
||||
|
||||
final client = createHttpClient(withCredentials: useCookie);
|
||||
final headers = <String, String>{'Content-Type': 'application/json'};
|
||||
if (!useCookie && token != null) {
|
||||
headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await client.get(url, headers: headers);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Failed to load audit logs');
|
||||
}
|
||||
|
||||
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
final items = (body['items'] as List?) ?? [];
|
||||
final nextCursor = body['next_cursor']?.toString();
|
||||
final logs = items
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(AuditLogEntry.fromJson)
|
||||
.toList();
|
||||
|
||||
return AuditPage(items: logs, nextCursor: nextCursor);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadAuditLogs({bool reset = false}) async {
|
||||
if (!_isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (_auditLoading || _auditLoadingMore) {
|
||||
return;
|
||||
}
|
||||
final nextCursor = _auditNextCursor;
|
||||
if (!reset && (nextCursor == null || nextCursor.isEmpty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
setState(() {
|
||||
_auditLogs.clear();
|
||||
_auditNextCursor = null;
|
||||
_auditLoading = true;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_auditLoadingMore = true;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
final page = await _fetchAuditLogs(cursor: _auditNextCursor);
|
||||
setState(() {
|
||||
_auditLogs.addAll(page.items);
|
||||
_auditNextCursor = page.nextCursor;
|
||||
});
|
||||
} catch (_) {
|
||||
// 에러는 상위 UI에서 재시도 UX로 처리합니다.
|
||||
} finally {
|
||||
setState(() {
|
||||
_auditLoading = false;
|
||||
_auditLoadingMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
String _formatDateTime(DateTime dateTime) {
|
||||
final yyyy = dateTime.year.toString().padLeft(4, '0');
|
||||
final mm = dateTime.month.toString().padLeft(2, '0');
|
||||
@@ -2539,7 +2446,6 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
||||
_redirectToSignin();
|
||||
return;
|
||||
}
|
||||
await _loadAuditLogs(reset: true);
|
||||
} finally {
|
||||
_authBootstrapInProgress = false;
|
||||
}
|
||||
|
||||
16
userfront/test/dashboard_timeline_dedup_test.dart
Normal file
16
userfront/test/dashboard_timeline_dedup_test.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('대시보드 화면은 auth timeline fetch 구현을 직접 가지지 않는다', () async {
|
||||
final screenFile = File(
|
||||
'lib/features/dashboard/presentation/dashboard_screen.dart',
|
||||
);
|
||||
final source = await screenFile.readAsString();
|
||||
|
||||
expect(source.contains('_fetchAuditLogs('), isFalse);
|
||||
expect(source.contains('_loadAuditLogs('), isFalse);
|
||||
expect(source.contains('/api/v1/audit/auth/timeline'), isFalse);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user