From 2bd6c98538d357a2e6d15679f448528b475f101f Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Wed, 1 Feb 2023 20:12:53 +0100 Subject: [PATCH 1/3] Add OAuth 2.0 introspection endpoint --- app/indieauth.py | 38 ++++++++++++++++++++++++++++++++++++++ app/main.py | 2 +- app/micropub.py | 4 ++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/indieauth.py b/app/indieauth.py index 31ea458..57640b5 100644 --- a/app/indieauth.py +++ b/app/indieauth.py @@ -41,6 +41,7 @@ async def well_known_authorization_server( "revocation_endpoint": request.url_for("indieauth_revocation_endpoint"), "revocation_endpoint_auth_methods_supported": ["none"], "registration_endpoint": request.url_for("oauth_registration_endpoint"), + "introspection_endpoint": request.url_for("oauth_introspection_endpoint"), } @@ -378,6 +379,8 @@ async def _check_access_token( class AccessTokenInfo: scopes: list[str] client_id: str | None + access_token: str + exp: int async def verify_access_token( @@ -409,6 +412,13 @@ async def verify_access_token( if access_token.indieauth_authorization_request else None ), + access_token=access_token.access_token, + exp=int( + ( + access_token.created_at.replace(tzinfo=timezone.utc) + + timedelta(seconds=access_token.expires_in) + ).timestamp() + ), ) @@ -434,6 +444,13 @@ async def check_access_token( if access_token.indieauth_authorization_request else None ), + access_token=access_token.access_token, + exp=int( + ( + access_token.created_at.replace(tzinfo=timezone.utc) + + timedelta(seconds=access_token.expires_in) + ).timestamp() + ), ) logger.info( @@ -474,3 +491,24 @@ async def indieauth_revocation_endpoint( content={}, status_code=200, ) + + +@router.post("/token_introspection") +async def oauth_introspection_endpoint( + request: Request, + access_token_info: AccessTokenInfo = Depends(enforce_access_token), + token: str = Form(), +) -> JSONResponse: + # Ensure the requested token is the same as bearer token + if token != access_token_info.access_token: + raise HTTPException(status_code=401, detail="access token required") + + return JSONResponse( + content={ + "active": True, + "client_id": access_token_info.client_id, + "scope": " ".join(access_token_info.scopes), + "exp": access_token_info.exp, + }, + status_code=200, + ) diff --git a/app/main.py b/app/main.py index da96bc0..4cd987d 100644 --- a/app/main.py +++ b/app/main.py @@ -1696,7 +1696,7 @@ async def _gen_rss_feed( fe.id(outbox_object.url) if outbox_object.name is not None: fe.title(outbox_object.name) - elif not is_rss: # Atom feeds require a title + elif not is_rss: # Atom feeds require a title fe.title(outbox_object.url) fe.link(href=outbox_object.url) diff --git a/app/micropub.py b/app/micropub.py index 24c63f5..91692e6 100644 --- a/app/micropub.py +++ b/app/micropub.py @@ -132,7 +132,7 @@ async def post_micropub_endpoint( h = form_data["h"] entry_type = f"h-{h}" - logger.info(f"Creating {entry_type}") + logger.info(f"Creating {entry_type=} with {access_token_info=}") if entry_type != "h-entry": return JSONResponse( @@ -150,7 +150,7 @@ async def post_micropub_endpoint( else: content = form_data["content"] - public_id = await send_create( + public_id, _ = await send_create( db_session, "Note", content, From 625f399309b81c065cde088239eb675cebe55e20 Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Fri, 3 Feb 2023 08:32:50 +0100 Subject: [PATCH 2/3] Fix OAuth introspection endpoint --- app/indieauth.py | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/app/indieauth.py b/app/indieauth.py index 57640b5..7e657ea 100644 --- a/app/indieauth.py +++ b/app/indieauth.py @@ -10,6 +10,8 @@ from fastapi import Form from fastapi import HTTPException from fastapi import Request from fastapi.responses import JSONResponse +from fastapi.security import HTTPBasic +from fastapi.security import HTTPBasicCredentials from loguru import logger from pydantic import BaseModel from sqlalchemy import select @@ -26,6 +28,8 @@ from app.redirect import redirect from app.utils import indieauth from app.utils.datetime import now +basic_auth = HTTPBasic() + router = APIRouter() @@ -496,19 +500,49 @@ async def indieauth_revocation_endpoint( @router.post("/token_introspection") async def oauth_introspection_endpoint( request: Request, - access_token_info: AccessTokenInfo = Depends(enforce_access_token), + credentials: HTTPBasicCredentials = Depends(basic_auth), + db_session: AsyncSession = Depends(get_db_session), token: str = Form(), ) -> JSONResponse: - # Ensure the requested token is the same as bearer token - if token != access_token_info.access_token: - raise HTTPException(status_code=401, detail="access token required") + registered_client = ( + await db_session.scalars( + select(models.OAuthClient).where( + models.OAuthClient.client_id == credentials.username, + models.OAuthClient.client_secret == credentials.password, + ) + ) + ).one_or_none() + if not registered_client: + raise HTTPException(status_code=401, detail="unauthenticated") + + access_token = ( + await db_session.scalars( + select(models.IndieAuthAccessToken) + .where(models.IndieAuthAccessToken.access_token == token) + .join( + models.IndieAuthAuthorizationRequest, + models.IndieAuthAccessToken.indieauth_authorization_request_id + == models.IndieAuthAuthorizationRequest.id, + ) + .where( + models.IndieAuthAuthorizationRequest.client_id == credentials.username + ) + ) + ).one_or_none() + if not access_token: + return JSONResponse(content={"active": False}) return JSONResponse( content={ "active": True, - "client_id": access_token_info.client_id, - "scope": " ".join(access_token_info.scopes), - "exp": access_token_info.exp, + "client_id": credentials.username, + "scope": access_token.scope, + "exp": int( + ( + access_token.created_at.replace(tzinfo=timezone.utc) + + timedelta(seconds=access_token.expires_in) + ).timestamp() + ), }, status_code=200, ) From 4e1bb330aa312ae98cd8ef4ad73ca89510278ce3 Mon Sep 17 00:00:00 2001 From: Thomas Sileo Date: Fri, 3 Feb 2023 08:55:31 +0100 Subject: [PATCH 3/3] Fix OAuth introspection endpoint --- app/indieauth.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/indieauth.py b/app/indieauth.py index 7e657ea..ea4d21d 100644 --- a/app/indieauth.py +++ b/app/indieauth.py @@ -532,6 +532,10 @@ async def oauth_introspection_endpoint( if not access_token: return JSONResponse(content={"active": False}) + is_token_valid, _ = await _check_access_token(db_session, token) + if not is_token_valid: + return JSONResponse(content={"active": False}) + return JSONResponse( content={ "active": True,