Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seeds Rework #174 #175

Merged
merged 10 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .env_sample
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
export [email protected]
export AUTH_API_KEY=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx/dwylauth.herokuapp.com
export EMAIL_APP_URL=https://dwylmail.herokuapp.com
export [email protected]
export AUTH_URL=dwylauth.herokuapp.com
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to add the AUTH_URL environment variable to avoid the circular dependency on auth_plug noted by @tadasajon in #170

export SECRET_KEY_BASE=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx
export ENCRYPTION_KEYS='nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0='

# We use these two Oauth2 Providers:
# https://github.com/dwyl/elixir-auth-github
export GITHUB_CLIENT_ID=CreateGitHubApp
export GITHUB_CLIENT_SECRET=SuperSecret

# https://github.com/dwyl/elixir-auth-google
export GOOGLE_CLIENT_ID=YourAppsClientId.apps.googleusercontent.com
export GOOGLE_CLIENT_SECRET=SuperSecret
export SECRET_KEY_BASE=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx
export ENCRYPTION_KEYS='nMdayQpR0aoasLaq1g94FLba+A+wB44JLko47sVQXMg=,L+ZVX8iheoqgqb22mUpATmMDsvVGtafoAeb0KN5uWf0='

# Optional for sending emails:
export EMAIL_APP_URL=https://dwylmail.herokuapp.com

# Export AUTH_API_KEY once you have run Auth.Init.main/0
export AUTH_API_KEY=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx/dwylauth.herokuapp.com
52 changes: 52 additions & 0 deletions lib/auth/init/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div align="center">

# Welcome! 👋

![why](https://user-images.githubusercontent.com/194400/150698837-4eab1188-0aae-4dfd-b9c1-56ce0d311d20.png)

</div>

The purpose of the **`Auth` Application**
is to have a complete separation of concerns between
our
[**App**](https://github.com/dwyl/app)
and any Authentication/Authorization code
in order to: <br />

**a)** ***Simplify*** the **code** in the _main_
[**App**](https://github.com/dwyl/app)
because there is no "User Management"
to think about.

**b)** ***Maximize privacy/security*** of any/all **personal data**
that people using our App entrust in us
by storing it in a totally separate
fully encrypted database.

**c)** Minimize the number of environment variables
in the main App so that _anyone_ can run it
from scratch in less than 2 minutes.


For better or worse,
minimizing the number of environment variables
in the _main_
[**App**](https://github.com/dwyl/app)
means they have to go _somewhere_ ...
that somewhere is right _here_!

## Required Environment Variables for `Auth` App

In order to initialize the **`Auth` Application**
+ `ADMIN_EMAIL` - the email address of the person who will
administer the **`Auth` App**.
+ `AUTH_URL` - the base URL where the application will be hosted,
e.g: `"auth.dwyl.com"` (exclude the protocol)
+ `SECRET_KEY_BASE` - the secret Phoenix uses to sign and encrypt important information.
see:
https://hexdocs.pm/phoenix/deployment.html#handling-of-your-application-secrets
+ `ENCRYPTION_KEYS` - a list of one or more encryption keys
used to encrypt data in the database.
see: `.env_sample` for example.


File renamed without changes.
123 changes: 123 additions & 0 deletions lib/auth/init/init.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
defmodule Auth.Init do
@moduledoc """
`Init` as its' name suggests initializes the Auth Application
by creating the necessary records in the various tables.

This is the sequence of steps that are followed to init the App:

1. Create the "Super Admin" person who owns the Auth App
based on the `ADMIN_EMAIL` environment/config variable.

> The person.id (1) for the Super Admin
will own the remaining records so it needs to be created first.

2. Create default records (Statuses & Roles)

3. Create the App and `AUTH_API_KEY` for the Auth App.
> Log the `AUTH_API_KEY` so that it can be exported.
"""

require Logger
import Ecto.Changeset
alias Auth.{Person, Role, Repo, Status}

def main do
Logger.info("Initialising the Auth Database ...")
# check required environment variables:
Envar.is_set_all?(~w/ADMIN_EMAIL AUTH_URL ENCRYPTION_KEYS SECRET_KEY_BASE/)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could create the list of environment variables as a module variable to make it easier to change later on if required. However I think it does the job at the moment

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SimonLab yeah, that would be a great future enhancement. 👍


admin = Auth.Init.create_admin()

Auth.Init.insert_statuses()
Auth.Init.create_default_roles()

api_key = Auth.Init.create_apikey_for_admin(admin)

case Mix.env() do
:test ->
# set the AUTH_API_KEY environment variable during test run:
Envar.set("AUTH_API_KEY", api_key)
# ignore the next lines because we can't test them:
# coveralls-ignore-start
_ ->
# Log the AUTH_API_KEY so it can be exported:
Logger.info("export AUTH_API_KEY=#{api_key}")
# coveralls-ignore-stop
end

# Update status of Admin to "verified"
Auth.Person.verify_person_by_id(1)

# grant superadmin role to app owner:
Auth.PeopleRoles.upsert(1, 1, 1, 1)

:ok
end

# Get AUTH_URL or fallback to localhost:
defp get_auth_url do
# see .env_sample for example
Envar.get("AUTH_URL") || "localhost:4000"
end

def create_admin do
email = Envar.get("ADMIN_EMAIL")
case Person.get_person_by_email(email) do
# Ignore if the Super Admin already exists:
# coveralls-ignore-start
nil ->
%Person{}
|> Person.changeset(%{email: email})
|> Repo.insert!()
# coveralls-ignore-stop

person ->
person
end
end

def create_apikey_for_admin(person) do
{:ok, app} =
%{
"name" => "default system app",
"desc" => "Created by lib/auth/init/init.ex during setup.",
"url" => "localhost:4000",
"person_id" => person.id,
"status" => 3
}
|> Auth.App.create_app()

# If AUTH_API_KEY environment variable is already set, use it:
update_attrs = %{
"client_id" => AuthPlug.Token.client_id(),
"client_secret" => AuthPlug.Token.client_secret()
}

{:ok, key} =
Auth.Apikey.get_apikey_by_app_id(app.id)
|> cast(update_attrs, [:client_id, :client_secret])
|> Repo.update()

key.client_id <> "/" <> key.client_secret <> "/" <> get_auth_url()
end

# scripts for creating default roles and permissions
def get_json(filepath) do
path = File.cwd!() <> filepath
{:ok, data} = File.read(path)
json = Jason.decode!(data)
json
end

def create_default_roles do
Enum.each(get_json("/lib/auth/init/default_roles.json"), fn role ->
Role.upsert_role(role)
end)
end

def insert_statuses do
Enum.each(get_json("/lib/auth/init/statuses.json"), fn status ->
Status.upsert_status(status)
end)
end
end
File renamed without changes.
23 changes: 23 additions & 0 deletions lib/auth/people_roles.ex
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,29 @@ defmodule Auth.PeopleRoles do
|> Repo.insert()
end

@doc """
upsert/4 grants a role to the given person
app_id for app the person is granted the role for (always scoped to app!)
grantee_id is the person.id of the person being granted the role
granter_id is the id of the person (admin) granting the role
role_id is the role.id (int, e.g: 4) of th role being granted.
"""
def upsert(app_id, grantee_id, granter_id, role_id) do
case get_roles_for_person_for_app(app_id, grantee_id) do
# if there are no roles for the person, insert it:
n when n in [nil, []] ->
[insert(app_id, grantee_id, granter_id, role_id)]
roles ->
# if the role exists in the list of roles, return the list
if Enum.find_value(roles, fn r -> r.id == role_id end) do
roles
else
[insert(app_id, grantee_id, granter_id, role_id)]
end
end
end


@doc """
revoke/3 grants a role to the given person
revoker_id is the id of the person (admin) granting the role
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/status.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Auth.Status do
alias Auth.Repo
# https://stackoverflow.com/a/47501059/1148249
alias __MODULE__
@admin_email System.get_env("ADMIN_EMAIL")
@admin_email Envar.get("ADMIN_EMAIL")

schema "status" do
field :text, :string
Expand Down
2 changes: 1 addition & 1 deletion lib/auth_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule AuthWeb.Router do

pipeline :auth do
plug(AuthPlug)
end
end

scope "/", AuthWeb do
pipe_through :browser
Expand Down
5 changes: 4 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ defmodule Auth.Mixfile do
# https://github.com/dwyl/elixir-auth-google
{:elixir_auth_google, "~> 1.6.1"},

# Check/get Environment Variables: https://github.com/dwyl/envar
{:envar, "~> 1.0.4"},

# https://github.com/dwyl/auth_plug
{:auth_plug, "~> 1.4.7"},
{:auth_plug, "~> 1.4.11 "},

# https://github.com/dwyl/rbac
{:rbac, "~> 0.5.3"},
Expand Down
3 changes: 2 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
%{
"argon2_elixir": {:hex, :argon2_elixir, "2.4.0", "2a22ea06e979f524c53b42b598fc6ba38cdcbc977a155e33e057732cfb1fb311", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4ea82e183cf8e7f66dab1f767fedcfe6a195e140357ef2b0423146b72e0a551d"},
"auth_plug": {:hex, :auth_plug, "1.4.7", "7e09f8d60488a5cd28a095423c2c7772454fc4c3135208bf287e6ff8ef2e371d", [:mix], [{:httpoison, "~> 1.8.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2.2", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.4.1", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.12.1", [hex: :plug, repo: "hexpm", optional: false]}, {:useful, "~> 0.4.0", [hex: :useful, repo: "hexpm", optional: false]}], "hexpm", "98faa2c2b8d5af42854c23d19c013cef13670c0dfbe2584c0761b105e50d33aa"},
"auth_plug": {:hex, :auth_plug, "1.4.11", "f6f35708e737f6d56aae20abd3d974e877d5fd7eedff4f68480354c239be4ac1", [:mix], [{:httpoison, "~> 1.8.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.4.1", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.12.1", [hex: :plug, repo: "hexpm", optional: false]}, {:useful, "~> 0.4.0", [hex: :useful, repo: "hexpm", optional: false]}], "hexpm", "3ddc43065d095c4111f185f9ee1fa594b6d2cef1a620f315acd7e9ae8786f389"},
"b58": {:hex, :b58, "1.0.2", "d758f42d89fbe84052f18b2f310bed2a8c8c5b38dd0af86970026974cc7daeda", [:mix], [], "hexpm", "148e299e2753eae222b13f399f461a0c4910fed608b8723fca3c33d1d4f82c17"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"castore": {:hex, :castore, "0.1.13", "ccf3ab251ffaebc4319f41d788ce59a6ab3f42b6c27e598ad838ffecee0b04f9", [:mix], [], "hexpm", "a14a7eecfec7e20385493dbb92b0d12c5d77ecfd6307de10102d58c94e8c49c0"},
Expand All @@ -20,6 +20,7 @@
"elixir_auth_github": {:hex, :elixir_auth_github, "1.6.0", "f100dd3917b2395219d533e5a834eb450abff3e0a53eab36632d53335e1beb92", [:mix], [{:httpoison, "~> 1.8.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "abc700b484bc5dfd4d6a2aa2acbbe79a7fd8ce112ca28d2c864ede169790ab1f"},
"elixir_auth_google": {:hex, :elixir_auth_google, "1.6.1", "37b4812bf129628e61fc3c5403346cd24cd267750c57790b06bcd453daabbdab", [:mix], [{:httpoison, "~> 1.8.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "bd59482ca0369bd4f27a725d12bc12a0f575e65c0130bc3acce17b2a29c5e626"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"envar": {:hex, :envar, "1.0.4", "3264a6b13d5f9bca9a16e82ed2311c0efd08f3371d3c6b42dc5abe9d01da59aa", [:mix], [], "hexpm", "d7856849080745b907e39b33b825448810bc2348e3f5982ee32f8007d9b3226e"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.4.0", "9f17db148aead4cf1e6e6a584214357287a93407b5fb51a031f122b61385d4c2", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "b61e4e6b92ffe45e4ee4755a22de6211a67c67987dc02afb35a425a0add1d447"},
"ex_doc": {:hex, :ex_doc, "0.25.5", "ac3c5425a80b4b7c4dfecdf51fa9c23a44877124dd8ca34ee45ff608b1c6deb9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "688cfa538cdc146bc4291607764a7f1fcfa4cce8009ecd62de03b27197528350"},
Expand Down
Loading