Skip to content

Commit eee3d07

Browse files
authored
Merge pull request #233 from MorpheusAIs/feature/usage-with-api-key
feat: allow get billing info using api key
2 parents 72fb77c + d0a5b89 commit eee3d07

2 files changed

Lines changed: 71 additions & 10 deletions

File tree

src/api/v1/billing/index.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from ....db.database import get_db_session
1212
from ....db.models import User, LedgerEntryType
13-
from ....dependencies import get_current_user, get_api_key_user
13+
from ....dependencies import get_current_user, get_user_jwt_or_api_key
1414
from ....services.billing_service import billing_service
1515
from ....crud import credits as credits_crud
1616
from ....schemas.billing import (
@@ -40,10 +40,12 @@
4040
async def get_balance(
4141
request: Request,
4242
db: AsyncSession = Depends(get_db_session),
43-
current_user: User = Depends(get_current_user),
43+
current_user: User = Depends(get_user_jwt_or_api_key),
4444
):
4545
"""
4646
Get current credit balance for the authenticated user.
47+
48+
Authenticate with either a Cognito JWT or an ``sk-…`` API key.
4749
4850
Returns:
4951
- paid: Paid bucket balance (posted, holds, available)
@@ -132,11 +134,13 @@ async def list_transactions(
132134
from_date: Optional[datetime] = Query(default=None, alias="from"),
133135
to_date: Optional[datetime] = Query(default=None, alias="to"),
134136
db: AsyncSession = Depends(get_db_session),
135-
current_user: User = Depends(get_current_user),
137+
current_user: User = Depends(get_user_jwt_or_api_key),
136138
):
137139
"""
138140
Get paginated list of credit transactions (ledger entries).
139-
141+
142+
Authenticate with either a Cognito JWT or an ``sk-…`` API key.
143+
140144
Parameters:
141145
- limit: Maximum number of items to return (1-∞)
142146
- offset: Number of items to skip
@@ -226,11 +230,13 @@ async def get_monthly_spending(
226230
year: int = Query(default=None, description="Year for spending data (defaults to current year)"),
227231
mode: SpendingModeEnum = Query(default=SpendingModeEnum.gross),
228232
db: AsyncSession = Depends(get_db_session),
229-
current_user: User = Depends(get_current_user),
233+
current_user: User = Depends(get_user_jwt_or_api_key),
230234
):
231235
"""
232236
Get monthly spending metrics for a year.
233-
237+
238+
Authenticate with either a Cognito JWT or an ``sk-…`` API key.
239+
234240
Parameters:
235241
- year: Year to get spending for (defaults to current year)
236242
- mode:
@@ -300,11 +306,13 @@ async def list_usage(
300306
to_date: Optional[datetime] = Query(default=None, alias="to"),
301307
model: Optional[str] = Query(default=None),
302308
db: AsyncSession = Depends(get_db_session),
303-
current_user: User = Depends(get_current_user),
309+
current_user: User = Depends(get_user_jwt_or_api_key),
304310
):
305311
"""
306312
Get paginated list of usage entries (posted usage charges only).
307-
313+
314+
Authenticate with either a Cognito JWT or an ``sk-…`` API key.
315+
308316
Parameters:
309317
- limit: Maximum number of items to return (1-∞)
310318
- offset: Number of items to skip
@@ -374,11 +382,13 @@ async def list_usage_for_month(
374382
limit: int = Query(default=50, ge=1),
375383
offset: int = Query(default=0, ge=0),
376384
db: AsyncSession = Depends(get_db_session),
377-
current_user: User = Depends(get_current_user),
385+
current_user: User = Depends(get_user_jwt_or_api_key),
378386
):
379387
"""
380388
Get paginated list of usage entries for a specific month.
381-
389+
390+
Authenticate with either a Cognito JWT or an ``sk-…`` API key.
391+
382392
Parameters:
383393
- year: Year
384394
- month: Month (1-12)

src/dependencies.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,11 +553,62 @@ async def get_current_api_key(
553553
return auth.api_key
554554

555555

556+
# ---------------------------------------------------------------------------
557+
# Union auth: accept either Cognito JWT or sk-… API key
558+
# ---------------------------------------------------------------------------
559+
560+
async def get_user_jwt_or_api_key(
561+
db: AsyncSession = Depends(get_db_session),
562+
token: Optional[HTTPAuthorizationCredentials] = Depends(oauth2_scheme_optional),
563+
api_key_str: Optional[str] = Security(api_key_header),
564+
) -> User:
565+
"""
566+
Authenticate via either a Cognito JWT or an ``sk-…`` API key and return
567+
the associated :class:`User`.
568+
569+
Both schemes are carried in the ``Authorization`` header; the scheme is
570+
selected purely by the credential prefix:
571+
572+
* value starts with ``sk-`` → treated as API key, delegated to
573+
:func:`get_api_key_auth`
574+
* anything else → treated as a Cognito JWT, delegated to
575+
:func:`get_current_user`
576+
577+
Intended for read-only endpoints (e.g. billing GETs) that should be
578+
reachable from both the dashboard (JWT) and programmatic clients (key).
579+
"""
580+
# Local testing bypass mirrors the underlying dependencies.
581+
from src.core.local_testing import is_local_testing_mode, get_or_create_test_user
582+
if is_local_testing_mode():
583+
return await get_or_create_test_user(db)
584+
585+
# Both HTTPBearer and APIKeyHeader read the same Authorization header.
586+
# Prefer the HTTPBearer-parsed credential (already stripped of "Bearer ");
587+
# fall back to the raw header for clients that omit the scheme prefix.
588+
raw = token.credentials if token else (api_key_str or "")
589+
if raw.startswith("Bearer "):
590+
raw = raw[7:]
591+
592+
if not raw:
593+
raise HTTPException(
594+
status_code=status.HTTP_401_UNAUTHORIZED,
595+
detail="Not authenticated",
596+
headers={"WWW-Authenticate": "Bearer"},
597+
)
598+
599+
if raw.startswith("sk-"):
600+
auth = await get_api_key_auth(api_key_str=raw)
601+
return auth.user
602+
603+
return await get_current_user(db=db, token=token)
604+
605+
556606
# ---------------------------------------------------------------------------
557607
# Type aliases for commonly used dependency chains
558608
# ---------------------------------------------------------------------------
559609
CurrentUser = Annotated[User, Depends(get_current_user)]
560610
APIKeyUser = Annotated[User, Depends(get_api_key_user)]
561611
CurrentAPIKey = Annotated[APIKey, Depends(get_current_api_key)]
562612
APIKeyAuthentication = Annotated[APIKeyAuth, Depends(get_api_key_auth)]
613+
JwtOrApiKeyUser = Annotated[User, Depends(get_user_jwt_or_api_key)]
563614
DBSession = Annotated[AsyncSession, Depends(get_db_session)]

0 commit comments

Comments
 (0)