diff --git a/elixir-ory-network/.formatter.exs b/elixir-ory-network/.formatter.exs
new file mode 100644
index 00000000..9ff46bdd
--- /dev/null
+++ b/elixir-ory-network/.formatter.exs
@@ -0,0 +1,6 @@
+[
+ import_deps: [:ecto, :ecto_sql, :phoenix, :plug],
+ subdirectories: ["priv/*/migrations"],
+ plugins: [Phoenix.LiveView.HTMLFormatter],
+ inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
+]
diff --git a/elixir-ory-network/.gitignore b/elixir-ory-network/.gitignore
new file mode 100644
index 00000000..ac35e98c
--- /dev/null
+++ b/elixir-ory-network/.gitignore
@@ -0,0 +1,40 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where 3rd-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Temporary files, for example, from tests.
+/tmp/
+
+# Ignore package tarball (built via "mix hex.build").
+example-*.tar
+
+# Ignore assets that are produced by build tools.
+/priv/static/assets/
+
+# Ignore digested assets cache.
+/priv/static/cache_manifest.json
+
+# In case you use Node.js/npm, you want to ignore these.
+npm-debug.log
+/assets/node_modules/
+
+
+assets/vendor/heroicons
+priv/database.db*
\ No newline at end of file
diff --git a/elixir-ory-network/README.md b/elixir-ory-network/README.md
new file mode 100644
index 00000000..6f8c3368
--- /dev/null
+++ b/elixir-ory-network/README.md
@@ -0,0 +1,71 @@
+# Elixir Ory Cloud Phoenix Example
+A basic Example of integrating [Ory](https://ory.sh) with [Phoenix](https://www.phoenixframework.org/). Highlighting a controller flow, using [plugs](https://hexdocs.pm/phoenix/plug.html) to render HEEx templates with session details, etc.
+
+## Overview
+
+A simple server-side rendered web application serving Custom UI for Ory Flows.
+
+## Develop
+
+### Prerequisites
+
+[Elixir](https://elixir-lang.org/install.html) >= 1.14
+[Erlang](https://www.erlang.org/downloads)
+[Ory CLI](https://www.ory.sh/docs/guides/cli/installation)
+
+### Configure your Ory Network/Kratos project
+Set up your Custom UI to the following values:
+| Custom UI | URL |
+| --------------- | --------------------------------------- |
+| Login UI | https://localhost:3000/auth/login |
+| Register UI | https://localhost:3000/auth/register |
+
+> NOTE: The other UIs are currently not implemented in this example.
+
+### Environmental Variables
+
+`$ORY_PROJECT` is used when running the Ory Proxy to prevent CORS issues.
+
+### Run locally
+
+First build the dependencies and setup Phoenix:
+
+```sh
+$ mix deps.get && mix deps.compile
+$ mix setup
+```
+
+Start the Ory proxy for your `$ORY_PROJECT` slug in another shell
+```sh
+$ ory proxy http://localhost:4000 --dev --project $ORY_PROJECT --port 3000 --cookie-domain localhost
+```
+
+And finally start serving the Phoenix server
+```sh
+$ mix phx.server
+```
+
+### Run tests
+
+TODO: Integrate proper tests. :)
+
+```sh
+$ mix test
+```
+
+## Deploy
+
+Most deployment mechanisms will work fine, such as [Fly.io](https://fly.io).
+
+## Contribute
+
+Feel free to
+[open a discussion](https://github.com/ory/examples/discussions/new) to provide
+feedback or talk about ideas, or
+[open an issue](https://github.com/ory/examples/issues/new) if you want to add
+your example to the repository or encounter a bug. You can contribute to Ory in
+many ways, see the
+[Ory Contributing Guidelines](https://www.ory.sh/docs/ecosystem/contributing)
+for more information.
+
+
diff --git a/elixir-ory-network/assets/css/app.css b/elixir-ory-network/assets/css/app.css
new file mode 100644
index 00000000..378c8f90
--- /dev/null
+++ b/elixir-ory-network/assets/css/app.css
@@ -0,0 +1,5 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
+
+/* This file is for your main application CSS */
diff --git a/elixir-ory-network/assets/js/app.js b/elixir-ory-network/assets/js/app.js
new file mode 100644
index 00000000..df0cdd9f
--- /dev/null
+++ b/elixir-ory-network/assets/js/app.js
@@ -0,0 +1,41 @@
+// If you want to use Phoenix channels, run `mix help phx.gen.channel`
+// to get started and then uncomment the line below.
+// import "./user_socket.js"
+
+// You can include dependencies in two ways.
+//
+// The simplest option is to put them in assets/vendor and
+// import them using relative paths:
+//
+// import "../vendor/some-package.js"
+//
+// Alternatively, you can `npm install some-package --prefix assets` and import
+// them using a path starting with the package name:
+//
+// import "some-package"
+//
+
+// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
+import "phoenix_html"
+// Establish Phoenix Socket and LiveView configuration.
+import {Socket} from "phoenix"
+import {LiveSocket} from "phoenix_live_view"
+import topbar from "../vendor/topbar"
+
+let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
+let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
+
+// Show progress bar on live navigation and form submits
+topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
+window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
+window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
+
+// connect if there are any LiveViews on the page
+liveSocket.connect()
+
+// expose liveSocket on window for web console debug logs and latency simulation:
+// >> liveSocket.enableDebug()
+// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
+// >> liveSocket.disableLatencySim()
+window.liveSocket = liveSocket
+
diff --git a/elixir-ory-network/assets/tailwind.config.js b/elixir-ory-network/assets/tailwind.config.js
new file mode 100644
index 00000000..856af57c
--- /dev/null
+++ b/elixir-ory-network/assets/tailwind.config.js
@@ -0,0 +1,68 @@
+// See the Tailwind configuration guide for advanced usage
+// https://tailwindcss.com/docs/configuration
+
+const plugin = require("tailwindcss/plugin")
+const fs = require("fs")
+const path = require("path")
+
+module.exports = {
+ content: [
+ "./js/**/*.js",
+ "../lib/*_web.ex",
+ "../lib/*_web/**/*.*ex"
+ ],
+ theme: {
+ extend: {
+ colors: {
+ brand: "#FD4F00",
+ }
+ },
+ },
+ plugins: [
+ require("@tailwindcss/forms"),
+ // Allows prefixing tailwind classes with LiveView classes to add rules
+ // only when LiveView classes are applied, for example:
+ //
+ //
+ //
+ plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
+ plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])),
+ plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])),
+ plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
+
+ // Embeds Heroicons (https://heroicons.com) into your app.css bundle
+ // See your `CoreComponents.icon/1` for more information.
+ //
+ plugin(function({matchComponents, theme}) {
+ let iconsDir = path.join(__dirname, "./vendor/heroicons/optimized")
+ let values = {}
+ let icons = [
+ ["", "/24/outline"],
+ ["-solid", "/24/solid"],
+ ["-mini", "/20/solid"]
+ ]
+ icons.forEach(([suffix, dir]) => {
+ fs.readdirSync(path.join(iconsDir, dir)).map(file => {
+ let name = path.basename(file, ".svg") + suffix
+ values[name] = {name, fullPath: path.join(iconsDir, dir, file)}
+ })
+ })
+ matchComponents({
+ "hero": ({name, fullPath}) => {
+ let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "")
+ return {
+ [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
+ "-webkit-mask": `var(--hero-${name})`,
+ "mask": `var(--hero-${name})`,
+ "mask-repeat": "no-repeat",
+ "background-color": "currentColor",
+ "vertical-align": "middle",
+ "display": "inline-block",
+ "width": theme("spacing.5"),
+ "height": theme("spacing.5")
+ }
+ }
+ }, {values})
+ })
+ ]
+}
diff --git a/elixir-ory-network/assets/vendor/topbar.js b/elixir-ory-network/assets/vendor/topbar.js
new file mode 100644
index 00000000..41957274
--- /dev/null
+++ b/elixir-ory-network/assets/vendor/topbar.js
@@ -0,0 +1,165 @@
+/**
+ * @license MIT
+ * topbar 2.0.0, 2023-02-04
+ * https://buunguyen.github.io/topbar
+ * Copyright (c) 2021 Buu Nguyen
+ */
+(function (window, document) {
+ "use strict";
+
+ // https://gist.github.com/paulirish/1579671
+ (function () {
+ var lastTime = 0;
+ var vendors = ["ms", "moz", "webkit", "o"];
+ for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame =
+ window[vendors[x] + "RequestAnimationFrame"];
+ window.cancelAnimationFrame =
+ window[vendors[x] + "CancelAnimationFrame"] ||
+ window[vendors[x] + "CancelRequestAnimationFrame"];
+ }
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = function (callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function () {
+ callback(currTime + timeToCall);
+ }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = function (id) {
+ clearTimeout(id);
+ };
+ })();
+
+ var canvas,
+ currentProgress,
+ showing,
+ progressTimerId = null,
+ fadeTimerId = null,
+ delayTimerId = null,
+ addEvent = function (elem, type, handler) {
+ if (elem.addEventListener) elem.addEventListener(type, handler, false);
+ else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
+ else elem["on" + type] = handler;
+ },
+ options = {
+ autoRun: true,
+ barThickness: 3,
+ barColors: {
+ 0: "rgba(26, 188, 156, .9)",
+ ".25": "rgba(52, 152, 219, .9)",
+ ".50": "rgba(241, 196, 15, .9)",
+ ".75": "rgba(230, 126, 34, .9)",
+ "1.0": "rgba(211, 84, 0, .9)",
+ },
+ shadowBlur: 10,
+ shadowColor: "rgba(0, 0, 0, .6)",
+ className: null,
+ },
+ repaint = function () {
+ canvas.width = window.innerWidth;
+ canvas.height = options.barThickness * 5; // need space for shadow
+
+ var ctx = canvas.getContext("2d");
+ ctx.shadowBlur = options.shadowBlur;
+ ctx.shadowColor = options.shadowColor;
+
+ var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
+ for (var stop in options.barColors)
+ lineGradient.addColorStop(stop, options.barColors[stop]);
+ ctx.lineWidth = options.barThickness;
+ ctx.beginPath();
+ ctx.moveTo(0, options.barThickness / 2);
+ ctx.lineTo(
+ Math.ceil(currentProgress * canvas.width),
+ options.barThickness / 2
+ );
+ ctx.strokeStyle = lineGradient;
+ ctx.stroke();
+ },
+ createCanvas = function () {
+ canvas = document.createElement("canvas");
+ var style = canvas.style;
+ style.position = "fixed";
+ style.top = style.left = style.right = style.margin = style.padding = 0;
+ style.zIndex = 100001;
+ style.display = "none";
+ if (options.className) canvas.classList.add(options.className);
+ document.body.appendChild(canvas);
+ addEvent(window, "resize", repaint);
+ },
+ topbar = {
+ config: function (opts) {
+ for (var key in opts)
+ if (options.hasOwnProperty(key)) options[key] = opts[key];
+ },
+ show: function (delay) {
+ if (showing) return;
+ if (delay) {
+ if (delayTimerId) return;
+ delayTimerId = setTimeout(() => topbar.show(), delay);
+ } else {
+ showing = true;
+ if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
+ if (!canvas) createCanvas();
+ canvas.style.opacity = 1;
+ canvas.style.display = "block";
+ topbar.progress(0);
+ if (options.autoRun) {
+ (function loop() {
+ progressTimerId = window.requestAnimationFrame(loop);
+ topbar.progress(
+ "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
+ );
+ })();
+ }
+ }
+ },
+ progress: function (to) {
+ if (typeof to === "undefined") return currentProgress;
+ if (typeof to === "string") {
+ to =
+ (to.indexOf("+") >= 0 || to.indexOf("-") >= 0
+ ? currentProgress
+ : 0) + parseFloat(to);
+ }
+ currentProgress = to > 1 ? 1 : to;
+ repaint();
+ return currentProgress;
+ },
+ hide: function () {
+ clearTimeout(delayTimerId);
+ delayTimerId = null;
+ if (!showing) return;
+ showing = false;
+ if (progressTimerId != null) {
+ window.cancelAnimationFrame(progressTimerId);
+ progressTimerId = null;
+ }
+ (function loop() {
+ if (topbar.progress("+.1") >= 1) {
+ canvas.style.opacity -= 0.05;
+ if (canvas.style.opacity <= 0.05) {
+ canvas.style.display = "none";
+ fadeTimerId = null;
+ return;
+ }
+ }
+ fadeTimerId = window.requestAnimationFrame(loop);
+ })();
+ },
+ };
+
+ if (typeof module === "object" && typeof module.exports === "object") {
+ module.exports = topbar;
+ } else if (typeof define === "function" && define.amd) {
+ define(function () {
+ return topbar;
+ });
+ } else {
+ this.topbar = topbar;
+ }
+}.call(this, window, document));
diff --git a/elixir-ory-network/config/config.exs b/elixir-ory-network/config/config.exs
new file mode 100644
index 00000000..34f950df
--- /dev/null
+++ b/elixir-ory-network/config/config.exs
@@ -0,0 +1,57 @@
+# This file is responsible for configuring your application
+# and its dependencies with the aid of the Config module.
+#
+# This configuration file is loaded before any dependency and
+# is restricted to this project.
+
+# General application configuration
+import Config
+
+config :example,
+ ecto_repos: [Example.Repo]
+
+config :example, Example.Repo, database: "priv/database.db"
+
+# Configures the endpoint
+config :example, ExampleWeb.Endpoint,
+ url: [host: "localhost"],
+ render_errors: [
+ formats: [html: ExampleWeb.ErrorHTML, json: ExampleWeb.ErrorJSON],
+ layout: false
+ ],
+ pubsub_server: Example.PubSub,
+ live_view: [signing_salt: "i5wDSk9E"]
+
+# Configure esbuild (the version is required)
+config :esbuild,
+ version: "0.17.11",
+ default: [
+ args:
+ ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
+ cd: Path.expand("../assets", __DIR__),
+ env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
+ ]
+
+# Configure tailwind (the version is required)
+config :tailwind,
+ version: "3.2.7",
+ default: [
+ args: ~w(
+ --config=tailwind.config.js
+ --input=css/app.css
+ --output=../priv/static/assets/app.css
+ ),
+ cd: Path.expand("../assets", __DIR__)
+ ]
+
+# Configures Elixir's Logger
+config :logger, :console,
+ format: "$time $metadata[$level] $message\n",
+ metadata: [:request_id]
+
+# Use Jason for JSON parsing in Phoenix
+config :phoenix, :json_library, Jason
+
+# Import environment specific config. This must remain at the bottom
+# of this file so it overrides the configuration defined above.
+import_config "#{config_env()}.exs"
diff --git a/elixir-ory-network/config/dev.exs b/elixir-ory-network/config/dev.exs
new file mode 100644
index 00000000..bca76ed2
--- /dev/null
+++ b/elixir-ory-network/config/dev.exs
@@ -0,0 +1,72 @@
+import Config
+
+# Configure your Ory Network URL
+config :ory_client,
+ # Via ory proxy
+ :base_url,
+ "http://localhost:3000/.ory"
+
+# For development, we disable any cache and enable
+# debugging and code reloading.
+#
+# The watchers configuration can be used to run external
+# watchers to your application. For example, we can use it
+# to bundle .js and .css sources.
+config :example, ExampleWeb.Endpoint,
+ # Binding to loopback ipv4 address prevents access from other machines.
+ # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
+ http: [ip: {127, 0, 0, 1}, port: 4000],
+ check_origin: false,
+ code_reloader: true,
+ debug_errors: true,
+ secret_key_base: "5fIBbNftDx6gQuGq3NsTqV3wE6Rz1QJLJwmjtuhfalulLCEhh94+NY+Lcf+zRkX5",
+ watchers: [
+ esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
+ tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
+ ]
+
+# ## SSL Support
+#
+# In order to use HTTPS in development, a self-signed
+# certificate can be generated by running the following
+# Mix task:
+#
+# mix phx.gen.cert
+#
+# Run `mix help phx.gen.cert` for more information.
+#
+# The `http:` config above can be replaced with:
+#
+# https: [
+# port: 4001,
+# cipher_suite: :strong,
+# keyfile: "priv/cert/selfsigned_key.pem",
+# certfile: "priv/cert/selfsigned.pem"
+# ],
+#
+# If desired, both `http:` and `https:` keys can be
+# configured to run both http and https servers on
+# different ports.
+
+# Watch static and templates for browser reloading.
+config :example, ExampleWeb.Endpoint,
+ live_reload: [
+ patterns: [
+ ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
+ ~r"priv/gettext/.*(po)$",
+ ~r"lib/example_web/(controllers|live|components)/.*(ex|heex)$"
+ ]
+ ]
+
+# Enable dev routes for dashboard and mailbox
+config :example, dev_routes: true
+
+# Do not include metadata nor timestamps in development logs
+config :logger, :console, format: "[$level] $message\n"
+
+# Set a higher stacktrace during development. Avoid configuring such
+# in production as building large stacktraces may be expensive.
+config :phoenix, :stacktrace_depth, 20
+
+# Initialize plugs at runtime for faster development compilation
+config :phoenix, :plug_init_mode, :runtime
diff --git a/elixir-ory-network/config/prod.exs b/elixir-ory-network/config/prod.exs
new file mode 100644
index 00000000..b49883b7
--- /dev/null
+++ b/elixir-ory-network/config/prod.exs
@@ -0,0 +1,14 @@
+import Config
+
+# Note we also include the path to a cache manifest
+# containing the digested version of static files. This
+# manifest is generated by the `mix assets.deploy` task,
+# which you should run after static files are built and
+# before starting your production server.
+config :example, ExampleWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
+
+# Do not print debug messages in production
+config :logger, level: :info
+
+# Runtime production configuration, including reading
+# of environment variables, is done on config/runtime.exs.
diff --git a/elixir-ory-network/config/runtime.exs b/elixir-ory-network/config/runtime.exs
new file mode 100644
index 00000000..68ddf324
--- /dev/null
+++ b/elixir-ory-network/config/runtime.exs
@@ -0,0 +1,82 @@
+import Config
+
+# config/runtime.exs is executed for all environments, including
+# during releases. It is executed after compilation and before the
+# system starts, so it is typically used to load production configuration
+# and secrets from environment variables or elsewhere. Do not define
+# any compile-time configuration in here, as it won't be applied.
+# The block below contains prod specific runtime configuration.
+
+# ## Using releases
+#
+# If you use `mix release`, you need to explicitly enable the server
+# by passing the PHX_SERVER=true when you start it:
+#
+# PHX_SERVER=true bin/example start
+#
+# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`
+# script that automatically sets the env var above.
+if System.get_env("PHX_SERVER") do
+ config :example, ExampleWeb.Endpoint, server: true
+end
+
+if config_env() == :prod do
+ # The secret key base is used to sign/encrypt cookies and other secrets.
+ # A default value is used in config/dev.exs and config/test.exs but you
+ # want to use a different value for prod and you most likely don't want
+ # to check this value into version control, so we use an environment
+ # variable instead.
+ secret_key_base =
+ System.get_env("SECRET_KEY_BASE") ||
+ raise """
+ environment variable SECRET_KEY_BASE is missing.
+ You can generate one by calling: mix phx.gen.secret
+ """
+
+ host = System.get_env("PHX_HOST") || "example.com"
+ port = String.to_integer(System.get_env("PORT") || "4000")
+
+ config :example, ExampleWeb.Endpoint,
+ url: [host: host, port: 443, scheme: "https"],
+ http: [
+ # Enable IPv6 and bind on all interfaces.
+ # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
+ # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
+ # for details about using IPv6 vs IPv4 and loopback vs public addresses.
+ ip: {0, 0, 0, 0, 0, 0, 0, 0},
+ port: port
+ ],
+ secret_key_base: secret_key_base
+
+ # ## SSL Support
+ #
+ # To get SSL working, you will need to add the `https` key
+ # to your endpoint configuration:
+ #
+ # config :example, ExampleWeb.Endpoint,
+ # https: [
+ # ...,
+ # port: 443,
+ # cipher_suite: :strong,
+ # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
+ # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")
+ # ]
+ #
+ # The `cipher_suite` is set to `:strong` to support only the
+ # latest and more secure SSL ciphers. This means old browsers
+ # and clients may not be supported. You can set it to
+ # `:compatible` for wider support.
+ #
+ # `:keyfile` and `:certfile` expect an absolute path to the key
+ # and cert in disk or a relative path inside priv, for example
+ # "priv/ssl/server.key". For all supported SSL configuration
+ # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
+ #
+ # We also recommend setting `force_ssl` in your endpoint, ensuring
+ # no data is ever sent via http, always redirecting to https:
+ #
+ # config :example, ExampleWeb.Endpoint,
+ # force_ssl: [hsts: true]
+ #
+ # Check `Plug.SSL` for all available options in `force_ssl`.
+end
diff --git a/elixir-ory-network/config/test.exs b/elixir-ory-network/config/test.exs
new file mode 100644
index 00000000..ad342829
--- /dev/null
+++ b/elixir-ory-network/config/test.exs
@@ -0,0 +1,20 @@
+import Config
+
+# Configure your database
+#
+# The MIX_TEST_PARTITION environment variable can be used
+# to provide built-in test partitioning in CI environment.
+# Run `mix help test` for more information.
+
+# We don't run a server during test. If one is required,
+# you can enable the server option below.
+config :example, ExampleWeb.Endpoint,
+ http: [ip: {127, 0, 0, 1}, port: 4002],
+ secret_key_base: "3o1BjMPrdjBl6QR9wmzJD5FKkb8w31WjZEyu6cw2zKxc4Z/vyrKDiDGSWUUwyTwa",
+ server: false
+
+# Print only warnings and errors during test
+config :logger, level: :warning
+
+# Initialize plugs at runtime for faster test compilation
+config :phoenix, :plug_init_mode, :runtime
diff --git a/elixir-ory-network/lib/example.ex b/elixir-ory-network/lib/example.ex
new file mode 100644
index 00000000..5f4a4e0b
--- /dev/null
+++ b/elixir-ory-network/lib/example.ex
@@ -0,0 +1,9 @@
+defmodule Example do
+ @moduledoc """
+ Example keeps the contexts that define your domain
+ and business logic.
+
+ Contexts are also responsible for managing your data, regardless
+ if it comes from the database, an external API or others.
+ """
+end
diff --git a/elixir-ory-network/lib/example/application.ex b/elixir-ory-network/lib/example/application.ex
new file mode 100644
index 00000000..ff6273d7
--- /dev/null
+++ b/elixir-ory-network/lib/example/application.ex
@@ -0,0 +1,36 @@
+defmodule Example.Application do
+ # See https://hexdocs.pm/elixir/Application.html
+ # for more information on OTP Applications
+ @moduledoc false
+
+ use Application
+
+ @impl true
+ def start(_type, _args) do
+ children = [
+ # Start the Telemetry supervisor
+ ExampleWeb.Telemetry,
+ # Start the Ecto repository
+ Example.Repo,
+ # Start the PubSub system
+ {Phoenix.PubSub, name: Example.PubSub},
+ # Start the Endpoint (http/https)
+ ExampleWeb.Endpoint
+ # Start a worker by calling: Example.Worker.start_link(arg)
+ # {Example.Worker, arg}
+ ]
+
+ # See https://hexdocs.pm/elixir/Supervisor.html
+ # for other strategies and supported options
+ opts = [strategy: :one_for_one, name: Example.Supervisor]
+ Supervisor.start_link(children, opts)
+ end
+
+ # Tell Phoenix to update the endpoint configuration
+ # whenever the application is updated.
+ @impl true
+ def config_change(changed, _new, removed) do
+ ExampleWeb.Endpoint.config_change(changed, removed)
+ :ok
+ end
+end
diff --git a/elixir-ory-network/lib/example/repo.ex b/elixir-ory-network/lib/example/repo.ex
new file mode 100644
index 00000000..ebed8072
--- /dev/null
+++ b/elixir-ory-network/lib/example/repo.ex
@@ -0,0 +1,5 @@
+defmodule Example.Repo do
+ use Ecto.Repo,
+ otp_app: :example,
+ adapter: Ecto.Adapters.SQLite3
+end
diff --git a/elixir-ory-network/lib/example_web.ex b/elixir-ory-network/lib/example_web.ex
new file mode 100644
index 00000000..b6bb1ac0
--- /dev/null
+++ b/elixir-ory-network/lib/example_web.ex
@@ -0,0 +1,113 @@
+defmodule ExampleWeb do
+ @moduledoc """
+ The entrypoint for defining your web interface, such
+ as controllers, components, channels, and so on.
+
+ This can be used in your application as:
+
+ use ExampleWeb, :controller
+ use ExampleWeb, :html
+
+ The definitions below will be executed for every controller,
+ component, etc, so keep them short and clean, focused
+ on imports, uses and aliases.
+
+ Do NOT define functions inside the quoted expressions
+ below. Instead, define additional modules and import
+ those modules here.
+ """
+
+ def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
+
+ def router do
+ quote do
+ use Phoenix.Router, helpers: false
+
+ # Import common connection and controller functions to use in pipelines
+ import Plug.Conn
+ import Phoenix.Controller
+ import Phoenix.LiveView.Router
+ end
+ end
+
+ def channel do
+ quote do
+ use Phoenix.Channel
+ end
+ end
+
+ def controller do
+ quote do
+ use Phoenix.Controller,
+ formats: [:html, :json],
+ layouts: [html: ExampleWeb.Layouts]
+
+ import Plug.Conn
+ import ExampleWeb.Gettext
+
+ unquote(verified_routes())
+ end
+ end
+
+ def live_view do
+ quote do
+ use Phoenix.LiveView,
+ layout: {ExampleWeb.Layouts, :app}
+
+ unquote(html_helpers())
+ end
+ end
+
+ def live_component do
+ quote do
+ use Phoenix.LiveComponent
+
+ unquote(html_helpers())
+ end
+ end
+
+ def html do
+ quote do
+ use Phoenix.Component
+
+ # Import convenience functions from controllers
+ import Phoenix.Controller,
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
+
+ # Include general helpers for rendering HTML
+ unquote(html_helpers())
+ end
+ end
+
+ defp html_helpers do
+ quote do
+ # HTML escaping functionality
+ import Phoenix.HTML
+ # Core UI components and translation
+ import ExampleWeb.CoreComponents
+ import ExampleWeb.Gettext
+
+ # Shortcut for generating JS commands
+ alias Phoenix.LiveView.JS
+
+ # Routes generation with the ~p sigil
+ unquote(verified_routes())
+ end
+ end
+
+ def verified_routes do
+ quote do
+ use Phoenix.VerifiedRoutes,
+ endpoint: ExampleWeb.Endpoint,
+ router: ExampleWeb.Router,
+ statics: ExampleWeb.static_paths()
+ end
+ end
+
+ @doc """
+ When used, dispatch to the appropriate controller/view/etc.
+ """
+ defmacro __using__(which) when is_atom(which) do
+ apply(__MODULE__, which, [])
+ end
+end
diff --git a/elixir-ory-network/lib/example_web/authentication.ex b/elixir-ory-network/lib/example_web/authentication.ex
new file mode 100644
index 00000000..f0bcfe66
--- /dev/null
+++ b/elixir-ory-network/lib/example_web/authentication.ex
@@ -0,0 +1,60 @@
+defmodule ExampleWeb.Authentication do
+ use ExampleWeb, :controller
+
+ import ExampleWeb.Helpers, only: [cookies: 1]
+
+ alias Ory.Api.Frontend
+
+ def init(opts), do: []
+
+ def call(conn, opts) do
+ # TODO(@tobbbles): Look at expiration and optionally refresh the session
+ if Map.has_key?(conn.assigns, :session) do
+ conn
+ else
+ case Frontend.to_session(Ory.Connection.new(), Cookie: cookies(conn)) do
+ {:ok, %Ory.Model.Session{} = session} ->
+ assign(conn, :session, session)
+
+ _ ->
+ assign(conn, :session, nil)
+ end
+ end
+ end
+
+ def has_session(conn, opts) do
+ if conn.assigns[:session] do
+ conn
+ else
+ conn
+ |> put_flash(:error, "You must be logged in")
+ |> redirect(to: ~p"/auth/login")
+ |> halt()
+ end
+ end
+
+ @doc """
+ Ensures a user's session identity has all emails verified, redirects to verification if not.
+ """
+ def is_verified(%{assigns: %{session: session}} = conn, opts) do
+ IO.puts("checking if user is verified")
+
+ has_unverified =
+ session.identity.verifiable_addresses
+ |> Enum.filter(&unverified_address/1)
+ |> Enum.any?()
+
+ if has_unverified do
+ conn |> redirect(to: ~p"/account/verify") |> halt()
+ else
+ conn
+ end
+
+ conn
+ end
+
+ @spec unverified_address(Ory.Model.VerifiableIdentityAddress.t()) :: boolean()
+ defp unverified_address(address) do
+ !address.verified
+ end
+end
diff --git a/elixir-ory-network/lib/example_web/components/core_components.ex b/elixir-ory-network/lib/example_web/components/core_components.ex
new file mode 100644
index 00000000..30075651
--- /dev/null
+++ b/elixir-ory-network/lib/example_web/components/core_components.ex
@@ -0,0 +1,665 @@
+defmodule ExampleWeb.CoreComponents do
+ @moduledoc """
+ Provides core UI components.
+
+ At the first glance, this module may seem daunting, but its goal is
+ to provide some core building blocks in your application, such modals,
+ tables, and forms. The components are mostly markup and well documented
+ with doc strings and declarative assigns. You may customize and style
+ them in any way you want, based on your application growth and needs.
+
+ The default components use Tailwind CSS, a utility-first CSS framework.
+ See the [Tailwind CSS documentation](https://tailwindcss.com) to learn
+ how to customize them or feel free to swap in another framework altogether.
+
+ Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage.
+ """
+ use Phoenix.Component
+
+ alias Phoenix.LiveView.JS
+ import ExampleWeb.Gettext
+
+ @doc """
+ Renders a modal.
+
+ ## Examples
+
+ <.modal id="confirm-modal">
+ This is a modal.
+
+
+ JS commands may be passed to the `:on_cancel` to configure
+ the closing/cancel event, for example:
+
+ <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}>
+ This is another modal.
+
+
+ """
+ attr :id, :string, required: true
+ attr :show, :boolean, default: false
+ attr :on_cancel, JS, default: %JS{}
+ slot :inner_block, required: true
+
+ def modal(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Shows the flash group with standard titles and content.
+
+ ## Examples
+
+ <.flash_group flash={@flash} />
+ """
+ attr :flash, :map, required: true, doc: "the map of flash messages"
+
+ def flash_group(assigns) do
+ ~H"""
+ <.flash kind={:info} title="Success!" flash={@flash} />
+ <.flash kind={:error} title="Error!" flash={@flash} />
+ <.flash
+ id="client-error"
+ kind={:error}
+ title="We can't find the internet"
+ phx-disconnected={show(".phx-client-error #client-error")}
+ phx-connected={hide("#client-error")}
+ hidden
+ >
+ Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
+
+
+ <.flash
+ id="server-error"
+ kind={:error}
+ title="Something went wrong!"
+ phx-disconnected={show(".phx-server-error #server-error")}
+ phx-connected={hide("#server-error")}
+ hidden
+ >
+ Hang in there while we get back on track
+ <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
+
+ """
+ end
+
+ @doc """
+ Renders a simple form.
+
+ ## Examples
+
+ <.simple_form for={@form} phx-change="validate" phx-submit="save">
+ <.input field={@form[:email]} label="Email"/>
+ <.input field={@form[:username]} label="Username" />
+ <:actions>
+ <.button>Save
+
+
+ """
+ attr :for, :any, required: true, doc: "the datastructure for the form"
+ attr :as, :any, default: nil, doc: "the server side parameter to collect all input under"
+
+ attr :rest, :global,
+ include: ~w(autocomplete name rel action enctype method novalidate target multipart),
+ doc: "the arbitrary HTML attributes to apply to the form tag"
+
+ slot :inner_block, required: true
+ slot :actions, doc: "the slot for form actions, such as a submit button"
+
+ def simple_form(assigns) do
+ ~H"""
+ <.form :let={f} for={@for} as={@as} {@rest}>
+
+ <%= render_slot(@inner_block, f) %>
+
+ <%= render_slot(action, f) %>
+
+
+
+ """
+ end
+
+ @doc """
+ Renders a button.
+
+ ## Examples
+
+ <.button>Send!
+ <.button phx-click="go" class="ml-2">Send!
+ """
+ attr :type, :string, default: nil
+ attr :class, :string, default: nil
+ attr :rest, :global, include: ~w(disabled form name value)
+
+ slot :inner_block, required: true
+
+ def button(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Renders an input with label and error messages.
+
+ A `Phoenix.HTML.FormField` may be passed as argument,
+ which is used to retrieve the input name, id, and values.
+ Otherwise all attributes may be passed explicitly.
+
+ ## Types
+
+ This function accepts all HTML input types, considering that:
+
+ * You may also set `type="select"` to render a `