1
0
forked from baron/baron-sso

린트 적용

This commit is contained in:
2026-02-12 10:39:47 +09:00
parent 21b9594de5
commit 74884f6616
65 changed files with 26389 additions and 1583 deletions

View File

@@ -11,7 +11,8 @@ class AuditService {
return dotenv.env[key] ?? fallback;
}
static String get _baseUrl => _envOrDefault('BACKEND_URL', 'https://sso.hmac.kr');
static String get _baseUrl =>
_envOrDefault('BACKEND_URL', 'https://sso.hmac.kr');
static Future<void> logEvent({
required String userId,
@@ -20,7 +21,7 @@ class AuditService {
String? details,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/audit');
try {
final response = await http.post(
url,

View File

@@ -19,10 +19,12 @@ class AuthProxyService {
// 배포 환경에서 $ 기호나 공백이 섞여 들어오는 경우를 방지하기 위해 정제합니다.
return rawUrl.replaceAll(r'$', '').trim().replaceAll(RegExp(r'/$'), '');
}
static bool get _isProd {
final env = _envOrDefault('APP_ENV', 'dev').toLowerCase();
return env == 'prod' || env == 'production';
}
static bool get isProdEnv => _isProd;
static bool _shouldSendDrySend(bool? drySend) {
if (_isProd) {
@@ -76,13 +78,14 @@ class AuthProxyService {
}
}
static Future<int> getSessionStatus({String? token, bool useCookie = false}) async {
static Future<int> getSessionStatus({
String? token,
bool useCookie = false,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/user/me');
final client = createHttpClient(withCredentials: useCookie);
try {
final headers = <String, String>{
'Content-Type': 'application/json',
};
final headers = <String, String>{'Content-Type': 'application/json'};
if (!useCookie && token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
@@ -101,11 +104,8 @@ class AuthProxyService {
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/enchanted-link/init');
final userfrontUrl = _envOrDefault('USERFRONT_URL', 'http://sso.hmac.kr');
final body = <String, dynamic>{
'loginId': loginId,
'uri': userfrontUrl,
};
final body = <String, dynamic>{'loginId': loginId, 'uri': userfrontUrl};
if (_shouldSendDrySend(drySend)) {
body['drySend'] = true;
}
@@ -115,7 +115,7 @@ class AuthProxyService {
if (codeOnly == true) {
body['codeOnly'] = true;
}
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
@@ -133,15 +133,15 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> pollEnchantedLink(String pendingRef) async {
static Future<Map<String, dynamic>> pollEnchantedLink(
String pendingRef,
) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/enchanted-link/poll');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'pendingRef': pendingRef,
}),
body: jsonEncode({'pendingRef': pendingRef}),
);
if (response.statusCode == 200) {
@@ -157,16 +157,16 @@ class AuthProxyService {
);
}
static Future<Map<String, dynamic>> verifyMagicLink(String token, {bool verifyOnly = false}) async {
static Future<Map<String, dynamic>> verifyMagicLink(
String token, {
bool verifyOnly = false,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/magic-link/verify');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'token': token,
'verifyOnly': verifyOnly,
}),
body: jsonEncode({'token': token, 'verifyOnly': verifyOnly}),
);
if (response.statusCode == 200) {
@@ -223,10 +223,7 @@ class AuthProxyService {
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'shortCode': shortCode,
'verifyOnly': verifyOnly,
}),
body: jsonEncode({'shortCode': shortCode, 'verifyOnly': verifyOnly}),
);
if (response.statusCode == 200) {
@@ -240,13 +237,18 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> loginWithPassword(String loginId, String password, {String? loginChallenge}) async {
static Future<Map<String, dynamic>> loginWithPassword(
String loginId,
String password, {
String? loginChallenge,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/password/login');
final payload = {
'loginId': loginId,
'password': password,
if (loginChallenge != null && loginChallenge.isNotEmpty) 'login_challenge': loginChallenge,
if (loginChallenge != null && loginChallenge.isNotEmpty)
'login_challenge': loginChallenge,
};
final response = await http.post(
@@ -272,8 +274,13 @@ class AuthProxyService {
);
}
}
static Future<Map<String, dynamic>> getConsentInfo(String consentChallenge) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/consent').replace(queryParameters: {'consent_challenge': consentChallenge});
static Future<Map<String, dynamic>> getConsentInfo(
String consentChallenge,
) async {
final url = Uri.parse(
'$_baseUrl/api/v1/auth/consent',
).replace(queryParameters: {'consent_challenge': consentChallenge});
final response = await http.get(
url,
headers: {'Content-Type': 'application/json'},
@@ -293,11 +300,12 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> acceptConsent(String consentChallenge, {List<String>? grantScope}) async {
static Future<Map<String, dynamic>> acceptConsent(
String consentChallenge, {
List<String>? grantScope,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/consent/accept');
final body = <String, dynamic>{
'consent_challenge': consentChallenge,
};
final body = <String, dynamic>{'consent_challenge': consentChallenge};
if (grantScope != null) {
body['grant_scope'] = grantScope;
}
@@ -322,11 +330,11 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> rejectConsent(String consentChallenge) async {
static Future<Map<String, dynamic>> rejectConsent(
String consentChallenge,
) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/consent/reject');
final body = <String, dynamic>{
'consent_challenge': consentChallenge,
};
final body = <String, dynamic>{'consent_challenge': consentChallenge};
final response = await http.post(
url,
@@ -353,9 +361,7 @@ class AuthProxyService {
String? token,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/oidc/login/accept');
final headers = <String, String>{
'Content-Type': 'application/json',
};
final headers = <String, String>{'Content-Type': 'application/json'};
if (token != null && token.isNotEmpty) {
headers['Authorization'] = 'Bearer $token';
}
@@ -364,9 +370,7 @@ class AuthProxyService {
final response = await client.post(
url,
headers: headers,
body: jsonEncode({
'login_challenge': loginChallenge,
}),
body: jsonEncode({'login_challenge': loginChallenge}),
);
if (response.statusCode == 200) {
@@ -386,8 +390,10 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> initiatePasswordReset(String loginId, {bool? drySend}) async {
static Future<Map<String, dynamic>> initiatePasswordReset(
String loginId, {
bool? drySend,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/password/reset/initiate');
final response = await http.post(
url,
@@ -424,7 +430,9 @@ class AuthProxyService {
if (token != null && token.isNotEmpty) {
query['token'] = token;
}
final url = Uri.parse('$_baseUrl/api/v1/auth/password/reset/complete').replace(queryParameters: query);
final url = Uri.parse(
'$_baseUrl/api/v1/auth/password/reset/complete',
).replace(queryParameters: query);
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
@@ -447,13 +455,11 @@ class AuthProxyService {
static Future<void> sendSms(String phoneNumber) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/sms');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'phoneNumber': phoneNumber,
}),
body: jsonEncode({'phoneNumber': phoneNumber}),
);
if (response.statusCode != 200) {
@@ -465,16 +471,16 @@ class AuthProxyService {
}
}
static Future<Map<String, dynamic>> verifySmsCode(String phoneNumber, String code) async {
static Future<Map<String, dynamic>> verifySmsCode(
String phoneNumber,
String code,
) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/verify-sms');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'phoneNumber': phoneNumber,
'code': code,
}),
body: jsonEncode({'phoneNumber': phoneNumber, 'code': code}),
);
if (response.statusCode == 200) {
@@ -532,10 +538,10 @@ class AuthProxyService {
String? token,
bool withCredentials = false,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/qr/approve'); // Mapping to ScanQRLogin on backend
final payload = <String, dynamic>{
'pendingRef': pendingRef,
};
final url = Uri.parse(
'$_baseUrl/api/v1/auth/qr/approve',
); // Mapping to ScanQRLogin on backend
final payload = <String, dynamic>{'pendingRef': pendingRef};
if (token != null && token.isNotEmpty) {
payload['token'] = token;
}
@@ -593,7 +599,7 @@ class AuthProxyService {
String? displayName,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/admin/users');
final response = await http.post(
url,
headers: {
@@ -617,7 +623,10 @@ class AuthProxyService {
}
}
static Future<List<dynamic>> listUsers(String adminPassword, {String? query}) async {
static Future<List<dynamic>> listUsers(
String adminPassword, {
String? query,
}) async {
var uri = Uri.parse('$_baseUrl/api/v1/admin/users');
if (query != null && query.isNotEmpty) {
uri = uri.replace(queryParameters: {'text': query});
@@ -646,7 +655,7 @@ class AuthProxyService {
static Future<void> deleteUser(String adminPassword, String loginId) async {
final encodedId = Uri.encodeComponent(loginId);
final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId');
final response = await http.delete(
url,
headers: {
@@ -664,10 +673,14 @@ class AuthProxyService {
}
}
static Future<void> updateUserStatus(String adminPassword, String loginId, String status) async {
static Future<void> updateUserStatus(
String adminPassword,
String loginId,
String status,
) async {
final encodedId = Uri.encodeComponent(loginId);
final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId/status');
final response = await http.patch(
url,
headers: {
@@ -695,7 +708,7 @@ class AuthProxyService {
}) async {
final encodedId = Uri.encodeComponent(loginId);
final url = Uri.parse('$_baseUrl/api/v1/admin/users/$encodedId');
final body = <String, dynamic>{};
if (email != null) body['email'] = email;
if (phone != null) body['phone'] = phone;
@@ -725,18 +738,13 @@ class AuthProxyService {
final token = AuthTokenStore.getToken();
final client = createHttpClient(withCredentials: useCookie);
final headers = <String, String>{
'Content-Type': 'application/json',
};
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,
);
final response = await client.get(url, headers: headers);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
@@ -758,18 +766,13 @@ class AuthProxyService {
final token = AuthTokenStore.getToken();
final client = createHttpClient(withCredentials: useCookie);
final headers = <String, String>{
'Content-Type': 'application/json',
};
final headers = <String, String>{'Content-Type': 'application/json'};
if (!useCookie && token != null) {
headers['Authorization'] = 'Bearer $token';
}
try {
final response = await client.delete(
url,
headers: headers,
);
final response = await client.delete(url, headers: headers);
if (response.statusCode != 200) {
final errorBody = jsonDecode(response.body);
@@ -786,7 +789,11 @@ class AuthProxyService {
}
}
static Future<void> sendLog(String level, String message, {Map<String, dynamic>? data}) async {
static Future<void> sendLog(
String level,
String message, {
Map<String, dynamic>? data,
}) async {
if (!_canSendClientLog()) {
return;
}
@@ -808,11 +815,15 @@ class AuthProxyService {
}
}
static Future<void> logError(String message, {dynamic error, StackTrace? stackTrace}) async {
static Future<void> logError(
String message, {
dynamic error,
StackTrace? stackTrace,
}) async {
final data = <String, dynamic>{};
if (error != null) data['error'] = error.toString();
if (stackTrace != null) data['stack'] = stackTrace.toString();
await sendLog('ERROR', message, data: data);
}
@@ -861,7 +872,7 @@ class AuthProxyService {
static Future<void> sendSignupCode(String target, String type) async {
final path = type == 'email' ? 'send-email-code' : 'send-sms-code';
final url = Uri.parse('$_baseUrl/api/v1/auth/signup/$path');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
@@ -877,17 +888,17 @@ class AuthProxyService {
}
}
static Future<bool> verifySignupCode(String target, String type, String code) async {
static Future<bool> verifySignupCode(
String target,
String type,
String code,
) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/signup/verify-code');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'target': target,
'type': type,
'code': code,
}),
body: jsonEncode({'target': target, 'type': type, 'code': code}),
);
if (response.statusCode == 200) {
@@ -908,7 +919,7 @@ class AuthProxyService {
required bool termsAccepted,
}) async {
final url = Uri.parse('$_baseUrl/api/v1/auth/signup');
final response = await http.post(
url,
headers: {'Content-Type': 'application/json'},

View File

@@ -1,6 +1,5 @@
import 'package:http/http.dart' as http;
import 'http_client_stub.dart'
if (dart.library.html) 'http_client_web.dart';
import 'http_client_stub.dart' if (dart.library.html) 'http_client_web.dart';
http.Client createHttpClient({bool withCredentials = false}) {
return httpClientFactory.create(withCredentials: withCredentials);

View File

@@ -25,8 +25,10 @@ class LoggerService {
);
// 2. Configure Standard Logger (logging package)
std_log.Logger.root.level = kReleaseMode ? std_log.Level.WARNING : std_log.Level.ALL;
std_log.Logger.root.level = kReleaseMode
? std_log.Level.WARNING
: std_log.Level.ALL;
std_log.Logger.root.onRecord.listen((record) {
if (kReleaseMode) {
// [Production] Log as JSON
@@ -41,13 +43,17 @@ class LoggerService {
/// Initialize the logger. Call this in main.dart
static void init() {
// Accessing the instance triggers the constructor
LoggerService();
LoggerService();
std_log.Logger('BaronSSO').info('Logger initialized');
}
void _logPretty(std_log.LogRecord record) {
if (record.level >= std_log.Level.SEVERE) {
_prettyLogger.e(record.message, error: record.error, stackTrace: record.stackTrace);
_prettyLogger.e(
record.message,
error: record.error,
stackTrace: record.stackTrace,
);
} else if (record.level >= std_log.Level.WARNING) {
_prettyLogger.w(record.message);
} else if (record.level >= std_log.Level.INFO) {
@@ -66,7 +72,7 @@ class LoggerService {
if (record.error != null) 'error': record.error.toString(),
if (record.stackTrace != null) 'stack': record.stackTrace.toString(),
};
// 1. Print to Browser Console (F12)
debugPrint(jsonEncode(logData));

View File

@@ -13,16 +13,22 @@ void implSendLoginSuccess(String token) {
final fullUrl = html.window.location.href;
final uri = Uri.base;
// Try to find redirect_uri from standard parsing first, then manual string search
String? redirectUri = uri.queryParameters['redirect_uri'] ?? uri.queryParameters['redirect_url'];
String? redirectUri =
uri.queryParameters['redirect_uri'] ??
uri.queryParameters['redirect_url'];
if (redirectUri == null) {
// Manual fallback for cases where Uri.base misses params
final searchParams = html.window.location.search;
if (searchParams != null && searchParams.isNotEmpty) {
final sUri = Uri.parse('?${searchParams.startsWith('?') ? searchParams.substring(1) : searchParams}');
redirectUri = sUri.queryParameters['redirect_uri'] ?? sUri.queryParameters['redirect_url'];
final sUri = Uri.parse(
'?${searchParams.startsWith('?') ? searchParams.substring(1) : searchParams}',
);
redirectUri =
sUri.queryParameters['redirect_uri'] ??
sUri.queryParameters['redirect_url'];
}
}
@@ -48,14 +54,14 @@ void implSendLoginSuccess(String token) {
final query = Map<String, String>.from(target.queryParameters);
query['token'] = effectiveToken;
final finalUri = target.replace(queryParameters: query);
debugPrint('Redirecting to: ${finalUri.toString()}');
html.window.location.href = finalUri.toString();
return;
}
final message = {'type': 'LOGIN_SUCCESS', 'token': effectiveToken};
if (html.window.opener != null) {
try {
html.window.opener!.postMessage(message, '*');
@@ -63,20 +69,20 @@ void implSendLoginSuccess(String token) {
} catch (e) {
debugPrint('Failed to postMessage: $e');
}
// Close the popup after a short delay to ensure message sending
Timer(const Duration(milliseconds: 500), () {
html.window.close();
});
} else {
// Should not happen given isPopup check, but as fallback:
// Should not happen given isPopup check, but as fallback:
debugPrint('No opener found during popup flow.');
}
}
bool implIsPopup() {
if (html.window.opener != null) return true;
// Fallback: Check query parameters for integration source
final uri = Uri.base;
if (uri.queryParameters['source'] == 'adminfront') return true;