From 67650b65235f994c597e09895dfe385b479a3fdf Mon Sep 17 00:00:00 2001 From: Bluenix Date: Thu, 12 Aug 2021 21:40:01 +0200 Subject: [PATCH 1/5] Check for wrong token before all other checks When passing a refresh token (that does not have an expiration key), the application would attempt get 'expiration' from a token missing that key. Raising a KeyError. --- pixels/utils/auth.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pixels/utils/auth.py b/pixels/utils/auth.py index e628b59..8bc09ae 100644 --- a/pixels/utils/auth.py +++ b/pixels/utils/auth.py @@ -39,11 +39,12 @@ async def __call__(self, request: Request): # Handle bad scenarios + if token_data["grant_type"] != "access_token": + raise HTTPException(status_code=403, detail=AuthState.WRONG_TOKEN) + expired = int(token_data["expiration"]) < datetime.now(timezone.utc).timestamp() if user_state is None or user_state["key_salt"] != token_data["salt"] or expired: raise HTTPException(status_code=403, detail=AuthState.INVALID_TOKEN.value) - elif token_data["grant_type"] != "refresh_token": - raise HTTPException(status_code=403, detail=AuthState.WRONG_TOKEN) elif user_state["is_banned"]: raise HTTPException(status_code=403, detail=AuthState.BANNED.value) elif self.is_mod_endpoint and not user_state["is_mod"]: From a4ec55f60935f04c20b9600af8ba2fa1d499f091 Mon Sep 17 00:00:00 2001 From: Bluenix Date: Thu, 12 Aug 2021 21:41:40 +0200 Subject: [PATCH 2/5] Fix wrong grant types used when generating tokens From looking at Discord's OAuth2 implementation, `authorization_code` and `refresh_token` was used instead of `refresh_token` and `access_token`. --- pixels/utils/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pixels/utils/auth.py b/pixels/utils/auth.py index 8bc09ae..da220f0 100644 --- a/pixels/utils/auth.py +++ b/pixels/utils/auth.py @@ -79,7 +79,7 @@ async def reset_user_token(conn: Connection, user_id: str) -> str: return jwt.encode( { "id": user_id, - "grant_type": "authorization_code", + "grant_type": "refresh_token", "salt": token_salt }, Server.JWT_SECRET, @@ -111,7 +111,7 @@ async def generate_access_token(conn: Connection, refresh_token: str) -> str: return jwt.encode( { "id": token_data["id"], - "grant_type": "refresh_token", + "grant_type": "access_token", "expiration": expiration.timestamp(), "salt": key_salt }, From 745c06cf1dcfb26e106c25bd0956d186da726b47 Mon Sep 17 00:00:00 2001 From: Bluenix Date: Thu, 12 Aug 2021 21:49:05 +0200 Subject: [PATCH 3/5] Touch up api_token.html template after access token introduction --- pixels/templates/api_token.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pixels/templates/api_token.html b/pixels/templates/api_token.html index 976154f..ffc9b0f 100644 --- a/pixels/templates/api_token.html +++ b/pixels/templates/api_token.html @@ -23,11 +23,14 @@
-

API token:

+

Refresh token:

+
+

You're not done quite yet! You need to turn this into an access token before making requests to the API.

+

Refer to the documentation for how to do this.

From f113b9e192f616a752ee3f08f58c7453dc612ed7 Mon Sep 17 00:00:00 2001 From: Bluenix Date: Thu, 12 Aug 2021 21:53:03 +0200 Subject: [PATCH 4/5] Correctly send enum value to HTTPException detail --- pixels/utils/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixels/utils/auth.py b/pixels/utils/auth.py index da220f0..ed3bf9c 100644 --- a/pixels/utils/auth.py +++ b/pixels/utils/auth.py @@ -40,7 +40,7 @@ async def __call__(self, request: Request): # Handle bad scenarios if token_data["grant_type"] != "access_token": - raise HTTPException(status_code=403, detail=AuthState.WRONG_TOKEN) + raise HTTPException(status_code=403, detail=AuthState.WRONG_TOKEN.value) expired = int(token_data["expiration"]) < datetime.now(timezone.utc).timestamp() if user_state is None or user_state["key_salt"] != token_data["salt"] or expired: From d10564c1ac6ee6fb99b50efd1d5d7a92c861f535 Mon Sep 17 00:00:00 2001 From: Bluenix Date: Thu, 12 Aug 2021 23:59:20 +0200 Subject: [PATCH 5/5] Validate that an access token isn't used to generate a new access token --- pixels/utils/auth.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pixels/utils/auth.py b/pixels/utils/auth.py index ed3bf9c..cf958a4 100644 --- a/pixels/utils/auth.py +++ b/pixels/utils/auth.py @@ -99,6 +99,9 @@ async def generate_access_token(conn: Connection, refresh_token: str) -> str: except JWTError: raise HTTPException(status_code=403, detail=AuthState.INVALID_TOKEN.value) + if token_data["grant_type"] != "refresh_token": + raise HTTPException(status_code=403, detail=AuthState.WRONG_TOKEN.value) + is_banned, key_salt = await conn.fetchrow( "SELECT is_banned, key_salt FROM users WHERE user_id = $1", int(token_data["id"]) )