forked from baron/baron-sso
로그인 챌린지 루프 방지 가드 추가
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import 'login_challenge_loop_guard_base.dart';
|
||||
import 'login_challenge_loop_guard_stub.dart'
|
||||
if (dart.library.js_interop) 'login_challenge_loop_guard_web.dart';
|
||||
|
||||
final loginChallengeLoopGuard = createLoginChallengeLoopGuard();
|
||||
@@ -0,0 +1,5 @@
|
||||
abstract class LoginChallengeLoopGuard {
|
||||
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000});
|
||||
void markAutoAcceptAttempt(String loginChallenge);
|
||||
void clear(String loginChallenge);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'login_challenge_loop_guard_base.dart';
|
||||
|
||||
class _InMemoryLoginChallengeLoopGuard implements LoginChallengeLoopGuard {
|
||||
final Map<String, int> _lastAttemptAtMs = <String, int>{};
|
||||
|
||||
@override
|
||||
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000}) {
|
||||
final challenge = loginChallenge.trim();
|
||||
if (challenge.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
final lastMs = _lastAttemptAtMs[challenge];
|
||||
if (lastMs == null) {
|
||||
return true;
|
||||
}
|
||||
return nowMs - lastMs > cooldownMs;
|
||||
}
|
||||
|
||||
@override
|
||||
void markAutoAcceptAttempt(String loginChallenge) {
|
||||
final challenge = loginChallenge.trim();
|
||||
if (challenge.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_lastAttemptAtMs[challenge] = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear(String loginChallenge) {
|
||||
_lastAttemptAtMs.remove(loginChallenge.trim());
|
||||
}
|
||||
}
|
||||
|
||||
LoginChallengeLoopGuard createLoginChallengeLoopGuard() {
|
||||
return _InMemoryLoginChallengeLoopGuard();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||
|
||||
import 'dart:js_interop';
|
||||
import 'login_challenge_loop_guard_base.dart';
|
||||
|
||||
@JS('window.sessionStorage')
|
||||
external _JSStorage get _sessionStorage;
|
||||
|
||||
@JS()
|
||||
extension type _JSStorage(JSObject _) implements JSObject {
|
||||
external String? getItem(String key);
|
||||
external void setItem(String key, String value);
|
||||
external void removeItem(String key);
|
||||
}
|
||||
|
||||
class _WebLoginChallengeLoopGuard implements LoginChallengeLoopGuard {
|
||||
static const String _keyPrefix = 'baron_oidc_auto_accept_last:';
|
||||
|
||||
String _key(String challenge) => '$_keyPrefix$challenge';
|
||||
|
||||
@override
|
||||
bool shouldAllowAutoAccept(String loginChallenge, {int cooldownMs = 15000}) {
|
||||
final challenge = loginChallenge.trim();
|
||||
if (challenge.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
final raw = _sessionStorage.getItem(_key(challenge));
|
||||
if (raw == null || raw.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
final lastMs = int.tryParse(raw);
|
||||
if (lastMs == null) {
|
||||
return true;
|
||||
}
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
return nowMs - lastMs > cooldownMs;
|
||||
} catch (_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void markAutoAcceptAttempt(String loginChallenge) {
|
||||
final challenge = loginChallenge.trim();
|
||||
if (challenge.isEmpty) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
_sessionStorage.setItem(_key(challenge), nowMs.toString());
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
void clear(String loginChallenge) {
|
||||
final challenge = loginChallenge.trim();
|
||||
if (challenge.isEmpty) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
_sessionStorage.removeItem(_key(challenge));
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
LoginChallengeLoopGuard createLoginChallengeLoopGuard() {
|
||||
return _WebLoginChallengeLoopGuard();
|
||||
}
|
||||
Reference in New Issue
Block a user