Skip to content

Commit

Permalink
fix(api keys): robust to replay of provisioned URL
Browse files Browse the repository at this point in the history
  • Loading branch information
jayceslesar committed Jul 22, 2024
1 parent 080d92d commit 242003d
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 16 deletions.
14 changes: 9 additions & 5 deletions masterbase/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,16 +348,20 @@ def provision_handler(request: Request) -> str:
add_loser(app.state.engine, steam_id)
return "limited"

api_key = check_steam_id_has_api_key(engine, steam_id)
api_key, existing_oid_hash = check_steam_id_has_api_key(engine, steam_id)
new_api_key = generate_api_key()
invalidated_text = ""
oid_hash = str(hash(str(request.url)))
if api_key is not None:
# invalidate old API key and provision a new one
invalidated_text = "Your old key was invalidated!"
update_api_key(engine, steam_id, new_api_key)
if oid_hash == existing_oid_hash:
new_api_key = api_key
else:
# invalidate old API key and provision a new one
invalidated_text = "Your old key was invalidated!"
update_api_key(engine, steam_id, new_api_key, oid_hash)

else:
provision_api_key(engine, steam_id, new_api_key)
provision_api_key(engine, steam_id, new_api_key, oid_hash)

text = f"Successfully authenticated! Your API key is '{new_api_key}' {invalidated_text} Do not lose this as the client needs it!" # noqa

Expand Down
34 changes: 23 additions & 11 deletions masterbase/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,40 +626,52 @@ def list_demos_helper(
return rows


def check_steam_id_has_api_key(engine: Engine, steam_id: str) -> str | None:
def check_steam_id_has_api_key(engine: Engine, steam_id: str) -> tuple[str | None, str | None]:
"""Check that a given steam id has an API key or not."""
with engine.connect() as conn:
result = conn.execute(
sa.text("SELECT api_key FROM api_keys WHERE steam_id = :steam_id"), {"steam_id": steam_id}
).scalar_one_or_none()
sa.text("SELECT api_key, oid_hash FROM api_keys WHERE steam_id = :steam_id"), {"steam_id": steam_id}
).one_or_none()
if result is None:
api_key, oid_hash = None, None
else:
api_key, oid_hash = result

return result
return api_key, oid_hash


def update_api_key(engine: Engine, steam_id: str, new_api_key: str) -> None:
def update_api_key(engine: Engine, steam_id: str, new_api_key: str, oid_hash: str) -> None:
"""Update an API key."""
with engine.connect() as conn:
updated_at = datetime.now().astimezone(timezone.utc).isoformat()
conn.execute(
sa.text("UPDATE api_keys SET api_key = :new_api_key, updated_at = :updated_at WHERE steam_id = :steam_id"),
{"steam_id": steam_id, "updated_at": updated_at, "new_api_key": new_api_key},
sa.text(
"UPDATE api_keys SET api_key = :new_api_key, updated_at = :updated_at, oid_hash = :oid_hash WHERE steam_id = :steam_id" # noqa
),
{"steam_id": steam_id, "updated_at": updated_at, "new_api_key": new_api_key, "oid_hash": oid_hash},
)
conn.commit()


def provision_api_key(engine: Engine, steam_id: str, api_key: str) -> None:
def provision_api_key(engine: Engine, steam_id: str, api_key: str, oid_hash: str) -> None:
"""Provision an API key."""
with engine.connect() as conn:
created_at = datetime.now().astimezone(timezone.utc).isoformat()
updated_at = created_at
conn.execute(
sa.text(
"""INSERT INTO api_keys (
steam_id, api_key, created_at, updated_at
steam_id, api_key, created_at, updated_at, oid_hash
) VALUES (
:steam_id, :api_key, :created_at, :updated_at);"""
:steam_id, :api_key, :created_at, :updated_at, :oid_hash);"""
),
{"steam_id": steam_id, "api_key": api_key, "created_at": created_at, "updated_at": updated_at},
{
"steam_id": steam_id,
"api_key": api_key,
"created_at": created_at,
"updated_at": updated_at,
"oid_hash": oid_hash,
},
)
conn.commit()

Expand Down
34 changes: 34 additions & 0 deletions migrations/versions/53d7f00c595e_oid_key_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""oid-key-hash
Revision ID: 53d7f00c595e
Revises: 9f376f19995e
Create Date: 2024-07-21 20:48:37.755305
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '53d7f00c595e'
down_revision: Union[str, None] = '9f376f19995e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.execute(
"""
ALTER TABLE api_keys ADD COLUMN oid_hash varchar;
"""
)


def downgrade() -> None:
op.execute(
"""
ALTER TABLE api_keys DROP COLUMN oid_hash;
"""
)

0 comments on commit 242003d

Please sign in to comment.