diff --git a/masterbase/app.py b/masterbase/app.py index f2964ba..086d02e 100644 --- a/masterbase/app.py +++ b/masterbase/app.py @@ -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 diff --git a/masterbase/lib.py b/masterbase/lib.py index e6e5014..9aec222 100644 --- a/masterbase/lib.py +++ b/masterbase/lib.py @@ -626,28 +626,34 @@ 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() @@ -655,11 +661,17 @@ def provision_api_key(engine: Engine, steam_id: str, api_key: str) -> None: 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() diff --git a/migrations/versions/53d7f00c595e_oid_key_hash.py b/migrations/versions/53d7f00c595e_oid_key_hash.py new file mode 100644 index 0000000..d598dcb --- /dev/null +++ b/migrations/versions/53d7f00c595e_oid_key_hash.py @@ -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; + """ + )