Support TLD wildcards in MRF matches
[akkoma] / lib / pleroma / password.ex
index e96249650b9177e366fe781393ae78a3f4a12b3c..92d78552bc7c6ed3ede71014c3f1f663ba2cb476 100644 (file)
@@ -1,55 +1,55 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
 defmodule Pleroma.Password do
   @moduledoc """
-  This module implements Pleroma.Password passwords in terms of Plug.Crypto.
+  This module handles password hashing and verification.
+  It will delegate to the appropriate module based on the password hash.
+  It also handles upgrading of password hashes.
   """
 
-  alias Plug.Crypto.KeyGenerator
-
-  def decode64(str) do
-    str
-    |> String.replace(".", "+")
-    |> Base.decode64!(padding: false)
-  end
-
-  def encode64(bin) do
-    bin
-    |> Base.encode64(padding: false)
-    |> String.replace("+", ".")
-  end
+  alias Pleroma.User
+  alias Pleroma.Password.Pbkdf2
+  require Logger
 
-  def verify_pass(password, hash) do
-    ["pbkdf2-" <> digest, iterations, salt, hash] = String.split(hash, "$", trim: true)
+  @hashing_module Argon2
 
-    salt = decode64(salt)
+  @spec hash_pwd_salt(String.t()) :: String.t()
+  defdelegate hash_pwd_salt(pass), to: @hashing_module
 
-    iterations = String.to_integer(iterations)
+  @spec checkpw(String.t(), String.t()) :: boolean()
+  def checkpw(password, "$2" <> _ = password_hash) do
+    # Handle bcrypt passwords for Mastodon migration
+    Bcrypt.verify_pass(password, password_hash)
+  end
 
-    digest = String.to_atom(digest)
+  def checkpw(password, "$pbkdf2" <> _ = password_hash) do
+    Pbkdf2.verify_pass(password, password_hash)
+  end
 
-    binary_hash =
-      KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64)
+  def checkpw(password, "$argon2" <> _ = password_hash) do
+    Argon2.verify_pass(password, password_hash)
+  end
 
-    encode64(binary_hash) == hash
+  def checkpw(_password, _password_hash) do
+    Logger.error("Password hash not recognized")
+    false
   end
 
-  def hash_pwd_salt(password, opts \\ []) do
-    salt =
-      Keyword.get_lazy(opts, :salt, fn ->
-        :crypto.strong_rand_bytes(16)
-      end)
+  @spec maybe_update_password(User.t(), String.t()) ::
+          {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+  def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
+    do_update_password(user, password)
+  end
 
-    digest = Keyword.get(opts, :digest, :sha512)
+  def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
+    do_update_password(user, password)
+  end
 
-    iterations =
-      Keyword.get(opts, :iterations, Pleroma.Config.get([:password, :iterations], 160_000))
+  def maybe_update_password(%User{password_hash: "$pbkdf2" <> _} = user, password) do
+    do_update_password(user, password)
+  end
 
-    binary_hash =
-      KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64)
+  def maybe_update_password(user, _), do: {:ok, user}
 
-    "$pbkdf2-#{digest}$#{iterations}$#{encode64(salt)}$#{encode64(binary_hash)}"
+  defp do_update_password(user, password) do
+    User.reset_password(user, %{password: password, password_confirmation: password})
   end
 end