defmodule SqlMembershipProvider.Membership do @moduledoc """ Struct for representing a user's membership to an application and their authentication information. """ use TypedEctoSchema import Ecto.Query, only: [from: 2] @primary_key {:user_id, :binary_id, null: false} @field_source_mapper fn name -> name |> Atom.to_string() |> Macro.camelize() end typed_schema "aspnet_Membership" do field(:password, :string, null: false) field(:password_format, :integer, null: false) field(:password_salt, :string, null: false) field(:mobile_pin, :string) field(:email, :string) field(:lowered_email, :string) field(:password_question, :string) field(:password_answer, :string) field(:is_approved, :boolean, null: false) field(:is_locked_out, :boolean, null: false) field(:create_date, :utc_datetime, null: false) field(:last_login_date, :utc_datetime, null: false) field(:last_password_changed_date, :utc_datetime, null: false) field(:last_lockout_date, :utc_datetime, null: false) field(:failed_password_attempt_count, :integer, null: false) field(:failed_password_attempt_window_start, :utc_datetime, null: false) field(:failed_password_answer_attempt_count, :integer, null: false) field(:failed_password_answer_attempt_window_start, :utc_datetime, null: false) field(:comment, :string) belongs_to(:application, SqlMembershipProvider.Application, references: :application_id, type: :binary_id ) belongs_to(:user, SqlMembershipProvider.User, define_field: false, primary_key: true, references: :user_id ) end @doc """ Fetch a membership by user id. """ @spec find_by_user_id(String.t()) :: Ecto.Query.t() def find_by_user_id(user_id) when is_binary(user_id) do from( m in SqlMembershipProvider.Membership, where: m.user_id == ^user_id ) end @doc """ Check if a plaintext password matches a user's hashed password. """ @spec is_password_valid?( SqlMembershipProvider.Membership.t(), String.t() ) :: boolean() def is_password_valid?( %SqlMembershipProvider.Membership{ password: hashed_password, password_format: format, password_salt: salt }, password ) when is_binary(password) do hash_password(password, format, salt) == hashed_password end defp hash_password(password, 1, salt) do salt = Base.decode64!(salt) password = String.to_charlist(password) |> :unicode.characters_to_binary(:utf8, {:utf16, :little}) :crypto.hash(:sha, salt <> password) |> Base.encode64() end defp hash_password(_password, format, _salt), do: raise(SqlMembershipProvider.UnsupportedPasswordFormatError, format) end