From 13703c35a249fdd70a9d0bb2a4245925c9dfe149 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Fri, 23 Oct 2026 02:38:43 +0200 Subject: [PATCH] use S256 pkce challenge instead of plain Co-Authored-By: Claude Opus 5.5 --- bot/src/cogs/galtinn.py | 12 +++++++++--- verification_server/src/server.py | 5 +++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/bot/src/cogs/galtinn.py b/bot/src/cogs/galtinn.py index f78d6fa..fe3edff 100644 --- a/bot/src/cogs/galtinn.py +++ b/bot/src/cogs/galtinn.py @@ -1,4 +1,6 @@ import asyncio +import base64 +import hashlib import secrets import urllib.parse @@ -544,8 +546,11 @@ class Galtinn(commands.Cog): await interaction.followup.send(embed=embed) return - challenge = f"{secrets.token_urlsafe(32)}" # TODO: generate challenge based on private key? - state = f"{secrets.token_urlsafe(32)}" + # PKCE (S256). Only the hashed challenge is sent to Galtinn. The verifier is kept in the database + # (in the challenge column) for the verification server to complete the token exchange with + verifier = secrets.token_urlsafe(32) + challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=") + state = secrets.token_urlsafe(32) # Insert into verfications table, unless the user already has a pending verification. # Expired verifications that haven't been cleaned up yet are replaced @@ -559,7 +564,7 @@ class Galtinn(commands.Cog): RETURNING TRUE; """, interaction.user.id, - challenge, + verifier, state, ) if not inserted: @@ -575,6 +580,7 @@ class Galtinn(commands.Cog): "response_type": "code", "redirect_uri": self.bot.galtinn["redirect_uri"], "code_challenge": challenge, + "code_challenge_method": "S256", "state": state, } url = f"{base_url}?{urllib.parse.urlencode(params)}" diff --git a/verification_server/src/server.py b/verification_server/src/server.py index 9ede3d3..e15d98f 100644 --- a/verification_server/src/server.py +++ b/verification_server/src/server.py @@ -66,7 +66,8 @@ async def callback(request: Request, code: str, state: str): status_code=400, ) - discord_id, code_challenge = list(verification.values()) + # The challenge column holds the PKCE code verifier + discord_id, code_verifier = list(verification.values()) # Get auth_token from code payload = { @@ -74,7 +75,7 @@ async def callback(request: Request, code: str, state: str): "grant_type": "authorization_code", "code": code, "redirect_uri": GALTINN_REDIRECT_URI, - "code_verifier": code_challenge, + "code_verifier": code_verifier, } async with aiohttp.ClientSession() as session: async with session.post(f"{GALTINN_API_URL}/oauth/token/", data=payload) as r: