Skip to content

Instantly share code, notes, and snippets.

@starbelly
Created November 16, 2019 22:48
Show Gist options
  • Save starbelly/fd5ed1ffb977e9535939b73d7ed1e777 to your computer and use it in GitHub Desktop.
Save starbelly/fd5ed1ffb977e9535939b73d7ed1e777 to your computer and use it in GitHub Desktop.
recovery_wip
From ea1390c4b78175077b52e268842bcbae9af4fea5 Mon Sep 17 00:00:00 2001
From: Bryan Paxton <starbelly@pobox.com>
Date: Mon, 11 Nov 2019 14:22:02 -0600
Subject: [PATCH] wip
---
lib/hexpm/accounts/recovery.ex | 38 ++++++--------
lib/hexpm/accounts/recovery_code.ex | 6 +--
lib/hexpm/accounts/user.ex | 1 -
...91015010144_create_user_recovery_codes.exs | 8 +--
test/hexpm/accounts/recovery_code_test.exs | 49 ++++++++++++++++---
test/hexpm/accounts/recovery_test.exs | 3 +-
6 files changed, 65 insertions(+), 40 deletions(-)
diff --git a/lib/hexpm/accounts/recovery.ex b/lib/hexpm/accounts/recovery.ex
index 6f220450..1d8dc837 100644
--- a/lib/hexpm/accounts/recovery.ex
+++ b/lib/hexpm/accounts/recovery.ex
@@ -2,6 +2,10 @@ defmodule Hexpm.Accounts.Recovery do
@moduledoc """
Functions used for generating and verifying recovery codes.
+ Recovery codes are
+ Recovery codes are treated as passwords and thus encrypted using a one way hashing function.
+
+
### Examples:
iex(1)> codes = [code|_more_codes] = Hexpm.Accounts.Recovery.gen_recovery_codes
iex(2)> _hashes = [hash|_more_hashes] = Hexpm.Accounts.Recovery.hash_recovery_codes(codes)
@@ -20,33 +24,23 @@ defmodule Hexpm.Accounts.Recovery do
def gen_recovery_codes, do: Enum.map(1..5, fn _ -> gen_code() end)
- def hash_recovery_codes(codes), do: Enum.map(codes, &hash_code/1)
-
- # It is not clear whether this should take a binary or a list of binaries at this point.
- # It depends how the form in UI is implemented as well as plans for the CLI.
- def verify_code(<<code::binary-size(19)>>, hash) do
- code
- |> String.split("-")
- |> verify_code(hash)
- end
-
- def verify_code([_p1, _p2, _p3, _p4] = parts, hash) do
- parts
- |> Enum.join("-")
- |> Bcrypt.verify_pass(hash)
- end
+ # # It is not clear whether this should take a binary or a list of binaries at this point.
+ # # It depends how the form in UI is implemented as well as plans for the CLI.
+ # def verify_code(user, <<code::binary-size(19)>>) do
+ # code
+ # |> String.split("-")
+ # |> verify_code(hash)
+ # end
- def verify_code(_, _), do: false
- def hash_code(code), do: Bcrypt.hash_pwd_salt(code)
+ defp verify_code
defp gen_code do
:crypto.strong_rand_bytes(@rand_bytes)
- |> Base.hex_encode32()
- |> String.downcase()
- |> String.codepoints()
+ |> Base.hex_encode32(case: :lower)
+ |> String.to_charlist()
|> Enum.chunk_every(@part_size)
- |> Enum.reduce("", fn s, acc -> Enum.join(s) <> "-" <> acc end)
- |> String.trim_trailing("-")
+ |> Enum.intersperse("-")
+ |> List.to_string()
end
end
diff --git a/lib/hexpm/accounts/recovery_code.ex b/lib/hexpm/accounts/recovery_code.ex
index 3a3461ec..f0c0c747 100644
--- a/lib/hexpm/accounts/recovery_code.ex
+++ b/lib/hexpm/accounts/recovery_code.ex
@@ -1,12 +1,11 @@
defmodule Hexpm.Accounts.RecoveryCode do
use Hexpm.Schema
- schema "user_recovery_codes" do
+ @primary_key false
+ embedded_schema do
field :code_digest, :string
field :used_at, :utc_datetime_usec
timestamps()
-
- belongs_to :user, User
end
def changeset(recovery_code, params) do
@@ -14,5 +13,4 @@ defmodule Hexpm.Accounts.RecoveryCode do
|> validate_required([:code_digest, :used_at])
|> unique_constraint(:code_digest)
end
-
end
diff --git a/lib/hexpm/accounts/user.ex b/lib/hexpm/accounts/user.ex
index d4fa84f6..ac5658ed 100644
--- a/lib/hexpm/accounts/user.ex
+++ b/lib/hexpm/accounts/user.ex
@@ -24,7 +24,6 @@ defmodule Hexpm.Accounts.User do
has_many :keys, Key
has_many :audit_logs, AuditLog
has_many :password_resets, PasswordReset
- has_many :recovery_codes, Hexpm.Accounts.RecoveryCode
end
@username_regex ~r"^[a-z0-9_\-\.]+$"
diff --git a/priv/repo/migrations/20191015010144_create_user_recovery_codes.exs b/priv/repo/migrations/20191015010144_create_user_recovery_codes.exs
index b1da269d..c4e13166 100644
--- a/priv/repo/migrations/20191015010144_create_user_recovery_codes.exs
+++ b/priv/repo/migrations/20191015010144_create_user_recovery_codes.exs
@@ -3,12 +3,12 @@ defmodule Hexpm.RepoBase.Migrations.CreateUserRecoveryCodes do
def change do
create table("user_recovery_codes") do
- add :user_id, references(:users), null: false
- add :code_digest, :string, null: false
- add :used_at, :utc_datetime_usec
+ add(:user_id, references(:users), null: false)
+ add(:code_digest, :string, null: false)
+ add(:used_at, :utc_datetime_usec)
timestamps()
end
- create unique_index(:user_recovery_codes, [:code_digest])
+ create(unique_index(:user_recovery_codes, [:code_digest]))
end
end
diff --git a/test/hexpm/accounts/recovery_code_test.exs b/test/hexpm/accounts/recovery_code_test.exs
index 072003ca..a47122e4 100644
--- a/test/hexpm/accounts/recovery_code_test.exs
+++ b/test/hexpm/accounts/recovery_code_test.exs
@@ -3,19 +3,54 @@ defmodule Hexpm.Accounts.RecoveryCodeTest do
alias Hexpm.Accounts.RecoveryCode
+
+ defmodule TwoFactor do
+ use Ecto.Schema
+
+ schema "two_factors" do
+ embeds_many :recovery_codes, RecoveryCode, on_replace: :delete
+ end
+ end
+
setup do
+ Repo.query("create table two_factors (id serial primary key, recovery_codes jsonb, user_id integer references users(id));")
%{user: create_user("starbelly", "starbelly@mail.com", "hunter42")}
end
test "create recovery code and get", %{user: user} do
- recovery_code = %RecoveryCode{user_id: user.id, code_digest: "1234"} |> Hexpm.Repo.insert!()
- assert Hexpm.Repo.get(RecoveryCode, recovery_code.id).user_id == user.id
- end
+ two_factor =
+ %TwoFactor{}
+ |> Ecto.Changeset.change
+ |> Ecto.Changeset.put_embed(:recovery_codes, [%RecoveryCode{code_digest: "12345"}])
+ |> Hexpm.Repo.insert!()
- test "create unique key name", %{user: user} do
- assert %RecoveryCode{} = %RecoveryCode{user_id: user.id, code_digest: "1234"} |> Hexpm.Repo.insert!()
- assert %RecoveryCode{} = %RecoveryCode{user_id: user.id, code_digest: "12345"} |> Hexpm.Repo.insert!()
- assert_raise Ecto.ConstraintError, fn -> %RecoveryCode{user_id: user.id, code_digest: "1234"} |> Hexpm.Repo.insert!() end
+ assert Enum.map(two_factor.recovery_codes, fn(x) -> x.code_digest end) == ["12345"]
+ #assert Hexpm.Repo.get(RecoveryCode, recovery_code.id).user_id == user.id
end
+# test "create unique reocvery code", %{user: user} do
+# assert %RecoveryCode{} =
+# %RecoveryCode{code_digest: "1234"} |> Hexpm.Repo.insert!()
+
+# assert %RecoveryCode{} =
+# %RecoveryCode{code_digest: "12345"} |> Hexpm.Repo.insert!()
+
+# assert_raise Ecto.ConstraintError, fn ->
+# %RecoveryCode{code_digest: "1234"} |> Hexpm.Repo.insert!()
+# end
+# end
+
+# test "update recovery code", %{user: user} do
+# recovery_code = %RecoveryCode{code_digest: "1234"} |> Hexpm.Repo.insert!()
+# now = DateTime.utc_now()
+# changeset = RecoveryCode.changeset(recovery_code, %{used_at: now})
+# assert {:ok, %RecoveryCode{used_at: ^now}} = Hexpm.Repo.update(changeset)
+# end
+
+# test "delete recovery code", %{user: user} do
+# recovery_code = %RecoveryCode{code_digest: "1234"} |> Hexpm.Repo.insert!()
+# assert Hexpm.Repo.delete(recovery_code)
+# assert Hexpm.Repo.get(RecoveryCode, recovery_code.id) == nil
+# end
+
end
diff --git a/test/hexpm/accounts/recovery_test.exs b/test/hexpm/accounts/recovery_test.exs
index 82c60522..7a488657 100644
--- a/test/hexpm/accounts/recovery_test.exs
+++ b/test/hexpm/accounts/recovery_test.exs
@@ -1,5 +1,4 @@
defmodule Hexpm.Accounts.RecoveryTest do
use ExUnit.Case, async: true
- doctest Hexpm.Accounts.Recovery
+ doctest Hexpm.Accounts.Recovery
end
-
--
2.21.0 (Apple Git-122)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment