Compare commits

...

8 commits

30 changed files with 906 additions and 238 deletions

View file

@ -1,7 +1,9 @@
[
import_deps: [
:ash_authentication_phoenix,
:ash_admin,
:ash_postgres,
:ash_authentication,
:ash_phoenix,
:ash,
:reactor,

3
.gitignore vendored
View file

@ -36,3 +36,6 @@ npm-debug.log
/assets/node_modules/
.cursor
# Ignore the .env file with env variables
.env

10
.igniter.exs Normal file
View file

@ -0,0 +1,10 @@
# This is a configuration file for igniter.
# For option documentation, see https://hexdocs.pm/igniter/Igniter.Project.IgniterConfig.html
# To keep it up to date, use `mix igniter.setup`
[
module_location: :outside_matching_folder,
extensions: [{Igniter.Extensions.Phoenix, []}],
deps_location: :last_list_literal,
source_folders: ["lib", "test/support"],
dont_move_files: [~r"lib/mix"]
]

View file

@ -1,3 +1,5 @@
set dotenv-load := true
run: install-dependencies start-database migrate-database seed-database
mix phx.server

View file

@ -7,6 +7,7 @@ const path = require("path")
module.exports = {
content: [
"../deps/ash_authentication_phoenix/**/*.*ex",
"./js/**/*.js",
"../lib/mv_web.ex",
"../lib/mv_web/**/*.*ex"

View file

@ -49,7 +49,7 @@ config :spark,
config :mv,
ecto_repos: [Mv.Repo],
generators: [timestamp_type: :utc_datetime],
ash_domains: [Mv.Membership]
ash_domains: [Mv.Membership, Mv.Accounts]
# Configures the endpoint
config :mv, MvWeb.Endpoint,

View file

@ -84,3 +84,14 @@ config :phoenix_live_view,
# Disable swoosh api client as it is only required for production adapters.
config :swoosh, :api_client, false
config :mv, :secret_key_base, "ryn7D6ssmIHQFWIks2sFiTGATgwwAR1+3bN8p7fy6qVtB8qnxOuk1uyAwHz1Q8WB"
# Signing Secret for Authentication
config :mv, :token_signing_secret, "IwUwi65TrEeExwBXXFPGm2I7889NsL"
config :mv, :rauthy,
client_id: "mv",
base_url: "http://localhost:8080/auth/v1",
client_secret: System.get_env("OIDC_CLIENT_SECRET"),
redirect_uri: "http://localhost:4000/auth/user/rauthy/callback"

View file

@ -53,6 +53,8 @@ if config_env() == :prod do
config :mv, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY")
config :mv, :rauthy, redirect_uri: "http://localhost:4000/auth/user/rauthy/callback"
config :mv, MvWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"],
http: [

View file

@ -1,3 +1,10 @@
version: "3.5"
networks:
local:
rauthy-test:
driver: bridge
services:
db:
image: postgres:17.5-alpine
@ -16,9 +23,46 @@ services:
networks:
- local
networks:
local:
mailcrab:
image: marlonb/mailcrab:latest
ports:
- "1080:1080"
networks:
- rauthy-test
rauthy:
container_name: rauthy-test
image: ghcr.io/sebadob/rauthy:0.30.2
environment:
- LOCAL_TEST=true
- SMTP_URL=mailcrab
- SMTP_PORT=1025
- SMTP_DANGER_INSECURE=true
- LISTEN_SCHEME=http
- PUB_URL=localhost:8080
- BOOTSTRAP_ADMIN_PASSWORD_PLAIN=RauthyTest12345
#- HIQLITE=false
#- PG_HOST=db
#- PG_PORT=5432
#- PG_USER=postgres
#- PG_PASSWORD=postgres
#- PG_DB_NAME=mv_dev
ports:
- "8080:8080"
depends_on:
- mailcrab
- db
networks:
- rauthy-test
- local
volumes:
- type: volume
source: rauthy-data
target: /app/data
volumes:
postgres-data:
rauthy-data:

18
lib/accounts/accounts.ex Normal file
View file

@ -0,0 +1,18 @@
defmodule Mv.Accounts do
@moduledoc """
AshAuthentication specific domain to handle Authentication for users.
"""
use Ash.Domain,
extensions: [AshPhoenix]
resources do
resource Mv.Accounts.User do
define :create_user, action: :create
define :list_users, action: :read
define :update_user, action: :update
define :destroy_user, action: :destroy
end
resource Mv.Accounts.Token
end
end

14
lib/accounts/token.ex Normal file
View file

@ -0,0 +1,14 @@
defmodule Mv.Accounts.Token do
@moduledoc """
AshAuthentication specific ressource
"""
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.TokenResource],
domain: Mv.Accounts
postgres do
table "tokens"
repo Mv.Repo
end
end

117
lib/accounts/user.ex Normal file
View file

@ -0,0 +1,117 @@
defmodule Mv.Accounts.User do
@moduledoc """
The ressource for keeping user-specific data related to the login process. It is used by AshAuthentication to handle the Authentication strategies like SSO.
"""
use Ash.Resource,
domain: Mv.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication]
# authorizers: [Ash.Policy.Authorizer]
postgres do
table "users"
repo Mv.Repo
end
@doc """
AshAuthentication specific: Defines the strategies we want to use for authentication.
Currently password and SSO with Rauthy as OIDC provider
"""
authentication do
tokens do
enabled? true
token_resource Mv.Accounts.Token
require_token_presence_for_authentication? true
store_all_tokens? true
#signing_algorithm "EdDSA" -> https://git.local-it.org/local-it/mitgliederverwaltung/issues/87
signing_secret fn _, _ ->
{:ok, Application.get_env(:mv, :token_signing_secret)}
end
end
strategies do
oidc :rauthy do
client_id Mv.Secrets
base_url Mv.Secrets
redirect_uri Mv.Secrets
client_secret Mv.Secrets
auth_method :client_secret_jwt
code_verifier true
#id_token_signed_response_alg "EdDSA" #-> https://git.local-it.org/local-it/mitgliederverwaltung/issues/87
end
password :password do
identity_field :email
hash_provider AshAuthentication.BcryptProvider
confirmation_required? false
end
end
end
actions do
defaults [:read, :create, :destroy, :update]
read :get_by_subject do
description "Get a user by the subject claim in a JWT"
argument :subject, :string, allow_nil?: false
get? true
prepare AshAuthentication.Preparations.FilterBySubject
end
read :sign_in_with_rauthy do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
prepare AshAuthentication.Strategy.OAuth2.SignInPreparation
filter expr(email == get_path(^arg(:user_info), [:email]))
end
create :register_with_rauthy do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_email
change AshAuthentication.GenerateTokenChange
change fn changeset, _ctx ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
changeset
|> Ash.Changeset.change_attribute(:email, user_info["preferred_username"])
|> Ash.Changeset.change_attribute(:oidc_id, user_info["id"])
end
end
end
attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, sensitive?: true, allow_nil?: true
attribute :oidc_id, :string, allow_nil?: true
end
relationships do
belongs_to :member, Mv.Membership.Member
end
identities do
identity :unique_email, [:email]
identity :unique_oidc_id, [:oidc_id]
end
# You can customize this if you wish, but this is a safe default that
# only allows user data to be interacted with via AshAuthentication.
# policies do
# bypass AshAuthentication.Checks.AshAuthenticationInteraction do
# authorize_if(always())
# end
# policy always() do
# forbid_if(always())
# end
# end
end

View file

@ -0,0 +1,18 @@
defmodule Mv.Accounts.UserIdentity do
@moduledoc """
AshAuthentication specific ressource
"""
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.UserIdentity],
domain: Mv.Accounts
postgres do
table "user_identities"
repo Mv.Repo
end
user_identity do
user_resource Mv.Accounts.User
end
end

View file

@ -0,0 +1,32 @@
defmodule Mv.Accounts.User.Senders.SendNewUserConfirmationEmail do
@moduledoc """
Sends an email for a new user to confirm their email address.
"""
use AshAuthentication.Sender
use MvWeb, :verified_routes
import Swoosh.Email
alias Mv.Mailer
@impl true
def send(user, token, _) do
new()
# TODO: Replace with your email
|> from({"noreply", "noreply@example.com"})
|> to(to_string(user.email))
|> subject("Confirm your email address")
|> html_body(body(token: token))
|> Mailer.deliver!()
end
defp body(params) do
url = url(~p"/confirm_new_user/#{params[:token]}")
"""
<p>Click this link to confirm your email:</p>
<p><a href="#{url}">#{url}</a></p>
"""
end
end

View file

@ -0,0 +1,32 @@
defmodule Mv.Accounts.User.Senders.SendPasswordResetEmail do
@moduledoc """
Sends a password reset email
"""
use AshAuthentication.Sender
use MvWeb, :verified_routes
import Swoosh.Email
alias Mv.Mailer
@impl true
def send(user, token, _) do
new()
# TODO: Replace with your email
|> from({"noreply", "noreply@example.com"})
|> to(to_string(user.email))
|> subject("Reset your password")
|> html_body(body(token: token))
|> Mailer.deliver!()
end
defp body(params) do
url = url(~p"/password-reset/#{params[:token]}")
"""
<p>Click this link to reset your password:</p>
<p><a href="#{url}">#{url}</a></p>
"""
end
end

View file

@ -14,6 +14,7 @@ defmodule Mv.Application do
{Phoenix.PubSub, name: Mv.PubSub},
# Start the Finch HTTP client for sending emails
{Finch, name: Mv.Finch},
{AshAuthentication.Supervisor, otp_app: :my},
# Start a worker by calling: Mv.Worker.start_link(arg)
# {Mv.Worker, arg},
# Start to serve requests, typically the last entry

View file

@ -5,7 +5,7 @@ defmodule Mv.Repo do
@impl true
def installed_extensions do
# Add extensions here, and the migration generator will install them.
["ash-functions"]
["ash-functions", "citext"]
end
# Don't open unnecessary transactions

26
lib/mv/secrets.ex Normal file
View file

@ -0,0 +1,26 @@
defmodule Mv.Secrets do
use AshAuthentication.Secret
def secret_for([:authentication, :strategies, :rauthy, :client_id], Mv.Accounts.User, _opts, _meth) do
get_config(:client_id)
end
def secret_for([:authentication, :strategies, :rauthy, :redirect_uri], Mv.Accounts.User, _opts, _meth) do
get_config(:redirect_uri)
end
def secret_for([:authentication, :strategies, :rauthy, :client_secret], Mv.Accounts.User, _opts, _meth) do
get_config(:client_secret)
end
def secret_for([:authentication, :strategies, :rauthy, :base_url], Mv.Accounts.User, _opts, _meth) do
get_config(:base_url)
end
defp get_config(key) do
:mv
|> Application.fetch_env!(:rauthy)
|> Keyword.fetch!(key)
|> then(&{:ok, &1})
end
end

View file

@ -0,0 +1,20 @@
defmodule MvWeb.AuthOverrides do
use AshAuthentication.Phoenix.Overrides
# configure your UI overrides here
# First argument to `override` is the component name you are overriding.
# The body contains any number of configurations you wish to override
# Below are some examples
# For a complete reference, see https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html
# override AshAuthentication.Phoenix.Components.Banner do
# set :image_url, "https://media.giphy.com/media/g7GKcSzwQfugw/giphy.gif"
# set :text_class, "bg-red-500"
# end
# override AshAuthentication.Phoenix.Components.SignIn do
# set :show_banner, false
# end
end

View file

@ -0,0 +1,57 @@
require Logger
defmodule MvWeb.AuthController do
use MvWeb, :controller
use AshAuthentication.Phoenix.Controller
def success(conn, activity, user, _token) do
return_to = get_session(conn, :return_to) || ~p"/"
message =
case activity do
{:confirm_new_user, :confirm} -> "Your email address has now been confirmed"
{:password, :reset} -> "Your password has successfully been reset"
_ -> "You are now signed in"
end
conn
|> delete_session(:return_to)
|> store_in_session(user)
# If your resource has a different name, update the assign name here (i.e :current_admin)
|> assign(:current_user, user)
|> put_flash(:info, message)
|> redirect(to: return_to)
end
def failure(conn, activity, reason) do
Logger.error(%{conn: conn, reason: reason})
message =
case {activity, reason} do
{_,
%AshAuthentication.Errors.AuthenticationFailed{
caused_by: %Ash.Error.Forbidden{
errors: [%AshAuthentication.Errors.CannotConfirmUnconfirmedUser{}]
}
}} ->
"""
You have already signed in another way, but have not confirmed your account.
You can confirm your account using the link we sent to you, or by resetting your password.
"""
_ ->
"Incorrect email or password"
end
conn
|> put_flash(:error, message)
|> redirect(to: ~p"/sign-in")
end
def sign_out(conn, _params) do
return_to = get_session(conn, :return_to) || ~p"/"
conn
|> clear_session(:mv)
|> put_flash(:info, "You are now signed out")
|> redirect(to: return_to)
end
end

View file

@ -1,222 +1,55 @@
<.flash_group flash={@flash} />
<div class="left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]">
<svg
viewBox="0 0 1480 957"
fill="none"
aria-hidden="true"
class="absolute inset-0 h-full w-full"
preserveAspectRatio="xMinYMid slice"
>
<path fill="#EE7868" d="M0 0h1480v957H0z" />
<path
d="M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z"
fill="#FF9F92"
/>
<path
d="M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z"
fill="#FA8372"
/>
<path
d="M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z"
fill="#E96856"
fill-opacity=".6"
/>
<path
d="M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z"
fill="#C42652"
fill-opacity=".2"
/>
<path
d="M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z"
fill="#A41C42"
fill-opacity=".2"
/>
<path
d="M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z"
fill="#A41C42"
fill-opacity=".2"
/>
</svg>
</div>
<div class="px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32">
<div class="mx-auto max-w-xl lg:mx-0">
<svg viewBox="0 0 71 48" class="h-12" aria-hidden="true">
<path
d="m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z"
fill="#FD4F00"
/>
</svg>
<h1 class="text-brand mt-10 flex items-center text-sm font-semibold leading-6">
Phoenix Framework
<small class="bg-brand/5 text-[0.8125rem] ml-3 rounded-full px-2 font-medium leading-6">
v{Application.spec(:phoenix, :vsn)}
</small>
</h1>
<p class="text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-zinc-900 text-balance">
Peace of mind from prototype to production.
</p>
<p class="mt-4 text-base leading-7 text-zinc-600">
Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.
</p>
<div class="flex">
<div class="w-full sm:w-auto">
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3">
<a
href="https://hexdocs.pm/phoenix/overview.html"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
<path d="m12 4 10-2v18l-10 2V4Z" fill="#18181B" fill-opacity=".15" />
<path
d="M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Guides &amp; Docs
</span>
</a>
<a
href="https://github.com/phoenixframework/phoenix"
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" aria-hidden="true" class="h-6 w-6">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z"
fill="#18181B"
/>
</svg>
Source Code
</span>
</a>
<a
href={"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md"}
class="group relative rounded-2xl px-6 py-4 text-sm font-semibold leading-6 text-zinc-900 sm:py-6"
>
<span class="absolute inset-0 rounded-2xl bg-zinc-50 transition group-hover:bg-zinc-100 sm:group-hover:scale-105">
</span>
<span class="relative flex items-center gap-4 sm:flex-col">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" class="h-6 w-6">
<path
d="M12 1v6M12 17v6"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<circle
cx="12"
cy="12"
r="4"
fill="#18181B"
fill-opacity=".15"
stroke="#18181B"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
Changelog
</span>
</a>
</div>
<div class="mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-zinc-700 sm:grid-cols-2">
<div>
<a
href="https://twitter.com/elixirphoenix"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M5.403 14c5.283 0 8.172-4.617 8.172-8.62 0-.131 0-.262-.008-.391A6.033 6.033 0 0 0 15 3.419a5.503 5.503 0 0 1-1.65.477 3.018 3.018 0 0 0 1.263-1.676 5.579 5.579 0 0 1-1.824.736 2.832 2.832 0 0 0-1.63-.916 2.746 2.746 0 0 0-1.821.319A2.973 2.973 0 0 0 8.076 3.78a3.185 3.185 0 0 0-.182 1.938 7.826 7.826 0 0 1-3.279-.918 8.253 8.253 0 0 1-2.64-2.247 3.176 3.176 0 0 0-.315 2.208 3.037 3.037 0 0 0 1.203 1.836A2.739 2.739 0 0 1 1.56 6.22v.038c0 .7.23 1.377.65 1.919.42.54 1.004.912 1.654 1.05-.423.122-.866.14-1.297.052.184.602.541 1.129 1.022 1.506a2.78 2.78 0 0 0 1.662.598 5.656 5.656 0 0 1-2.007 1.074A5.475 5.475 0 0 1 1 12.64a7.827 7.827 0 0 0 4.403 1.358" />
</svg>
Follow on Twitter
</a>
</div>
<div>
<a
href="https://elixirforum.com"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z" />
</svg>
Discuss on the Elixir Forum
</a>
</div>
<div>
<a
href="https://web.libera.chat/#elixir"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.356 2.007a.75.75 0 0 1 .637.849l-1.5 10.5a.75.75 0 1 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.637ZM11.356 2.008a.75.75 0 0 1 .637.848l-1.5 10.5a.75.75 0 0 1-1.485-.212l1.5-10.5a.75.75 0 0 1 .848-.636Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 5.25a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75ZM13 10.75a.75.75 0 0 1-.75.75h-9.5a.75.75 0 0 1 0-1.5h9.5a.75.75 0 0 1 .75.75Z"
/>
</svg>
Chat on Libera IRC
</a>
</div>
<div>
<a
href="https://discord.gg/elixir"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 16 16"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z" />
</svg>
Join our Discord server
</a>
</div>
<div>
<a
href="https://fly.io/docs/elixir/getting-started/"
class="group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-zinc-50 hover:text-zinc-900"
>
<svg
viewBox="0 0 20 20"
aria-hidden="true"
class="h-4 w-4 fill-zinc-400 group-hover:fill-zinc-600"
>
<path d="M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z" />
</svg>
Deploy your application
</a>
<!--
THIS IS JUST THE ASHAUTHENTICATION EXAMPLE - WE NEED TO CHANGE IT LATER
-->
<nav class="bg-gray-800">
<div class="px-2 mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="relative flex items-center justify-between h-16">
<div class="flex items-center justify-center flex-1 sm:items-stretch sm:justify-start">
<div class="block ml-6">
<div class="flex space-x-4">
<div class="px-3 py-2 text-xl font-medium text-white ">
Mitgliederverwaltung
</div>
</div>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<%= if @current_user do %>
<span class="px-3 py-2 text-sm font-medium text-white rounded-md">
{@current_user.email}
</span>
<a
href="/sign-out"
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
>
Sign out
</a>
<% else %>
<a
href="/sign-in"
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
>
Sign In
</a>
<% end %>
</div>
</div>
</div>
</nav>
<div class="py-10">
<header>
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">
Demo
</h1>
</div>
</header>
<main>
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="px-4 py-8 sm:px-0">
<div class="border-4 border-gray-200 border-dashed rounded-lg h-96"></div>
</div>
</div>
</main>
</div>

View file

@ -0,0 +1,46 @@
defmodule MvWeb.LiveUserAuth do
@moduledoc """
Helpers for authenticating users in LiveViews.
"""
import Phoenix.Component
use MvWeb, :verified_routes
# This is used for nested liveviews to fetch the current user.
# To use, place the following at the top of that liveview:
# on_mount {MvWeb.LiveUserAuth, :current_user}
def on_mount(:current_user, _params, session, socket) do
return_to = session[:return_to]
socket =
socket
|> assign(:return_to, return_to)
|> AshAuthentication.Phoenix.LiveSession.assign_new_resources(session)
{:cont, session, socket}
end
def on_mount(:live_user_optional, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:cont, assign(socket, :current_user, nil)}
end
end
def on_mount(:live_user_required, _params, _session, socket) do
if socket.assigns[:current_user] do
{:cont, socket}
else
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/sign-in")}
end
end
def on_mount(:live_no_user, _params, _session, socket) do
if socket.assigns[:current_user] do
{:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
else
{:cont, assign(socket, :current_user, nil)}
end
end
end

View file

@ -1,6 +1,10 @@
defmodule MvWeb.Router do
use MvWeb, :router
use AshAuthentication.Phoenix.Router
import AshAuthentication.Plug.Helpers
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
@ -8,33 +12,86 @@ defmodule MvWeb.Router do
plug :put_root_layout, html: {MvWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :load_from_session
end
pipeline :api do
plug :accepts, ["json"]
plug :load_from_bearer
plug :set_actor, :user
end
scope "/", MvWeb do
pipe_through :browser
get "/", PageController, :home
live "/members", MemberLive.Index, :index
live "/members/new", MemberLive.Index, :new
live "/members/:id/edit", MemberLive.Index, :edit
live "/members/:id", MemberLive.Show, :show
live "/members/:id/show/edit", MemberLive.Show, :edit
ash_authentication_live_session :authenticated_routes do
# in each liveview, add one of the following at the top of the module:
#
# If an authenticated user must be present:
# on_mount {MvWeb.LiveUserAuth, :live_user_required}
#
# If an authenticated user *may* be present:
# on_mount {MvWeb.LiveUserAuth, :live_user_optional}
#
# If an authenticated user must *not* be present:
# on_mount {MvWeb.LiveUserAuth, :live_no_user}
end
end
live "/property_types", PropertyTypeLive.Index, :index
live "/property_types/new", PropertyTypeLive.Index, :new
live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
live "/property_types/:id", PropertyTypeLive.Show, :show
live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
scope "/", MvWeb do
pipe_through :browser
live "/properties", PropertyLive.Index, :index
live "/properties/new", PropertyLive.Index, :new
live "/properties/:id/edit", PropertyLive.Index, :edit
live "/properties/:id", PropertyLive.Show, :show
live "/properties/:id/show/edit", PropertyLive.Show, :edit
@doc """
AshAuthentication-specific: We define that all routes can only be accessed when the user is signed in.
"""
ash_authentication_live_session :authentication_required,
on_mount: {MvWeb.LiveUserAuth, :live_user_required} do
get "/", PageController, :home
live "/members", MemberLive.Index, :index
live "/members/new", MemberLive.Index, :new
live "/members/:id/edit", MemberLive.Index, :edit
live "/members/:id", MemberLive.Show, :show
live "/members/:id/show/edit", MemberLive.Show, :edit
live "/property_types", PropertyTypeLive.Index, :index
live "/property_types/new", PropertyTypeLive.Index, :new
live "/property_types/:id/edit", PropertyTypeLive.Index, :edit
live "/property_types/:id", PropertyTypeLive.Show, :show
live "/property_types/:id/show/edit", PropertyTypeLive.Show, :edit
live "/properties", PropertyLive.Index, :index
live "/properties/new", PropertyLive.Index, :new
live "/properties/:id/edit", PropertyLive.Index, :edit
live "/properties/:id", PropertyLive.Show, :show
live "/properties/:id/show/edit", PropertyLive.Show, :edit
end
# ASHAUTHENTICATION GENERATED AUTH ROUTES
auth_routes AuthController, Mv.Accounts.User, path: "/auth"
sign_out_route AuthController
# Remove these if you'd like to use your own authentication views
sign_in_route register_path: "/register",
reset_path: "/reset",
auth_routes_prefix: "/auth",
on_mount: [{MvWeb.LiveUserAuth, :live_no_user}],
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# Remove this if you do not want to use the reset password feature
reset_route auth_routes_prefix: "/auth",
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# Remove this if you do not use the confirmation strategy
confirm_route Mv.Accounts.User, :confirm_new_user,
auth_routes_prefix: "/auth",
overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# Remove this if you do not use the magic link strategy.
# magic_sign_in_route(Mv.Accounts.User, :magic_link,
# auth_routes_prefix: "/auth",
# overrides: [MvWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# )
end
# Other scopes may use custom stacks.

View file

@ -40,6 +40,9 @@ defmodule Mv.MixProject do
{:ash_postgres, "~> 2.0"},
{:ash_phoenix, "~> 2.0"},
{:ash, "~> 3.0"},
{:bcrypt_elixir, "~> 3.0"},
{:ash_authentication, "~> 4.9"},
{:ash_authentication_phoenix, "~> 2.10"},
{:igniter, "~> 0.6", only: [:dev, :test]},
{:phoenix, "~> 1.7.20"},
{:phoenix_ecto, "~> 4.5"},
@ -91,7 +94,8 @@ defmodule Mv.MixProject do
"tailwind mv --minify",
"esbuild mv --minify",
"phx.digest"
]
],
"phx.routes": ["phx.routes", "ash_authentication.phoenix.routes"]
]
end
end

View file

@ -1,19 +1,25 @@
%{
"ash": {:hex, :ash, "3.5.19", "defd1c6b94475352a7b69f430b792fb64e3a9f7ca030195737bb97dc0f1311b5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.4 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 0.11", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.2.65 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, ">= 0.2.6 and < 1.0.0-0", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ded976230b1ef823aeb25008cc62de6545bf3ad6208cf1f3badb598fa6c01375"},
"ash_admin": {:hex, :ash_admin, "0.13.9", "8a7c0f52be4aa490e4a59137bc40e3abafba9e1977f800bb2edae3f331ef1ebb", [:mix], [{:ash, ">= 3.4.63 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_phoenix, ">= 2.1.8 and < 3.0.0-0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "1373e1749d6b5b21c7ff7d7fc79ac932f6f8d1bd0d154a80758eab168948ea37"},
"ash_authentication": {:hex, :ash_authentication, "4.9.3", "2347b7982e3b00ae1165a4ef6875e05540204e933922e302bd3ac2be4c043e20", [:mix], [{:argon2_elixir, "~> 4.0", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:ash, ">= 3.4.29 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_postgres, "~> 2.0", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:assent, "~> 0.2.13", [hex: :assent, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:finch, "~> 0.19", [hex: :finch, repo: "hexpm", optional: false]}, {:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.5", [hex: :joken, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}], "hexpm", "1641988f6c67b7d7517caed9e6cb0f6bd906bbb994e2831022b6ad7cecf45ad0"},
"ash_authentication_phoenix": {:hex, :ash_authentication_phoenix, "2.10.1", "6facb8e14d7e93c3268b8cb5300d42d3802bd754d241f4215f2c5fc1d34c4c94", [:mix], [{:ash, "~> 3.0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, ">= 4.9.1 and < 5.0.0-0", [hex: :ash_authentication, repo: "hexpm", optional: false]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: false]}, {:bcrypt_elixir, "~> 3.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26", [hex: :gettext, repo: "hexpm", optional: true]}, {:igniter, ">= 0.5.25 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:slugify, "~> 1.3", [hex: :slugify, repo: "hexpm", optional: false]}], "hexpm", "efc27905b29476cacb67562658d5b38ca0656b3c81c4bcb40a117a2d8d686433"},
"ash_phoenix": {:hex, :ash_phoenix, "2.3.6", "c2bea1673af52f305b2fe0c04999bd1f0dc8e127d4757a3d7f42d0b9dea16a7a", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:inertia, "~> 2.3", [hex: :inertia, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.6 or ~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.20.3 or ~> 1.0-rc.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.29 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "6923dca70fe1d533864134999f4d9c5c59ef745a6b50982d42d60c18966474cd"},
"ash_postgres": {:hex, :ash_postgres, "2.6.6", "f60f806e3e969669329dfd33068bf602f3d7f214e0bbb36c241433f34cbff2e0", [:mix], [{:ash, ">= 3.5.13 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_sql, ">= 0.2.72 and < 1.0.0-0", [hex: :ash_sql, repo: "hexpm", optional: false]}, {:ecto, ">= 3.12.1 and < 4.0.0-0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "3133800432273f9e6effb6f8464fe81da22c5b577aa73291f63fd229f4bb43fb"},
"ash_sql": {:hex, :ash_sql, "0.2.80", "7717dca3794d7461b8302b107f039bce2c57773840177528cf94c7c264ed763b", [:mix], [{:ash, "~> 3.5", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.9", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "036f96b78bf612a1d1fe798b8795ab1e6ecef81e41ca473b1533b139dd0202ab"},
"assent": {:hex, :assent, "0.2.13", "11226365d2d8661d23e9a2cf94d3255e81054ff9d88ac877f28bfdf38fa4ef31", [:mix], [{:certifi, ">= 0.0.0", [hex: :certifi, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: true]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: true]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:ssl_verify_fun, ">= 0.0.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: true]}], "hexpm", "bf9f351b01dd6bceea1d1f157f05438f6765ce606e6eb8d29296003d29bf6eab"},
"bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"},
"circular_buffer": {:hex, :circular_buffer, "0.4.1", "477f370fd8cfe1787b0a1bade6208bbd274b34f1610e41f1180ba756a7679839", [:mix], [], "hexpm", "633ef2e059dde0d7b89bbab13b1da9d04c6685e80e68fbdf41282d4fae746b72"},
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
"ecto": {:hex, :ecto, "3.12.6", "8bf762dc5b87d85b7aca7ad5fe31ef8142a84cea473a3381eb933bd925751300", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c0cba01795463eebbcd9e4b5ef53c1ee8e68b9c482baef2a80de5a61e7a57fe"},
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
"ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"},
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
@ -28,6 +34,8 @@
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"live_debugger": {:hex, :live_debugger, "0.2.4", "2e0b02874ca562ba2d8cebb9e024c25c0ae9c1f4ee499135a70814e1dea6183e", [:mix], [{:igniter, ">= 0.5.40 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20.4 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "bfd0db143be54ccf2872f15bfd2209fbec1083d0b06b81b4cedeecb2fa9ac208"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
@ -39,6 +47,7 @@
"phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.4", "dcf3483ab45bab4c15e3a47c34451392f64e433846b08469f5d16c2a4cd70052", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f5b8584c36ccc9b903948a696fc9b8b81102c79c7c0c751a9f00cdec55d5f2d7"},
"phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.17", "beeb16d83a7d3760f7ad463df94e83b087577665d2acc0bf2987cd7d9778068f", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a4ca05c1eb6922c4d07a508a75bfa12c45e5f4d8f77ae83283465f02c53741e1"},
@ -51,6 +60,7 @@
"reactor": {:hex, :reactor, "0.15.4", "ef0c56a901c132529a14ab59fed0ccb4fcecb24308fb189a94c908255d4fdafc", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, "~> 2.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "783bf62fd0c72ded033afabdb8b6190b7048769771a2a97256e6f0bf4fb0a891"},
"req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"},
"rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"},
"slugify": {:hex, :slugify, "1.3.1", "0d3b8b7e5c1eeaa960e44dce94382bee34a39b3ea239293e457a9c5b47cc6fd3", [:mix], [], "hexpm", "cb090bbeb056b312da3125e681d98933a360a70d327820e4b7f91645c4d8be76"},
"sobelow": {:hex, :sobelow, "0.14.0", "dd82aae8f72503f924fe9dd97ffe4ca694d2f17ec463dcfd365987c9752af6ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7ecf91e298acfd9b24f5d761f19e8f6e6ac585b9387fb6301023f1f2cd5eed5f"},
"sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"},
"spark": {:hex, :spark, "2.2.65", "4c10d109c108417ce394158f330be09ef184878bde45de6462397fbda68cec29", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "d66d5070a77f4c69cb4f007e941ac17d5d751ce71190fcd6e6e5fb42ba86f101"},

View file

@ -0,0 +1,19 @@
defmodule Mv.Repo.Migrations.AddAccountsDomainExtensions do
@moduledoc """
Installs any extensions that are mentioned in the repo's `installed_extensions/0` callback
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
execute("CREATE EXTENSION IF NOT EXISTS \"citext\"")
end
def down do
# Uncomment this if you actually want to uninstall the extensions
# when this migration is rolled back:
# execute("DROP EXTENSION IF EXISTS \"citext\"")
end
end

View file

@ -0,0 +1,58 @@
defmodule Mv.Repo.Migrations.AddAccountsDomain do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
create table(:users, primary_key: false) do
add :id, :uuid, null: false, default: fragment("gen_random_uuid()"), primary_key: true
add :email, :citext, null: false
add :hashed_password, :text
add :oidc_id, :text
add :member_id,
references(:members,
column: :id,
name: "users_member_id_fkey",
type: :uuid,
prefix: "public"
)
end
create unique_index(:users, [:email], name: "users_unique_email_index")
create unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
create table(:tokens, primary_key: false) do
add :updated_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :created_at, :utc_datetime_usec,
null: false,
default: fragment("(now() AT TIME ZONE 'utc')")
add :extra_data, :map
add :purpose, :text, null: false
add :expires_at, :utc_datetime, null: false
add :subject, :text, null: false
add :jti, :text, null: false, primary_key: true
end
end
def down do
drop table(:tokens)
drop_if_exists unique_index(:users, [:oidc_id], name: "users_unique_oidc_id_index")
drop_if_exists unique_index(:users, [:email], name: "users_unique_email_index")
drop constraint(:users, "users_member_id_fkey")
drop table(:users)
end
end

View file

@ -1,6 +1,7 @@
{
"ash_functions_version": 5,
"installed": [
"ash-functions"
"ash-functions",
"citext"
]
}

View file

@ -0,0 +1,103 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "created_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "extra_data",
"type": "map"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "purpose",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "expires_at",
"type": "utc_datetime"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "subject",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "jti",
"type": "text"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "EA1475C339B5BE2728560EFB2AF911275B2F65C2CE66CD1C093FAB5D9183BB11",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "tokens"
}

View file

@ -0,0 +1,127 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"precision": null,
"primary_key?": true,
"references": null,
"scale": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "email",
"type": "citext"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "hashed_password",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": null,
"scale": null,
"size": null,
"source": "oidc_id",
"type": "text"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"precision": null,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "users_member_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "members"
},
"scale": null,
"size": null,
"source": "member_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [],
"custom_statements": [],
"has_create_action": true,
"hash": "03EBA1A8BCE47C4706E2D718E00364465E08C9A3999988D49FC1B89DEC5D717C",
"identities": [
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_email_index",
"keys": [
{
"type": "atom",
"value": "email"
}
],
"name": "unique_email",
"nils_distinct?": true,
"where": null
},
{
"all_tenants?": false,
"base_filter": null,
"index_name": "users_unique_oidc_id_index",
"keys": [
{
"type": "atom",
"value": "oidc_id"
}
],
"name": "unique_oidc_id",
"nils_distinct?": true,
"where": null
}
],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.Mv.Repo",
"schema": null,
"table": "users"
}