Increased Agent Refresh Token TTL from 30 to 90 days and made expiration renew every refresh.

This commit is contained in:
2025-11-23 03:51:01 -07:00
parent 584cc940e2
commit aa9bbe1d7e
3 changed files with 18 additions and 6 deletions

View File

@@ -77,14 +77,16 @@ def test_refresh_token_success(engine_harness: EngineTestHarness) -> None:
with sqlite3.connect(str(harness.db_path)) as conn: with sqlite3.connect(str(harness.db_path)) as conn:
cur = conn.cursor() cur = conn.cursor()
cur.execute( cur.execute(
"SELECT last_used_at, revoked_at FROM refresh_tokens WHERE guid = ?", "SELECT last_used_at, revoked_at, expires_at FROM refresh_tokens WHERE guid = ?",
(guid,), (guid,),
) )
row = cur.fetchone() row = cur.fetchone()
assert row is not None assert row is not None
last_used_at, revoked_at = row last_used_at, revoked_at, bumped_expires_at = row
assert last_used_at is not None assert last_used_at is not None
assert revoked_at is None assert revoked_at is None
refreshed_expiry = datetime.fromisoformat(bumped_expires_at)
assert refreshed_expiry > now + timedelta(days=80)
def test_refresh_token_requires_payload(engine_harness: EngineTestHarness) -> None: def test_refresh_token_requires_payload(engine_harness: EngineTestHarness) -> None:

View File

@@ -312,9 +312,11 @@ def register(
return hashlib.sha256(token.encode("utf-8")).hexdigest() return hashlib.sha256(token.encode("utf-8")).hexdigest()
def _issue_refresh_token(cur: sqlite3.Cursor, guid: str) -> Dict[str, Any]: def _issue_refresh_token(cur: sqlite3.Cursor, guid: str) -> Dict[str, Any]:
# Sliding window expiration; refreshed on each successful token refresh call.
REFRESH_TOKEN_TTL_DAYS = 90
token = secrets.token_urlsafe(48) token = secrets.token_urlsafe(48)
now = _now() now = _now()
expires_at = now.replace(microsecond=0) + timedelta(days=30) expires_at = now.replace(microsecond=0) + timedelta(days=REFRESH_TOKEN_TTL_DAYS)
cur.execute( cur.execute(
""" """
INSERT INTO refresh_tokens (id, guid, token_hash, created_at, expires_at) INSERT INTO refresh_tokens (id, guid, token_hash, created_at, expires_at)

View File

@@ -12,7 +12,7 @@ from __future__ import annotations
import hashlib import hashlib
import sqlite3 import sqlite3
from datetime import datetime, timezone from datetime import datetime, timezone, timedelta
from typing import Callable from typing import Callable
from flask import Blueprint, current_app, jsonify, request from flask import Blueprint, current_app, jsonify, request
@@ -28,6 +28,7 @@ def register(
dpop_validator: DPoPValidator, dpop_validator: DPoPValidator,
) -> None: ) -> None:
blueprint = Blueprint("tokens", __name__) blueprint = Blueprint("tokens", __name__)
REFRESH_TOKEN_TTL_DAYS = 90
def _hash_token(token: str) -> str: def _hash_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest() return hashlib.sha256(token.encode("utf-8")).hexdigest()
@@ -70,7 +71,8 @@ def register(
return jsonify({"error": "refresh_token_revoked"}), 401 return jsonify({"error": "refresh_token_revoked"}), 401
if expires_at: if expires_at:
try: try:
if _parse_iso(expires_at) <= datetime.now(tz=timezone.utc): parsed_expiry = _parse_iso(expires_at)
if parsed_expiry <= datetime.now(tz=timezone.utc):
return jsonify({"error": "refresh_token_expired"}), 401 return jsonify({"error": "refresh_token_expired"}), 401
except Exception: except Exception:
pass pass
@@ -124,10 +126,16 @@ def register(
""" """
UPDATE refresh_tokens UPDATE refresh_tokens
SET last_used_at = ?, SET last_used_at = ?,
expires_at = ?,
dpop_jkt = COALESCE(NULLIF(?, ''), dpop_jkt) dpop_jkt = COALESCE(NULLIF(?, ''), dpop_jkt)
WHERE id = ? WHERE id = ?
""", """,
(_iso_now(), jkt, record_id), (
_iso_now(),
_iso(datetime.now(tz=timezone.utc) + timedelta(days=REFRESH_TOKEN_TTL_DAYS)),
jkt,
record_id,
),
) )
conn.commit() conn.commit()
finally: finally: