From cd16cb3a4ac7566ad07844aaa7fe742eb86534ff Mon Sep 17 00:00:00 2001 From: kyy Date: Fri, 15 May 2026 17:57:47 +0900 Subject: [PATCH] =?UTF-8?q?userfront=20=EB=9F=B0=ED=83=80=EC=9E=84=20BACKE?= =?UTF-8?q?ND=5FURL=20fallback=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/core/services/audit_service.dart | 12 +----- .../lib/core/services/auth_proxy_service.dart | 39 ++++--------------- userfront/lib/core/services/runtime_env.dart | 34 ++++++++++++++++ .../dashboard/domain/dashboard_providers.dart | 11 +----- .../domain/providers/linked_rps_provider.dart | 11 +----- .../providers/user_sessions_provider.dart | 11 +----- .../data/repositories/profile_repository.dart | 12 +----- userfront/lib/main.dart | 11 ++++++ userfront/nginx.conf | 2 + 9 files changed, 64 insertions(+), 79 deletions(-) create mode 100644 userfront/lib/core/services/runtime_env.dart diff --git a/userfront/lib/core/services/audit_service.dart b/userfront/lib/core/services/audit_service.dart index 8ee93b85..78a7e9e8 100644 --- a/userfront/lib/core/services/audit_service.dart +++ b/userfront/lib/core/services/audit_service.dart @@ -1,18 +1,10 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; -import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'runtime_env.dart'; class AuditService { - static String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - return dotenv.env[key] ?? fallback; - } - - static String get _baseUrl => - _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); + static String get _baseUrl => runtimeBackendUrl(); static Future logEvent({ required String userId, diff --git a/userfront/lib/core/services/auth_proxy_service.dart b/userfront/lib/core/services/auth_proxy_service.dart index 8adb0e13..6f9ae984 100644 --- a/userfront/lib/core/services/auth_proxy_service.dart +++ b/userfront/lib/core/services/auth_proxy_service.dart @@ -1,41 +1,16 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:userfront/i18n.dart'; import 'http_client.dart'; import 'auth_token_store.dart'; import 'log_policy.dart'; +import 'runtime_env.dart'; class AuthProxyService { - static String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - final value = dotenv.env[key]; - if (value == null || value.trim().isEmpty) { - return fallback; - } - return value; - } - - static String _fallbackOrigin() { - try { - final origin = Uri.base.origin; - if (origin.isNotEmpty && origin != 'null') { - return origin; - } - } catch (_) {} - return 'http://sso.hmac.kr'; - } - - static String get _baseUrl { - final rawUrl = _envOrDefault('BACKEND_URL', _fallbackOrigin()); - // 배포 환경에서 $ 기호나 공백이 섞여 들어오는 경우를 방지하기 위해 정제합니다. - return rawUrl.replaceAll(r'$', '').trim().replaceAll(RegExp(r'/$'), ''); - } + static String get _baseUrl => runtimeBackendUrl(); static bool get _isProd { - final env = _envOrDefault('APP_ENV', 'dev').toLowerCase(); + final env = envOrDefault('APP_ENV', 'dev').toLowerCase(); return LogPolicy.isProductionEnv(env); } @@ -156,7 +131,7 @@ class AuthProxyService { bool? drySend, }) async { final url = Uri.parse('$_baseUrl/api/v1/auth/enchanted-link/init'); - final userfrontUrl = _envOrDefault('USERFRONT_URL', _fallbackOrigin()); + final userfrontUrl = runtimeUserfrontUrl(); final body = {'loginId': loginId, 'uri': userfrontUrl}; if (_shouldSendDrySend(drySend)) { @@ -897,10 +872,10 @@ class AuthProxyService { if (!_canSendClientLog()) { return; } - final appEnv = _envOrDefault('APP_ENV', 'dev'); - final productionDebugFlag = _envOrDefault( + final appEnv = envOrDefault('APP_ENV', 'dev'); + final productionDebugFlag = envOrDefault( 'CLIENT_LOG_DEBUG', - _envOrDefault('USERFRONT_DEBUG_LOG', ''), + envOrDefault('USERFRONT_DEBUG_LOG', ''), ); if (!LogPolicy.shouldRelayClientLog( level: level, diff --git a/userfront/lib/core/services/runtime_env.dart b/userfront/lib/core/services/runtime_env.dart new file mode 100644 index 00000000..89ea64ac --- /dev/null +++ b/userfront/lib/core/services/runtime_env.dart @@ -0,0 +1,34 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +String runtimeOriginFallback() { + try { + final origin = Uri.base.origin; + if (origin.isNotEmpty && origin != 'null') { + return origin; + } + } catch (_) {} + return 'https://sso-test.hmac.kr'; +} + +String envOrDefault(String key, String fallback) { + if (!dotenv.isInitialized) { + return fallback; + } + final value = dotenv.env[key]; + if (value == null || value.trim().isEmpty) { + return fallback; + } + return value; +} + +String sanitizedUrl(String value) { + return value.replaceAll(r'$', '').trim().replaceAll(RegExp(r'/$'), ''); +} + +String runtimeBackendUrl() { + return sanitizedUrl(envOrDefault('BACKEND_URL', runtimeOriginFallback())); +} + +String runtimeUserfrontUrl() { + return sanitizedUrl(envOrDefault('USERFRONT_URL', runtimeOriginFallback())); +} diff --git a/userfront/lib/features/dashboard/domain/dashboard_providers.dart b/userfront/lib/features/dashboard/domain/dashboard_providers.dart index 1a4307b2..a005e8ff 100644 --- a/userfront/lib/features/dashboard/domain/dashboard_providers.dart +++ b/userfront/lib/features/dashboard/domain/dashboard_providers.dart @@ -1,21 +1,14 @@ import 'dart:convert'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/services/auth_token_store.dart'; import '../../../../core/services/http_client.dart'; +import '../../../../core/services/runtime_env.dart'; import 'package:userfront/i18n.dart'; import 'models.dart'; -String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - return dotenv.env[key] ?? fallback; -} - -String get _baseUrl => _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); +String get _baseUrl => runtimeBackendUrl(); Future _fetchAuthTimelinePage({String? cursor}) async { final queryParameters = {'limit': '20'}; diff --git a/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart b/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart index e601795d..c209f2ea 100644 --- a/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart +++ b/userfront/lib/features/dashboard/domain/providers/linked_rps_provider.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:userfront/core/services/auth_proxy_service.dart'; import 'package:userfront/core/services/auth_token_store.dart'; import 'package:userfront/core/services/http_client.dart'; +import 'package:userfront/core/services/runtime_env.dart'; class LinkedRp { final String id; @@ -62,16 +62,9 @@ class LinkedRpsNotifier extends AsyncNotifier> { return _fetchLinkedRps(); } - String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - return dotenv.env[key] ?? fallback; - } - Future> _fetchLinkedRps() async { try { - final baseUrl = _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); + final baseUrl = runtimeBackendUrl(); final url = Uri.parse('$baseUrl/api/v1/user/rp/linked'); final useCookie = AuthTokenStore.usesCookie(); diff --git a/userfront/lib/features/dashboard/domain/providers/user_sessions_provider.dart b/userfront/lib/features/dashboard/domain/providers/user_sessions_provider.dart index 881a88f0..7b003d5e 100644 --- a/userfront/lib/features/dashboard/domain/providers/user_sessions_provider.dart +++ b/userfront/lib/features/dashboard/domain/providers/user_sessions_provider.dart @@ -1,11 +1,11 @@ import 'dart:convert'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/services/auth_proxy_service.dart'; import '../../../../core/services/auth_token_store.dart'; import '../../../../core/services/http_client.dart'; +import '../../../../core/services/runtime_env.dart'; import '../models.dart'; class UserSessionsNotifier extends AsyncNotifier> { @@ -14,15 +14,8 @@ class UserSessionsNotifier extends AsyncNotifier> { return _fetchSessions(); } - String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - return dotenv.env[key] ?? fallback; - } - Future> _fetchSessions() async { - final baseUrl = _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); + final baseUrl = runtimeBackendUrl(); final url = Uri.parse('$baseUrl/api/v1/user/sessions'); final useCookie = AuthTokenStore.usesCookie(); diff --git a/userfront/lib/features/profile/data/repositories/profile_repository.dart b/userfront/lib/features/profile/data/repositories/profile_repository.dart index d46fdcfa..06638d6d 100644 --- a/userfront/lib/features/profile/data/repositories/profile_repository.dart +++ b/userfront/lib/features/profile/data/repositories/profile_repository.dart @@ -1,20 +1,12 @@ import 'dart:convert'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:userfront/i18n.dart'; import '../models/user_profile_model.dart'; import '../../../../core/services/auth_token_store.dart'; import '../../../../core/services/http_client.dart'; +import '../../../../core/services/runtime_env.dart'; class ProfileRepository { - static String _envOrDefault(String key, String fallback) { - if (!dotenv.isInitialized) { - return fallback; - } - return dotenv.env[key] ?? fallback; - } - - static String get _baseUrl => - _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr'); + static String get _baseUrl => runtimeBackendUrl(); // Helper to get session token static Future _getToken() async { diff --git a/userfront/lib/main.dart b/userfront/lib/main.dart index c35f45f6..d3093393 100644 --- a/userfront/lib/main.dart +++ b/userfront/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:easy_localization/easy_localization.dart' hide tr; @@ -161,10 +162,20 @@ bool _shouldRunStartupSessionRecovery(Uri uri) { return !isPublicAuthPath(path, uri); } +Future _loadRuntimeEnv() async { + for (final fileName in const ['assets/.env', '.env']) { + try { + await dotenv.load(fileName: fileName); + return; + } catch (_) {} + } +} + void main() async { WidgetsFlutterBinding.ensureInitialized(); usePathUrlStrategy(); await EasyLocalization.ensureInitialized(); + await _loadRuntimeEnv(); LocaleRegistry.primeWithDefaults(); // 1. Global Error Handling diff --git a/userfront/nginx.conf b/userfront/nginx.conf index 2df5fa0f..44c152e5 100644 --- a/userfront/nginx.conf +++ b/userfront/nginx.conf @@ -21,6 +21,8 @@ log_format json_combined escape=json server { listen 5000; + absolute_redirect off; + port_in_redirect off; root /usr/share/nginx/html; index index.html; include /etc/nginx/mime.types;