Merge pull request #2958 from markparonyan/api-issue-09-aef9as

feat(api): return token lifetimes and document API keys as bearer tokens
This commit is contained in:
Jeremy Grossmann authored and GitHub committed 2026-10-08 04:35:19 +02:00
commit ed8b0c3e20
5 files changed
+97 -21

No files matched your search

@@ -36,7 +36,12 @@ from gns3server.services.access_tickets import TICKET_PREFIX
from .database import get_repository
log = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/access/users/login", auto_error=False)
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/v3/access/users/login",
auto_error=False,
description="Send the access token as a bearer token. "
"An API key (gns3_<api_key_id>_<secret>) is also accepted as the bearer token.",
)
def _reject_refresh_token(token_data) -> None:
+3 -17
View File
@@ -65,12 +65,7 @@ async def login(
headers={"WWW-Authenticate": "Bearer"},
)
token = schemas.Token(
access_token=auth_service.create_access_token(user.username, token_version=user.token_version),
token_type="bearer",
refresh_token=auth_service.create_refresh_token(user.username, token_version=user.token_version),
)
return token
return auth_service.create_token_response(user)
@router.post("/authenticate", response_model=schemas.Token)
@@ -91,12 +86,7 @@ async def authenticate(
headers={"WWW-Authenticate": "Bearer"},
)
token = schemas.Token(
access_token=auth_service.create_access_token(user.username, token_version=user.token_version),
token_type="bearer",
refresh_token=auth_service.create_refresh_token(user.username, token_version=user.token_version),
)
return token
return auth_service.create_token_response(user)
@router.post("/refresh", response_model=schemas.Token)
@@ -135,11 +125,7 @@ async def refresh_access_token(
headers={"WWW-Authenticate": "Bearer"},
)
return schemas.Token(
access_token=auth_service.create_access_token(user.username, token_version=user.token_version),
token_type="bearer",
refresh_token=auth_service.create_refresh_token(user.username, token_version=user.token_version),
)
return auth_service.create_token_response(user)
@router.post("/logout", status_code=status.HTTP_204_NO_CONTENT)
+2
View File
@@ -22,7 +22,9 @@ from pydantic import BaseModel
class Token(BaseModel):
access_token: str
token_type: str
expires_in: int
refresh_token: Optional[str] = None
refresh_expires_in: Optional[int] = None
class TokenData(BaseModel):
+18 -3
View File
@@ -29,7 +29,7 @@ from joserfc.jwk import OctKey
from pydantic import ValidationError
from gns3server.config import Config
from gns3server.schemas.controller.tokens import TokenData
from gns3server.schemas.controller.tokens import Token, TokenData
log = logging.getLogger(__name__)
@@ -47,6 +47,9 @@ def _extract_alg(token: str) -> str:
return "<undecodable>"
BEARER_TOKEN_TYPE = "bearer"
class AuthService:
def hash_password(self, password: str) -> str:
@@ -61,8 +64,9 @@ class AuthService:
def _create_token(self, username, token_version, token_type, expires_in, secret_key=None) -> str:
"""Shared helper to create any kind of signed JWT token."""
expire = datetime.now(timezone.utc) + timedelta(minutes=expires_in)
to_encode = {"sub": username, "exp": expire, "ver": token_version, "type": token_type}
issued_at = datetime.now(timezone.utc)
expire = issued_at + timedelta(minutes=expires_in)
to_encode = {"sub": username, "iat": issued_at, "exp": expire, "ver": token_version, "type": token_type}
if secret_key is None:
secret_key = Config.instance().settings.Controller.jwt_secret_key
if secret_key is None:
@@ -89,6 +93,17 @@ class AuthService:
expires_in = Config.instance().settings.Controller.jwt_refresh_token_expire_minutes
return self._create_token(username, token_version, "refresh", expires_in, secret_key)
def create_token_response(self, user) -> Token:
settings = Config.instance().settings.Controller
return Token(
access_token=self.create_access_token(user.username, token_version=user.token_version),
token_type=BEARER_TOKEN_TYPE,
expires_in=settings.jwt_access_token_expire_minutes * 60,
refresh_token=self.create_refresh_token(user.username, token_version=user.token_version),
refresh_expires_in=settings.jwt_refresh_token_expire_minutes * 60,
)
def get_token_data(self, token: str, secret_key: Optional[str] = None) -> TokenData:
def auth_error(detail: str) -> HTTPException: