feat: merge cartsnitch/api into api/ subdirectory
Consolidate API gateway service into monorepo. Squashed from https://github.com/cartsnitch/api main (89bacb1). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""Encrypt existing plaintext session_data with Fernet.
|
||||
|
||||
Revision ID: 001_encrypt_session_data
|
||||
Revises:
|
||||
Create Date: 2026-03-19
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import sqlalchemy as sa
|
||||
from cryptography.fernet import Fernet
|
||||
from sqlalchemy import text
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "001_encrypt_session_data"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _get_fernet() -> Fernet:
|
||||
key = os.environ.get("CARTSNITCH_FERNET_KEY")
|
||||
if not key:
|
||||
raise RuntimeError("CARTSNITCH_FERNET_KEY must be set to run this migration")
|
||||
return Fernet(key.encode())
|
||||
|
||||
|
||||
def _is_fernet_token(value: str) -> bool:
|
||||
"""Check if a string looks like a Fernet token (base64 starting with gAAAAA)."""
|
||||
return value.startswith("gAAAAA")
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Change column type from JSON to TEXT to hold Fernet ciphertext
|
||||
op.alter_column(
|
||||
"user_store_accounts",
|
||||
"session_data",
|
||||
type_=sa.Text(),
|
||||
existing_type=sa.JSON(),
|
||||
existing_nullable=True,
|
||||
postgresql_using="session_data::text",
|
||||
)
|
||||
|
||||
conn = op.get_bind()
|
||||
rows = conn.execute(
|
||||
text("SELECT id, session_data FROM user_store_accounts WHERE session_data IS NOT NULL")
|
||||
).fetchall()
|
||||
|
||||
f = _get_fernet()
|
||||
for row_id, session_data in rows:
|
||||
raw = str(session_data)
|
||||
if _is_fernet_token(raw):
|
||||
continue
|
||||
plaintext = raw if isinstance(session_data, str) else json.dumps(session_data)
|
||||
encrypted = f.encrypt(plaintext.encode()).decode()
|
||||
conn.execute(
|
||||
text("UPDATE user_store_accounts SET session_data = :data WHERE id = :id"),
|
||||
{"data": encrypted, "id": row_id},
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
rows = conn.execute(
|
||||
text("SELECT id, session_data FROM user_store_accounts WHERE session_data IS NOT NULL")
|
||||
).fetchall()
|
||||
|
||||
f = _get_fernet()
|
||||
for row_id, session_data in rows:
|
||||
raw = str(session_data)
|
||||
if not _is_fernet_token(raw):
|
||||
continue
|
||||
decrypted = f.decrypt(raw.encode()).decode()
|
||||
conn.execute(
|
||||
text("UPDATE user_store_accounts SET session_data = :data WHERE id = :id"),
|
||||
{"data": decrypted, "id": row_id},
|
||||
)
|
||||
|
||||
# Revert column type from TEXT back to JSON
|
||||
op.alter_column(
|
||||
"user_store_accounts",
|
||||
"session_data",
|
||||
type_=sa.JSON(),
|
||||
existing_type=sa.Text(),
|
||||
existing_nullable=True,
|
||||
postgresql_using="session_data::json",
|
||||
)
|
||||
Reference in New Issue
Block a user