Skip to content

Commit a3bb23e

Browse files
authored
fix(remote): pick-up new tokens on pre-login sessions (#213)
* chore(dep): drop unused calamus * chore(dep): constrain minimal compatible linkml version * sec(dep): floor pynacl>=1.6.2 to protect from CVE-2025-69277 * chore(dep): declare explicit imports * chore(dep): cap typer for sphinx-click compat * docs(cli): fix out of date cli path * chore(dep): update lock file * test(c4gh): adapt to new key-generation api * chore(c4gh): drop unnecessary comment * test: add check for preserved links post-removal * fix(api): preserve links upon remove * test(htsget): add overlap test for fully contained region * fix(htsget): consider contained interval as overlap * test(remote): token pickup on pre-login sessions * fix(auth): reload cached token on each request * refactor(remote): flatten token refresh logic * chore(remote): drop unneeded comment * chore(remote): better user-facing signal for token refresh
1 parent 4f7130f commit a3bb23e

2 files changed

Lines changed: 43 additions & 10 deletions

File tree

src/modos/remote.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,26 @@
77
import os
88
from pathlib import Path
99
from typing import Any, Self
10-
import warnings
1110

1211
import jwt
12+
from loguru import logger
1313
from pydantic import BaseModel, HttpUrl, validate_call
1414
from pydantic.dataclasses import dataclass
1515
import requests
1616
from requests.auth import AuthBase
1717

1818

1919
class BearerAuth(AuthBase):
20-
def __init__(self):
21-
self.jwt = JWT.from_cache()
22-
2320
def __call__(
2421
self, r: requests.PreparedRequest
2522
) -> requests.PreparedRequest:
26-
if self.jwt:
27-
if self.jwt.is_expired:
28-
self.jwt = self.jwt.refresh()
29-
if self.jwt:
30-
r.headers["Authorization"] = f"Bearer {self.jwt.access_token}"
23+
token = JWT.from_cache()
24+
if not token:
25+
return r
26+
if token.is_expired:
27+
token = token.refresh()
28+
if token:
29+
r.headers["Authorization"] = f"Bearer {token.access_token}"
3130
return r
3231

3332

@@ -230,7 +229,10 @@ def is_expired(self, skew: int = 30) -> bool:
230229
)
231230

232231
def refresh(self) -> JWT | None:
233-
warnings.warn("Token refresh is not yet implemented.")
232+
# TODO: implement token refresh
233+
logger.warning(
234+
"Your session has expired. Run `modos login` to re-authenticate."
235+
)
234236
return None
235237

236238

tests/test_remote.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Tests for remote authentication handling."""
2+
3+
import jwt
4+
import pytest
5+
import requests
6+
7+
from modos import remote
8+
from modos.remote import BearerAuth, JWT
9+
10+
# Far-future expiry so the token is never considered expired.
11+
_FUTURE_EXP = 4102444800 # 2100-01-01 UTC
12+
13+
14+
@pytest.fixture
15+
def cache_dir(tmp_path, monkeypatch):
16+
"""Redirect the JWT cache to a temporary directory."""
17+
monkeypatch.setattr(remote, "get_cache_dir", lambda: tmp_path)
18+
return tmp_path
19+
20+
21+
def test_bearer_auth_picks_up_token_cached_after_construction(cache_dir):
22+
"""A session built before login must still send a token cached later."""
23+
auth = BearerAuth() # constructed while logged out
24+
25+
token = jwt.encode({"exp": _FUTURE_EXP}, "secret" * 8)
26+
JWT(token).to_cache() # simulate a later login
27+
28+
req = requests.Request("GET", "http://modos.example.org").prepare()
29+
auth(req)
30+
31+
assert req.headers.get("Authorization") == f"Bearer {token}"

0 commit comments

Comments
 (0)