Skip to content

Commit 6650a63

Browse files
committed
More migration
1 parent d6b2473 commit 6650a63

2 files changed

Lines changed: 41 additions & 76 deletions

File tree

bluesky_httpserver/_authentication.py

Lines changed: 30 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import secrets
44
import uuid as uuid_module
55
import warnings
6-
from datetime import datetime, timedelta
6+
from datetime import datetime, timedelta, timezone
77
from typing import Any, Optional
88

99
from fastapi import APIRouter, Depends, Form, HTTPException, Query, Request, Response, Security, WebSocket
@@ -19,7 +19,7 @@
1919
# int_from_bytes is deprecated, use int.from_bytes instead
2020
with warnings.catch_warnings():
2121
warnings.simplefilter("ignore")
22-
from jose import ExpiredSignatureError, JWTError, jwt
22+
from jose import ExpiredSignatureError
2323

2424
import pydantic
2525
from packaging import version
@@ -31,6 +31,7 @@
3131
from pydantic_settings import BaseSettings
3232

3333
from . import schemas
34+
from bluesky_authentication import tokens as auth_tokens
3435
from .authorization._defaults import _DEFAULT_ANONYMOUS_PROVIDER_NAME
3536
from .core import json_or_msgpack
3637
from .database import orm
@@ -54,7 +55,6 @@
5455
get_current_username,
5556
)
5657

57-
ALGORITHM = "HS256"
5858
UNIT_SECOND = timedelta(seconds=1)
5959

6060
# Device code flow constants
@@ -64,7 +64,7 @@
6464

6565
def utcnow():
6666
"UTC now with second resolution"
67-
return datetime.utcnow().replace(microsecond=0)
67+
return datetime.now(timezone.utc).replace(microsecond=0)
6868

6969

7070
class Token(BaseModel):
@@ -123,53 +123,38 @@ async def __call__(self, request: Request) -> Optional[str]:
123123

124124

125125
def create_access_token(data, secret_key, expires_delta):
126-
to_encode = data.copy()
127-
expire = utcnow() + expires_delta
128-
to_encode.update({"exp": expire, "type": "access"})
129-
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=ALGORITHM)
130-
return encoded_jwt
126+
return auth_tokens.create_access_token(
127+
data,
128+
secret_key,
129+
expires_delta,
130+
utcnow=utcnow,
131+
)
131132

132133

133134
def create_refresh_token(session_id, secret_key, expires_delta):
134-
expire = utcnow() + expires_delta
135-
to_encode = {
136-
"type": "refresh",
137-
"sid": session_id,
138-
"exp": expire,
139-
}
140-
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=ALGORITHM)
141-
return encoded_jwt
142-
143-
144-
def _decode_token_with_secret_keys(token, secret_keys):
145-
# The first key in settings.secret_keys is used for *encoding*.
146-
# All keys are tried for *decoding* until one works or they all
147-
# fail. They support key rotation.
148-
for secret_key in secret_keys:
149-
try:
150-
payload = jwt.decode(token, secret_key, algorithms=[ALGORITHM])
151-
return payload
152-
except ExpiredSignatureError:
153-
# Do not let this be caught below with the other JWTError types.
154-
raise
155-
except JWTError:
156-
# Try the next key in the key rotation.
157-
continue
158-
return None
135+
return auth_tokens.create_refresh_token(
136+
session_id,
137+
secret_key,
138+
expires_delta,
139+
utcnow=utcnow,
140+
)
159141

160142

161-
def decode_token(token, secret_keys, proxied_authenticator=None):
143+
async def decode_token(token, secret_keys, proxied_authenticator=None):
162144
credentials_exception = HTTPException(
163145
status_code=401,
164146
detail="Could not validate credentials",
165147
headers={"WWW-Authenticate": "Bearer"},
166148
)
167-
payload = _decode_token_with_secret_keys(token, secret_keys)
168-
if payload is not None:
169-
return payload
170-
if proxied_authenticator is not None:
171-
return proxied_authenticator.decode_token(token)
172-
raise credentials_exception
149+
proxied_decoder = (
150+
proxied_authenticator.decode_token if proxied_authenticator is not None else None
151+
)
152+
return await auth_tokens.decode_token(
153+
token,
154+
secret_keys,
155+
proxied_decoder=proxied_decoder,
156+
credentials_exception=credentials_exception,
157+
)
173158

174159

175160
def _extract_scopes(decoded_access_token: dict[str, Any]) -> set[str]:
@@ -201,7 +186,7 @@ async def get_api_key(
201186
return None
202187

203188

204-
def get_current_principal(
189+
async def get_current_principal(
205190
request: Request,
206191
security_scopes: SecurityScopes,
207192
access_token: str = Depends(oauth2_scheme),
@@ -306,7 +291,7 @@ def get_current_principal(
306291
request.state.cookies_to_set.append({"key": API_KEY_COOKIE_NAME, "value": api_key})
307292
elif access_token is not None:
308293
try:
309-
payload = decode_token(
294+
payload = await decode_token(
310295
access_token,
311296
settings.secret_keys,
312297
_get_proxied_authenticator(authenticators),
@@ -1188,9 +1173,9 @@ def revoke_session(
11881173
return JSONResponse(status_code=200, content={"success": True, "msg": ""})
11891174

11901175

1191-
def slide_session(refresh_token, settings, db, api_access_manager):
1176+
async def slide_session(refresh_token, settings, db, api_access_manager):
11921177
try:
1193-
payload = decode_token(refresh_token, settings.secret_keys)
1178+
payload = await decode_token(refresh_token, settings.secret_keys)
11941179
except ExpiredSignatureError:
11951180
raise HTTPException(status_code=401, detail="Session has expired. Please re-authenticate.")
11961181
# Find this session in the database.
Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,11 @@
1-
try:
2-
from bluesky_authentication.protocols import (
3-
ExternalAuthenticator,
4-
InternalAuthenticator,
5-
UserSessionState,
6-
)
7-
except ModuleNotFoundError:
8-
from abc import ABC
9-
from dataclasses import dataclass
10-
from typing import Optional
11-
12-
from fastapi import Request
13-
14-
@dataclass
15-
class UserSessionState:
16-
"""Data transfer class to communicate custom session state information."""
17-
18-
user_name: str
19-
state: dict = None
20-
21-
class InternalAuthenticator(ABC):
22-
"""Base class for authenticators that use username/password credentials."""
23-
24-
async def authenticate(self, username: str, password: str) -> Optional[UserSessionState]:
25-
raise NotImplementedError
26-
27-
class ExternalAuthenticator(ABC):
28-
"""Base class for authenticators that use external identity providers."""
29-
30-
async def authenticate(self, request: Request) -> Optional[UserSessionState]:
31-
raise NotImplementedError
1+
from bluesky_authentication.protocols import ( # noqa: F401
2+
ExternalAuthenticator,
3+
InternalAuthenticator,
4+
UserSessionState,
5+
)
6+
7+
__all__ = [
8+
"ExternalAuthenticator",
9+
"InternalAuthenticator",
10+
"UserSessionState",
11+
]

0 commit comments

Comments
 (0)