Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions alembic_osm/versions/f3a7b9c1d2e4_heal_short_tdei_pass_crypt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""heal short TDEI-provisioned users.pass_crypt

TDEI users are provisioned by the backend via raw SQL with a placeholder
``pass_crypt``. Older rows used a 4-char value (``"none"``), which violates OSM's
``pass_crypt`` length 8..255 ``User`` validation. That makes the user invalid,
and Rails operations that re-validate the author via ``validates :author,
:associated => true`` (posting a changeset comment or a note comment) then fail
to save. Replace any too-short value with a random throwaway -- TDEI manages
auth, so ``pass_crypt`` is never used to authenticate.

Revision ID: f3a7b9c1d2e4
Revises: 5303f61d7b3a
Create Date: 2026-07-11 00:00:00.000000

"""

from typing import Sequence, Union

from alembic import op
from sqlalchemy import inspect, text

# revision identifiers, used by Alembic.
revision: str = "f3a7b9c1d2e4"
down_revision: Union[str, None] = "5303f61d7b3a"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
bind = op.get_bind()
# `users` is owned by the OSM Rails website, not this tree; guard so the
# migration is a no-op if it hasn't been created yet.
if not inspect(bind).has_table("users"):
return

# Idempotent: re-running only touches rows that are still too short.
op.execute(
text(
"UPDATE users SET pass_crypt = md5(random()::text) "
"WHERE auth_provider = 'TDEI' AND length(pass_crypt) < 8"
)
)


def downgrade() -> None:
# The original short values are unrecoverable (and were invalid anyway).
pass
8 changes: 7 additions & 1 deletion api/core/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ async def _ensure_osm_user(
"INSERT INTO users (auth_uid, email, display_name, auth_provider, "
"status, pass_crypt, data_public, email_valid, terms_seen, "
"creation_time, terms_agreed, tou_agreed) "
"VALUES (:auth_uid, :email, :name, 'TDEI', 'active', 'none', "
"VALUES (:auth_uid, :email, :name, 'TDEI', 'active', :pass_crypt, "
"true, true, true, (now() at time zone 'utc'), "
"(now() at time zone 'utc'), (now() at time zone 'utc')) "
"ON CONFLICT (auth_uid) DO NOTHING"
Expand All @@ -363,6 +363,12 @@ async def _ensure_osm_user(
"auth_uid": auth_uid,
"email": email or f"{auth_uid}@tdei.invalid",
"name": display_name,
# OSM's User model validates pass_crypt length 8..255. TDEI manages
# auth, so this is a throwaway that just satisfies that rule (a too-
# short value makes the user invalid and breaks Rails operations
# that re-validate it via `validates :author, :associated => true`,
# e.g. posting a changeset comment).
"pass_crypt": secrets.token_hex(16),
},
)
row = (
Expand Down
8 changes: 7 additions & 1 deletion api/src/tasking/projects/repository.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import secrets
from datetime import datetime
from typing import Any
from uuid import UUID
Expand Down Expand Up @@ -313,13 +314,18 @@ async def _provision_users_from_tdei(
await self.session.execute(
text(
"INSERT INTO users (auth_uid, email, display_name, auth_provider, status, pass_crypt, data_public, email_valid, terms_seen, creation_time, terms_agreed, tou_agreed) "
"VALUES (:uid, :email, :name, 'TDEI', 'active', 'none', true, true, true, (now() at time zone 'utc'), (now() at time zone 'utc'), (now() at time zone 'utc')) "
"VALUES (:uid, :email, :name, 'TDEI', 'active', :pass_crypt, true, true, true, (now() at time zone 'utc'), (now() at time zone 'utc'), (now() at time zone 'utc')) "
"ON CONFLICT (auth_uid) DO NOTHING"
),
params={
"uid": uid,
"email": member.email,
"name": member.display_name,
# OSM validates pass_crypt length 8..255; a too-short value
# makes the user invalid and breaks Rails ops that re-validate
# the author (changeset/note comments). TDEI manages auth, so
# this is a throwaway.
"pass_crypt": secrets.token_hex(16),
},
)
resolved.add(uid)
Expand Down
Loading