Skip to content

Commit 25e626e

Browse files
committed
feat: add functions to cleanup the AuthDB tables
1 parent ae39fb4 commit 25e626e

4 files changed

Lines changed: 111 additions & 1 deletion

File tree

diracx-core/src/diracx/core/settings.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ class AuthSettings(ServiceSettingsBase):
183183
before it must be exchanged for tokens. Default: 5 minutes.
184184
"""
185185

186+
completed_flow_retention_minutes: int = 60
187+
"""Retention time in minutes for completed flow.
188+
189+
The maximum retention time of flow after being completed
190+
and before they are deleted. Default: 60 minutes.
191+
"""
192+
186193
state_key: FernetKey
187194
"""Encryption key used to encrypt/decrypt the state parameter passed to the IAM.
188195
@@ -226,6 +233,13 @@ class AuthSettings(ServiceSettingsBase):
226233
through a new authentication flow. Default: 60 minutes.
227234
"""
228235

236+
revoked_refresh_token_retention_days: int = 30
237+
"""Retention time in days for revoked refresh tokens.
238+
239+
The maximum retention time of refresh tokens after being
240+
revoked and before they are deleted. Default: 30 days.
241+
"""
242+
229243
available_properties: set[SecurityProperty] = Field(
230244
default_factory=SecurityProperty.available_properties
231245
)

diracx-db/src/diracx/db/sql/auth/db.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from itertools import pairwise
77

88
from dateutil.rrule import MONTHLY, rrule
9-
from sqlalchemy import insert, select, text, update
9+
from sqlalchemy import delete, insert, select, text, update
1010
from sqlalchemy.exc import IntegrityError, NoResultFound
1111
from sqlalchemy.ext.asyncio import AsyncConnection
1212
from uuid_utils import UUID, uuid7
@@ -339,3 +339,47 @@ async def revoke_user_refresh_tokens(self, subject):
339339
.where(RefreshTokens.sub == subject)
340340
.values(status=RefreshTokenStatus.REVOKED)
341341
)
342+
343+
async def clean_expired_refresh_token(
344+
self, max_validity: int, max_retention: int
345+
) -> tuple[int, int]:
346+
"""Delete expired and old revoked refresh tokens."""
347+
expired_date = str(
348+
uuid7_from_datetime(substract_date(minutes=max_validity), randomize=False)
349+
)
350+
stmt_expired = delete(RefreshTokens).where(
351+
RefreshTokens.status == RefreshTokenStatus.CREATED,
352+
RefreshTokens.jti < expired_date,
353+
)
354+
res_expired = await self.conn.execute(stmt_expired)
355+
356+
revoked_date = str(
357+
uuid7_from_datetime(substract_date(days=max_retention), randomize=False)
358+
)
359+
stmt_revoked = delete(RefreshTokens).where(
360+
RefreshTokens.status == RefreshTokenStatus.REVOKED,
361+
RefreshTokens.jti < revoked_date,
362+
)
363+
res_revoked = await self.conn.execute(stmt_revoked)
364+
365+
return res_expired.rowcount, res_revoked.rowcount
366+
367+
async def clean_expired_authorization_flows(self, max_retention: int) -> int:
368+
"""Delete old DONE/ERROR authorization flows."""
369+
stmt_auth = delete(AuthorizationFlows).where(
370+
AuthorizationFlows.status.in_([FlowStatus.DONE, FlowStatus.ERROR]),
371+
AuthorizationFlows.creation_time < substract_date(minutes=max_retention),
372+
)
373+
res_auth = await self.conn.execute(stmt_auth)
374+
375+
return res_auth.rowcount
376+
377+
async def clean_expired_device_flows(self, max_retention: int) -> int:
378+
"""Delete old DONE/ERROR device flows."""
379+
stmt_device = delete(DeviceFlows).where(
380+
DeviceFlows.status.in_([FlowStatus.DONE, FlowStatus.ERROR]),
381+
DeviceFlows.creation_time < substract_date(minutes=max_retention),
382+
)
383+
res_device = await self.conn.execute(stmt_device)
384+
385+
return res_device.rowcount

diracx-logic/src/diracx/logic/auth/token.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import base64
66
import hashlib
7+
import logging
78
import re
89
from datetime import datetime, timedelta, timezone
910
from typing import cast
@@ -37,6 +38,8 @@
3738
verify_dirac_refresh_token,
3839
)
3940

41+
logger = logging.getLogger(__name__)
42+
4043

4144
async def get_oidc_token(
4245
grant_type: GrantType,
@@ -429,6 +432,8 @@ async def get_authorization_flow(auth_db: AuthDB, code: str, max_validity: int):
429432
"""Get the authorization flow from the DB and check few parameters before returning it."""
430433
res = await auth_db.get_authorization_flow(code, max_validity)
431434

435+
print(f"Flow status : {res['Status']}")
436+
432437
if res["Status"] == FlowStatus.READY:
433438
await auth_db.update_authorization_flow_status(code, FlowStatus.DONE)
434439
return res
@@ -437,3 +442,32 @@ async def get_authorization_flow(auth_db: AuthDB, code: str, max_validity: int):
437442
raise AuthorizationError("Code was already used")
438443

439444
raise AuthorizationError("Bad state in authorization flow")
445+
446+
447+
async def cleanup_expired_data(auth_db: AuthDB, settings: AuthSettings) -> None:
448+
"""Remove expired data from the auth database."""
449+
expired_tokens, revoked_tokens = await auth_db.clean_expired_refresh_token(
450+
max_validity=settings.refresh_token_expire_minutes,
451+
max_retention=settings.revoked_refresh_token_retention_days,
452+
)
453+
logger.info(
454+
"Deleted %d expired and %d revoked refresh tokens",
455+
expired_tokens,
456+
revoked_tokens,
457+
)
458+
459+
auth = await auth_db.clean_expired_authorization_flows(
460+
max_retention=settings.completed_flow_retention_minutes,
461+
)
462+
logger.info(
463+
"Deleted %d expired authorization flows",
464+
auth,
465+
)
466+
467+
device = await auth_db.clean_expired_device_flows(
468+
max_retention=settings.completed_flow_retention_minutes,
469+
)
470+
logger.info(
471+
"Deleted %d expired device flows",
472+
device,
473+
)

docs/admin/reference/env-variables.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ Expiration time in seconds for authorization code flow.
4242
The time window during which the authorization code remains valid
4343
before it must be exchanged for tokens. Default: 5 minutes.
4444

45+
### `DIRACX_SERVICE_AUTH_COMPLETED_FLOW_RETENTION_MINUTES`
46+
47+
*Optional*, default value: `60`
48+
49+
Retention time in minutes for completed flow.
50+
51+
The maximum retention time of flow after being completed
52+
and before they are deleted. Default: 60 minutes.
53+
4554
### `DIRACX_SERVICE_AUTH_STATE_KEY`
4655

4756
**Required**
@@ -96,6 +105,15 @@ Expiration time in minutes for refresh tokens.
96105
The maximum lifetime of refresh tokens before they must be re-issued
97106
through a new authentication flow. Default: 60 minutes.
98107

108+
### `DIRACX_SERVICE_AUTH_REVOKED_REFRESH_TOKEN_RETENTION_DAYS`
109+
110+
*Optional*, default value: `30`
111+
112+
Retention time in days for revoked refresh tokens.
113+
114+
The maximum retention time of refresh tokens after being
115+
revoked and before they are deleted. Default: 30 days.
116+
99117
### `DIRACX_SERVICE_AUTH_AVAILABLE_PROPERTIES`
100118

101119
*Optional*

0 commit comments

Comments
 (0)