signing_key: ensure only one key per user exists

Fixes: AkkomaGang/akkoma issue 858
This commit is contained in:
Oneric 2025-01-11 21:25:00 +01:00
parent 2a4587f201
commit ea2de1f28a
2 changed files with 47 additions and 2 deletions

View file

@ -201,13 +201,22 @@ def fetch_remote_key(key_id) do
{:ok, user} <- User.get_or_fetch_by_ap_id(ap_id) do
Logger.debug("Fetched remote key: #{ap_id}")
# store the key
key = %__MODULE__{
key = %{
user_id: user.id,
public_key: public_key_pem,
key_id: key_id
}
Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id)
key_cs =
cast(%__MODULE__{}, key, [:user_id, :public_key, :key_id])
|> unique_constraint(:user_id)
Repo.insert(key_cs,
# while this should never run for local users anyway, etc make sure we really never loose privkey info!
on_conflict: {:replace_all_except, [:inserted_at, :private_key]},
# if the key owner overlaps with a distinct existing key entry, this intetionally still errros
conflict_target: :key_id
)
else
e ->
Logger.debug("Failed to fetch remote key: #{inspect(e)}")

View file

@ -0,0 +1,36 @@
defmodule Pleroma.Repo.Migrations.SigningKeyUniqueUserId do
use Ecto.Migration
import Ecto.Query
def up() do
# If dupes exists for any local user we do NOT want to delete the genuine privkey alongside the fake.
# Instead just filter out anything pertaining to local users, if dupes exists manual intervention
# is required anyway and index creation will just fail later (check against legacy field in users table)
dupes =
Pleroma.User.SigningKey
|> join(:inner, [s], u in Pleroma.User, on: s.user_id == u.id)
|> group_by([s], s.user_id)
|> having([], count() > 1)
|> having([_s, u], not fragment("bool_or(?)", u.local))
|> select([s], s.user_id)
# Delete existing remote duplicates
# theyll be reinserted on the next user update
# or proactively fetched when receiving a signature from it
Pleroma.User.SigningKey
|> where([s], s.user_id in subquery(dupes))
|> Pleroma.Repo.delete_all()
drop_if_exists(index(:signing_keys, [:user_id]))
create_if_not_exists(
index(:signing_keys, [:user_id], name: :signing_keys_user_id_index, unique: true)
)
end
def down() do
drop_if_exists(index(:signing_keys, [:user_id]))
create_if_not_exists(index(:signing_keys, [:user_id], name: :signing_keys_user_id_index))
end
end