feature/account-login-lock-coordinator #1
+124
@@ -784,6 +784,130 @@ def newserv_account_lock_heartbeat():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/newserv/account-locks/session-end")
|
||||||
|
def newserv_account_lock_session_end():
|
||||||
|
auth_error = require_newserv_shared_secret()
|
||||||
|
if auth_error:
|
||||||
|
return auth_error
|
||||||
|
|
||||||
|
data = json_body()
|
||||||
|
|
||||||
|
account_id = clean_lock_account_id(data.get("account_id") or data.get("account_id_str"))
|
||||||
|
source = clean_lock_text(data.get("source"), 64)
|
||||||
|
session_nonce = clean_lock_text(data.get("session_nonce"), 160)
|
||||||
|
version = clean_lock_text(data.get("version"), 64)
|
||||||
|
|
||||||
|
if not account_id:
|
||||||
|
return jsonify({"ok": False, "message": "missing account_id"}), 400
|
||||||
|
if not source:
|
||||||
|
return jsonify({"ok": False, "message": "missing source"}), 400
|
||||||
|
if not session_nonce:
|
||||||
|
return jsonify({"ok": False, "message": "missing session_nonce"}), 400
|
||||||
|
|
||||||
|
now = utcnow()
|
||||||
|
expires_at = account_lock_expires_at()
|
||||||
|
|
||||||
|
with connect() as conn:
|
||||||
|
with conn.transaction():
|
||||||
|
with conn.cursor(row_factory=psycopg.rows.dict_row) as cur:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT account_id, holder_source, source_region, source_ship,
|
||||||
|
account_store, state, sessions, created_at, updated_at, expires_at
|
||||||
|
FROM account_session_locks
|
||||||
|
WHERE account_id = %s
|
||||||
|
FOR UPDATE
|
||||||
|
""", (account_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
if not row:
|
||||||
|
return jsonify({
|
||||||
|
"ok": True,
|
||||||
|
"account_id": account_id_str(account_id),
|
||||||
|
"source": source,
|
||||||
|
"session_nonce": session_nonce,
|
||||||
|
"version": version,
|
||||||
|
"removed": False,
|
||||||
|
"state": "unlocked",
|
||||||
|
"remaining_sessions": 0,
|
||||||
|
"message": "no active lock",
|
||||||
|
})
|
||||||
|
|
||||||
|
if row["expires_at"] and row["expires_at"] <= now:
|
||||||
|
cur.execute("""
|
||||||
|
DELETE FROM account_session_locks
|
||||||
|
WHERE account_id = %s
|
||||||
|
""", (account_id,))
|
||||||
|
return jsonify({
|
||||||
|
"ok": True,
|
||||||
|
"account_id": account_id_str(account_id),
|
||||||
|
"source": source,
|
||||||
|
"session_nonce": session_nonce,
|
||||||
|
"version": version,
|
||||||
|
"removed": False,
|
||||||
|
"state": "expired",
|
||||||
|
"remaining_sessions": 0,
|
||||||
|
"message": "expired lock removed",
|
||||||
|
})
|
||||||
|
|
||||||
|
if row["holder_source"] != source:
|
||||||
|
return jsonify({
|
||||||
|
"ok": False,
|
||||||
|
"account_id": account_id_str(account_id),
|
||||||
|
"source": source,
|
||||||
|
"session_nonce": session_nonce,
|
||||||
|
"holder_source": row["holder_source"],
|
||||||
|
"state": row["state"],
|
||||||
|
"message": "session source does not hold this lock",
|
||||||
|
"lock": lock_row_payload(row),
|
||||||
|
}), 409
|
||||||
|
|
||||||
|
sessions = row["sessions"] or {}
|
||||||
|
if isinstance(sessions, str):
|
||||||
|
sessions = json.loads(sessions)
|
||||||
|
|
||||||
|
removed = session_nonce in sessions
|
||||||
|
if removed:
|
||||||
|
sessions.pop(session_nonce, None)
|
||||||
|
|
||||||
|
if sessions:
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE account_session_locks
|
||||||
|
SET state = 'active',
|
||||||
|
sessions = %s,
|
||||||
|
updated_at = now(),
|
||||||
|
expires_at = %s
|
||||||
|
WHERE account_id = %s
|
||||||
|
RETURNING account_id, holder_source, source_region, source_ship,
|
||||||
|
account_store, state, sessions, created_at, updated_at, expires_at
|
||||||
|
""", (Jsonb(sessions), expires_at, account_id))
|
||||||
|
row = cur.fetchone()
|
||||||
|
else:
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE account_session_locks
|
||||||
|
SET state = 'draining',
|
||||||
|
sessions = '{}'::jsonb,
|
||||||
|
updated_at = now(),
|
||||||
|
expires_at = %s
|
||||||
|
WHERE account_id = %s
|
||||||
|
RETURNING account_id, holder_source, source_region, source_ship,
|
||||||
|
account_store, state, sessions, created_at, updated_at, expires_at
|
||||||
|
""", (expires_at, account_id))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"ok": True,
|
||||||
|
"account_id": account_id_str(account_id),
|
||||||
|
"source": source,
|
||||||
|
"session_nonce": session_nonce,
|
||||||
|
"version": version,
|
||||||
|
"removed": removed,
|
||||||
|
"state": row["state"],
|
||||||
|
"remaining_sessions": len(row["sessions"] or {}),
|
||||||
|
"lock": lock_row_payload(row),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/newserv/account-locks/<int:account_id>")
|
@app.get("/api/newserv/account-locks/<int:account_id>")
|
||||||
def newserv_account_lock_status(account_id):
|
def newserv_account_lock_status(account_id):
|
||||||
auth_error = require_newserv_shared_secret()
|
auth_error = require_newserv_shared_secret()
|
||||||
|
|||||||
Reference in New Issue
Block a user