fix(oauth): Auto-refresh expired tokens on integrations page load
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Previously, the integrations page only read token status from DB without attempting refresh, causing "Token wygasł" after 1h of inactivity despite valid refresh_token. Now get_connected_services() auto-refreshes expired tokens on page load, matching the behavior already present in audit flows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30ef2f554b
commit
fd04349c04
@ -197,6 +197,27 @@ class OAuthService:
|
|||||||
logger.error(f"Token refresh error for {provider}: {e}")
|
logger.error(f"Token refresh error for {provider}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _try_refresh_token(self, db, token) -> bool:
|
||||||
|
"""Attempt to refresh an expired token in-place. Returns True on success."""
|
||||||
|
if not token.refresh_token:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
new_data = self.refresh_access_token(token.provider, token.refresh_token)
|
||||||
|
if new_data and new_data.get('access_token'):
|
||||||
|
token.access_token = new_data['access_token']
|
||||||
|
expires_in = new_data.get('expires_in')
|
||||||
|
if expires_in:
|
||||||
|
token.expires_at = datetime.now() + timedelta(seconds=int(expires_in))
|
||||||
|
token.updated_at = datetime.now()
|
||||||
|
db.commit()
|
||||||
|
logger.info(f"Auto-refreshed {token.provider}/{token.service} for company {token.company_id}")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Token auto-refresh error {token.provider}/{token.service}: {e}")
|
||||||
|
db.rollback()
|
||||||
|
return False
|
||||||
|
|
||||||
def save_token(self, db, company_id: int, user_id: int, provider: str,
|
def save_token(self, db, company_id: int, user_id: int, provider: str,
|
||||||
service: str, token_data: Dict) -> bool:
|
service: str, token_data: Dict) -> bool:
|
||||||
"""Save or update OAuth token in database.
|
"""Save or update OAuth token in database.
|
||||||
@ -278,19 +299,13 @@ class OAuthService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Check if token is expired
|
# Check if token is expired
|
||||||
if token.is_expired and token.refresh_token:
|
if token.is_expired:
|
||||||
new_data = self.refresh_access_token(provider, token.refresh_token)
|
if token.refresh_token:
|
||||||
if new_data:
|
if not self._try_refresh_token(db, token):
|
||||||
token.access_token = new_data.get('access_token', token.access_token)
|
token.is_active = False
|
||||||
expires_in = new_data.get('expires_in')
|
db.commit()
|
||||||
if expires_in:
|
return None
|
||||||
token.expires_at = datetime.now() + timedelta(seconds=int(expires_in))
|
|
||||||
token.updated_at = datetime.now()
|
|
||||||
db.commit()
|
|
||||||
else:
|
else:
|
||||||
# Refresh failed, mark as inactive
|
|
||||||
token.is_active = False
|
|
||||||
db.commit()
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return token.access_token
|
return token.access_token
|
||||||
@ -316,12 +331,20 @@ class OAuthService:
|
|||||||
result = {}
|
result = {}
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
key = f"{token.provider}/{token.service}"
|
key = f"{token.provider}/{token.service}"
|
||||||
|
|
||||||
|
# Auto-refresh expired tokens
|
||||||
|
refresh_failed = False
|
||||||
|
if token.is_expired and token.refresh_token:
|
||||||
|
if not self._try_refresh_token(db, token):
|
||||||
|
refresh_failed = True
|
||||||
|
|
||||||
result[key] = {
|
result[key] = {
|
||||||
'connected': True,
|
'connected': True,
|
||||||
'account_name': token.account_name,
|
'account_name': token.account_name,
|
||||||
'account_id': token.account_id,
|
'account_id': token.account_id,
|
||||||
'expires_at': token.expires_at.isoformat() if token.expires_at else None,
|
'expires_at': token.expires_at.isoformat() if token.expires_at else None,
|
||||||
'is_expired': token.is_expired,
|
'is_expired': token.is_expired,
|
||||||
|
'refresh_failed': refresh_failed,
|
||||||
'updated_at': token.updated_at.isoformat() if token.updated_at else None,
|
'updated_at': token.updated_at.isoformat() if token.updated_at else None,
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -366,7 +366,11 @@
|
|||||||
{% if gbp_conn.get('is_expired') %}
|
{% if gbp_conn.get('is_expired') %}
|
||||||
<div class="oauth-status expired">
|
<div class="oauth-status expired">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||||
|
{% if gbp_conn.get('refresh_failed') %}
|
||||||
|
Automatyczne odświeżenie nie powiodło się — połącz ponownie
|
||||||
|
{% else %}
|
||||||
Token wygasł — wymagane ponowne połączenie
|
Token wygasł — wymagane ponowne połączenie
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="oauth-status connected">
|
<div class="oauth-status connected">
|
||||||
@ -417,7 +421,11 @@
|
|||||||
{% if sc_conn.get('is_expired') %}
|
{% if sc_conn.get('is_expired') %}
|
||||||
<div class="oauth-status expired">
|
<div class="oauth-status expired">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||||
|
{% if sc_conn.get('refresh_failed') %}
|
||||||
|
Automatyczne odświeżenie nie powiodło się — połącz ponownie
|
||||||
|
{% else %}
|
||||||
Token wygasł — wymagane ponowne połączenie
|
Token wygasł — wymagane ponowne połączenie
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="oauth-status connected">
|
<div class="oauth-status connected">
|
||||||
@ -469,7 +477,11 @@
|
|||||||
{% if fb_conn.get('is_expired') %}
|
{% if fb_conn.get('is_expired') %}
|
||||||
<div class="oauth-status expired">
|
<div class="oauth-status expired">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||||
|
{% if fb_conn.get('refresh_failed') %}
|
||||||
|
Automatyczne odświeżenie nie powiodło się — połącz ponownie
|
||||||
|
{% else %}
|
||||||
Token wygasł — wymagane ponowne połączenie
|
Token wygasł — wymagane ponowne połączenie
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="oauth-status connected">
|
<div class="oauth-status connected">
|
||||||
@ -516,7 +528,11 @@
|
|||||||
{% if ig_conn.get('is_expired') %}
|
{% if ig_conn.get('is_expired') %}
|
||||||
<div class="oauth-status expired">
|
<div class="oauth-status expired">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
||||||
|
{% if ig_conn.get('refresh_failed') %}
|
||||||
|
Automatyczne odświeżenie nie powiodło się — połącz ponownie
|
||||||
|
{% else %}
|
||||||
Token wygasł — wymagane ponowne połączenie
|
Token wygasł — wymagane ponowne połączenie
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="oauth-status connected">
|
<div class="oauth-status connected">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user