Fix IAM credentials reset causing presigned URL to fail
This commit is contained in:
13
app/iam.py
13
app/iam.py
@@ -77,10 +77,20 @@ class IamService:
|
|||||||
self._users: Dict[str, Dict[str, Any]] = {}
|
self._users: Dict[str, Dict[str, Any]] = {}
|
||||||
self._raw_config: Dict[str, Any] = {}
|
self._raw_config: Dict[str, Any] = {}
|
||||||
self._failed_attempts: Dict[str, Deque[datetime]] = {}
|
self._failed_attempts: Dict[str, Deque[datetime]] = {}
|
||||||
|
self._last_load_time = 0.0
|
||||||
self._load()
|
self._load()
|
||||||
|
|
||||||
|
def _maybe_reload(self) -> None:
|
||||||
|
"""Reload configuration if the file has changed on disk."""
|
||||||
|
try:
|
||||||
|
if self.config_path.stat().st_mtime > self._last_load_time:
|
||||||
|
self._load()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
# ---------------------- authz helpers ----------------------
|
# ---------------------- authz helpers ----------------------
|
||||||
def authenticate(self, access_key: str, secret_key: str) -> Principal:
|
def authenticate(self, access_key: str, secret_key: str) -> Principal:
|
||||||
|
self._maybe_reload()
|
||||||
access_key = (access_key or "").strip()
|
access_key = (access_key or "").strip()
|
||||||
secret_key = (secret_key or "").strip()
|
secret_key = (secret_key or "").strip()
|
||||||
if not access_key or not secret_key:
|
if not access_key or not secret_key:
|
||||||
@@ -135,12 +145,14 @@ class IamService:
|
|||||||
return int(max(0, self.auth_lockout_window.total_seconds() - elapsed))
|
return int(max(0, self.auth_lockout_window.total_seconds() - elapsed))
|
||||||
|
|
||||||
def principal_for_key(self, access_key: str) -> Principal:
|
def principal_for_key(self, access_key: str) -> Principal:
|
||||||
|
self._maybe_reload()
|
||||||
record = self._users.get(access_key)
|
record = self._users.get(access_key)
|
||||||
if not record:
|
if not record:
|
||||||
raise IamError("Unknown access key")
|
raise IamError("Unknown access key")
|
||||||
return self._build_principal(access_key, record)
|
return self._build_principal(access_key, record)
|
||||||
|
|
||||||
def secret_for_key(self, access_key: str) -> str:
|
def secret_for_key(self, access_key: str) -> str:
|
||||||
|
self._maybe_reload()
|
||||||
record = self._users.get(access_key)
|
record = self._users.get(access_key)
|
||||||
if not record:
|
if not record:
|
||||||
raise IamError("Unknown access key")
|
raise IamError("Unknown access key")
|
||||||
@@ -245,6 +257,7 @@ class IamService:
|
|||||||
# ---------------------- config helpers ----------------------
|
# ---------------------- config helpers ----------------------
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
try:
|
try:
|
||||||
|
self._last_load_time = self.config_path.stat().st_mtime
|
||||||
content = self.config_path.read_text(encoding='utf-8')
|
content = self.config_path.read_text(encoding='utf-8')
|
||||||
raw = json.loads(content)
|
raw = json.loads(content)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
@@ -926,6 +926,12 @@ def rotate_iam_secret(access_key: str):
|
|||||||
return redirect(url_for("ui.iam_dashboard"))
|
return redirect(url_for("ui.iam_dashboard"))
|
||||||
try:
|
try:
|
||||||
new_secret = _iam().rotate_secret(access_key)
|
new_secret = _iam().rotate_secret(access_key)
|
||||||
|
# If rotating own key, update session immediately so subsequent API calls (like presign) work
|
||||||
|
if principal and principal.access_key == access_key:
|
||||||
|
creds = session.get("credentials", {})
|
||||||
|
creds["secret_key"] = new_secret
|
||||||
|
session["credentials"] = creds
|
||||||
|
session.modified = True
|
||||||
except IamError as exc:
|
except IamError as exc:
|
||||||
if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
|
if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
|
||||||
return jsonify({"error": str(exc)}), 400
|
return jsonify({"error": str(exc)}), 400
|
||||||
|
|||||||
@@ -286,9 +286,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body" id="rotateSecretConfirm">
|
<div class="modal-body" id="rotateSecretConfirm">
|
||||||
<p>Are you sure you want to rotate the secret key for <strong id="rotateUserLabel"></strong>?</p>
|
<p>Are you sure you want to rotate the secret key for <strong id="rotateUserLabel"></strong>?</p>
|
||||||
<div id="rotateSelfWarning" class="alert alert-warning d-none">
|
|
||||||
<strong>Warning:</strong> You are rotating your own secret key. You will need to sign in again with the new key.
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-warning mb-0">
|
<div class="alert alert-warning mb-0">
|
||||||
The old secret key will stop working immediately. Any applications using it must be updated.
|
The old secret key will stop working immediately. Any applications using it must be updated.
|
||||||
</div>
|
</div>
|
||||||
@@ -474,7 +471,6 @@
|
|||||||
const rotateSecretResult = document.getElementById('rotateSecretResult');
|
const rotateSecretResult = document.getElementById('rotateSecretResult');
|
||||||
const newSecretKeyInput = document.getElementById('newSecretKey');
|
const newSecretKeyInput = document.getElementById('newSecretKey');
|
||||||
const copyNewSecretBtn = document.getElementById('copyNewSecret');
|
const copyNewSecretBtn = document.getElementById('copyNewSecret');
|
||||||
const rotateSelfWarning = document.getElementById('rotateSelfWarning');
|
|
||||||
let currentRotateKey = null;
|
let currentRotateKey = null;
|
||||||
|
|
||||||
document.querySelectorAll('[data-rotate-user]').forEach(btn => {
|
document.querySelectorAll('[data-rotate-user]').forEach(btn => {
|
||||||
@@ -482,12 +478,6 @@
|
|||||||
currentRotateKey = btn.dataset.rotateUser;
|
currentRotateKey = btn.dataset.rotateUser;
|
||||||
rotateUserLabel.textContent = currentRotateKey;
|
rotateUserLabel.textContent = currentRotateKey;
|
||||||
|
|
||||||
if (currentRotateKey === currentUserKey) {
|
|
||||||
rotateSelfWarning.classList.remove('d-none');
|
|
||||||
} else {
|
|
||||||
rotateSelfWarning.classList.add('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset Modal State
|
// Reset Modal State
|
||||||
rotateSecretConfirm.classList.remove('d-none');
|
rotateSecretConfirm.classList.remove('d-none');
|
||||||
rotateSecretResult.classList.add('d-none');
|
rotateSecretResult.classList.add('d-none');
|
||||||
|
|||||||
Reference in New Issue
Block a user