signing_key: ensure only one key per user exists
Fixes: AkkomaGang/akkoma issue 858
This commit is contained in:
		
							parent
							
								
									2a4587f201
								
							
						
					
					
						commit
						ea2de1f28a
					
				
					 2 changed files with 47 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -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)}")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
    # they’ll 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
 | 
			
		||||
		Loading…
	
		Reference in a new issue