commit 7b82807deb4488125a563a01afcb74548bb7a9de
parent 38d194e8d9030f560369df57c23d56514bba16fb
Author: srfsh <dev@srf.sh>
Date: Wed, 6 Jul 2022 22:24:05 +0200
admin: separate functionality into more sensible modules
Diffstat:
8 files changed, 101 insertions(+), 142 deletions(-)
diff --git a/src/zenflows/admin.ex b/src/zenflows/admin.ex
@@ -0,0 +1,29 @@
+defmodule Zenflows.Admin do
+@moduledoc """
+Functionality to authenticate of admin-related calls.
+"""
+
+def auth(key) do
+ with {:ok, key_given} <- Base.decode16(key, case: :lower),
+ key_want = Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key],
+ true <- keys_match?(key_given, key_want) do
+ :ok
+ else _ ->
+ {:error, "you are not authorized"}
+ end
+end
+
+# TODO: replace with `:crypto.hash_equals/2` when we require OTP 25.
+defp keys_match?(left, right) do
+ byte_size(left) == byte_size(right) and keys_match?(left, right, 0)
+end
+
+defp keys_match?(<<x, left::binary>>, <<y, right::binary>>, acc) do
+ xorred = Bitwise.bxor(x, y)
+ keys_match?(left, right, Bitwise.bor(acc, xorred))
+end
+
+defp keys_match?(<<>>, <<>>, acc) do
+ acc === 0
+end
+end
diff --git a/src/zenflows/admin/resolv.ex b/src/zenflows/admin/resolv.ex
@@ -1,36 +0,0 @@
-defmodule Zenflows.Admin.Resolv do
-@moduledoc "Resolvers of Admin-related queries."
-
-alias Zenflows.VF.Person
-
-def create_user(params, _) do
- with :ok <- auth_admin(params),
- {:ok, per} <- Person.Domain.create(params) do
- {:ok, per}
- end
-end
-
-defp auth_admin(%{admin_key: key}) do
- with {:ok, key_given} <- Base.decode16(key, case: :lower),
- key_want = Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key],
- true <- keys_match?(key_given, key_want) do
- :ok
- else _ ->
- {:error, "you are not authorized"}
- end
-end
-
-# TODO: replace with `:crypto.hash_equals/2` when we require OTP 25.
-defp keys_match?(left, right) do
- byte_size(left) == byte_size(right) and keys_match?(left, right, 0)
-end
-
-defp keys_match?(<<x, left::binary>>, <<y, right::binary>>, acc) do
- xorred = Bitwise.bxor(x, y)
- keys_match?(left, right, Bitwise.bor(acc, xorred))
-end
-
-defp keys_match?(<<>>, <<>>, acc) do
- acc === 0
-end
-end
diff --git a/src/zenflows/admin/type.ex b/src/zenflows/admin/type.ex
@@ -1,44 +0,0 @@
-defmodule Zenflows.Admin.Type do
-@moduledoc """
-Basic authentication implementation to create Person Agents.
-"""
-
-use Absinthe.Schema.Notation
-
-alias Zenflows.Admin.Resolv
-
-@admin_key "The configuration-defined key to authenticate admin calls."
-
-object :mutation_admin do
- @desc "Create a Person Agent, a user."
- field :create_user, non_null(:person) do
- @desc @admin_key
- arg :admin_key, non_null(:string)
-
- @desc "A valid email address of the user. Must be unique."
- arg :email, non_null(:string)
-
- @desc "The username of the user. Must be unique"
- arg :user, non_null(:string)
-
- @desc "The full name/just a label of the user. Isn't unique."
- arg :name, non_null(:string)
-
- @desc "A JSON object encoded using a URL-safe, Base64 encoding."
- arg :pubkeys_encoded, non_null(:string), name: "pubkeys"
-
- resolve &Resolv.create_user/2
- end
-
- @desc "Import repositories from a softwarepassport instance."
- field :import_repos, :string do
- @desc @admin_key
- arg :admin_key, non_null(:string)
-
- @desc "The URL where all the repository information is listed."
- arg :url, non_null(:string)
-
- resolve &Resolv.import_repos/2
- end
-end
-end
diff --git a/src/zenflows/gql/schema.ex b/src/zenflows/gql/schema.ex
@@ -7,7 +7,6 @@ alias Zenflows.VF
import_types Absinthe.Type.Custom
import_types Zenflows.GQL.Type
-import_types Zenflows.Admin.Type
import_types VF.TimeUnit.Type
import_types VF.Action.Type
@@ -120,8 +119,6 @@ mutation do
#import_fields :mutation_proposal
#import_fields :mutation_proposed_intent
#import_fields :mutation_proposed_to
-
- import_fields :mutation_admin
end
@impl true
diff --git a/src/zenflows/vf/person/resolv.ex b/src/zenflows/vf/person/resolv.ex
@@ -1,14 +1,16 @@
defmodule Zenflows.VF.Person.Resolv do
@moduledoc "Resolvers of Persons."
+alias Zenflows.Admin
alias Zenflows.VF.{Agent, Person, Person.Domain}
def person(%{id: id}, _info) do
{:ok, Domain.by_id(id)}
end
-def create_person(%{person: params}, _info) do
- with {:ok, per} <- Domain.create(params) do
+def create_person(%{admin_key: key, person: params}, _info) do
+ with :ok <- Admin.auth(key),
+ {:ok, per} <- Domain.create(params) do
{:ok, %{agent: per}}
end
end
@@ -19,8 +21,9 @@ def update_person(%{person: %{id: id} = params}, _info) do
end
end
-def delete_person(%{id: id}, _info) do
- with {:ok, _} <- Domain.delete(id) do
+def delete_person(%{admin_key: key, id: id}, _info) do
+ with :ok <- Admin.auth(key),
+ {:ok, _} <- Domain.delete(id) do
{:ok, true}
end
end
diff --git a/src/zenflows/vf/person/type.ex b/src/zenflows/vf/person/type.ex
@@ -20,7 +20,7 @@ who have no physical location.
@user "Username of the agent. Implies uniqueness."
@email "Email address of the agent. Implies uniqueness."
@pubkeys """
-A URL-safe, Base64-encoded string of a JSON object.
+A URL-safe, lowercase-Base64-encoded string of a JSON object.
"""
@desc "A natural person."
@@ -49,7 +49,7 @@ object :person do
field :email, non_null(:string)
@desc @pubkeys
- field :pubkeys, non_null(:string), resolve: &Resolv.pubkeys/3
+ field :pubkeys, :string, resolve: &Resolv.pubkeys/3
end
object :person_response do
@@ -79,7 +79,7 @@ input_object :person_create_params do
field :email, non_null(:string)
@desc @pubkeys
- field :pubkeys_encoded, non_null(:string), name: "pubkeys"
+ field :pubkeys_encoded, :string, name: "pubkeys"
end
input_object :person_update_params do
@@ -116,6 +116,10 @@ object :mutation_person do
@desc "Registers a new (human) person with the collaboration space."
field :create_person, non_null(:person_response) do
arg :person, non_null(:person_create_params)
+
+ @desc "The configuration-defined key to authenticate admin calls."
+ arg :admin_key, non_null(:string)
+
resolve &Resolv.create_person/2
end
@@ -131,6 +135,10 @@ object :mutation_person do
"""
field :delete_person, non_null(:boolean) do
arg :id, non_null(:id)
+
+ @desc "The configuration-defined key to authenticate admin calls."
+ arg :admin_key, non_null(:string)
+
resolve &Resolv.delete_person/2
end
end
diff --git a/test/admin/type.test.exs b/test/admin/type.test.exs
@@ -1,41 +0,0 @@
-defmodule ZenflowsTest.Admin.Type do
-use ZenflowsTest.Help.AbsinCase, async: true
-
-setup do
- %{
- params: %{
- admin_key: Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key] |> Base.encode16(case: :lower),
- name: Factory.str("name"),
- email: "#{Factory.str("name")}@example.com",
- user: Factory.str("user"),
- pubkeys_encoded: Base.url_encode64(Jason.encode!(%{foobar: 1, barfoo: 2})),
- },
- }
-end
-
-test "createUser()", %{params: params} do
- assert %{data: %{"createUser" => data}} =
- mutation!("""
- createUser(
- adminKey: "#{params.admin_key}"
- name: "#{params.name}"
- email: "#{params.email}"
- user: "#{params.user}"
- pubkeys: "#{params.pubkeys_encoded}"
- ) {
- id
- name
- user
- email
- pubkeys
- }
- """)
-
- assert {:ok, _} = Zenflows.DB.ID.cast(data["id"])
- assert data["name"] == params.name
- assert data["email"] == params.email
- assert data["name"] == params.name
- assert data["user"] == params.user
- assert data["pubkeys"] == params.pubkeys_encoded
-end
-end
diff --git a/test/vf/person/type.test.exs b/test/vf/person/type.test.exs
@@ -3,6 +3,7 @@ use ZenflowsTest.Help.AbsinCase, async: true
setup do
%{
+ admin_key: Application.fetch_env!(:zenflows, Zenflows.Admin)[:admin_key] |> Base.encode16(case: :lower),
params: %{
name: Factory.uniq("name"),
# image
@@ -41,17 +42,46 @@ describe "Query" do
end
describe "Mutation" do
- test "createPerson()", %{params: params} do
+ test "createPerson() doesn't create a person without the admin key", %{params: params} do
+ assert %{data: nil, errors: [%{message: "you are not authorized", path: ["createPerson"]}]} =
+ mutation!("""
+ createPerson(
+ adminKey: "fake!!!"
+ person: {
+ name: "#{params.name}"
+ note: "#{params.note}"
+ primaryLocation: "#{params.primary_location_id}"
+ user: "#{params.user}"
+ email: "#{params.email}"
+ pubkeys: "#{params.pubkeys_encoded}"
+ }
+ ) {
+ agent {
+ id
+ name
+ note
+ primaryLocation { id }
+ user
+ email
+ }
+ }
+ """)
+ end
+
+ test "createPerson() creates a person with the admin key", %{params: params, admin_key: key} do
assert %{data: %{"createPerson" => %{"agent" => data}}} =
mutation!("""
- createPerson(person: {
- name: "#{params.name}"
- note: "#{params.note}"
- primaryLocation: "#{params.primary_location_id}"
- user: "#{params.user}"
- email: "#{params.email}"
- pubkeys: "#{params.pubkeys_encoded}"
- }) {
+ createPerson(
+ adminKey: "#{key}"
+ person: {
+ name: "#{params.name}"
+ note: "#{params.note}"
+ primaryLocation: "#{params.primary_location_id}"
+ user: "#{params.user}"
+ email: "#{params.email}"
+ pubkeys: "#{params.pubkeys_encoded}"
+ }
+ ) {
agent {
id
name
@@ -100,10 +130,23 @@ describe "Mutation" do
assert data["email"] == per.email
end
- test "deletePerson()", %{per: per} do
+ test "deletePerson() doesn't delete the person without the admin key", %{per: per} do
+ assert %{data: nil, errors: [%{message: "you are not authorized", path: ["deletePerson"]}]} =
+ mutation!("""
+ deletePerson(
+ adminKey: "fake!!!"
+ id: "#{per.id}"
+ )
+ """)
+ end
+
+ test "deletePerson() deletes the person with the admin key", %{per: per, admin_key: key} do
assert %{data: %{"deletePerson" => true}} =
mutation!("""
- deletePerson(id: "#{per.id}")
+ deletePerson(
+ adminKey: "#{key}"
+ id: "#{per.id}"
+ )
""")
end
end