aboutsummaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorJordan Bracco <href@random.sh>2021-08-06 15:55:35 +0200
committerJordan Bracco <href@random.sh>2021-08-06 15:55:35 +0200
commitfd639a6f74d3a2c4cdcc18f5191c101cd41f289b (patch)
tree70697bb7c165197eefdeb60f42fbb71045c8ca59 /apps
parentNew rebar app (diff)
Kratos flows
Diffstat (limited to 'apps')
-rw-r--r--apps/ory/.gitignore19
-rw-r--r--apps/ory/LICENSE191
-rw-r--r--apps/ory/README.md9
-rw-r--r--apps/ory/rebar.config2
-rw-r--r--apps/ory/src/ory.app.src16
-rw-r--r--apps/ory/src/ory.erl3
-rw-r--r--apps/ory/src/ory_kratos.erl76
-rw-r--r--apps/styx/src/styx.app.src6
-rw-r--r--apps/styx/src/styx.erl9
-rw-r--r--apps/styx_service/.gitignore19
-rw-r--r--apps/styx_service/LICENSE191
-rw-r--r--apps/styx_service/README.md9
-rw-r--r--apps/styx_service/rebar.config9
-rw-r--r--apps/styx_service/src/styx_service.app.src16
-rw-r--r--apps/styx_service/src/styx_service.erl25
-rw-r--r--apps/styx_service/src/styx_service_app.erl18
-rw-r--r--apps/styx_service/src/styx_service_sup.erl52
-rw-r--r--apps/styx_web/.gitignore19
-rw-r--r--apps/styx_web/LICENSE191
-rw-r--r--apps/styx_web/README.md9
-rw-r--r--apps/styx_web/priv/assets/app.css997
-rw-r--r--apps/styx_web/priv/assets/app.js3
-rw-r--r--apps/styx_web/rebar.config21
-rw-r--r--apps/styx_web/src/styx_web.app.src18
-rw-r--r--apps/styx_web/src/styx_web.erl77
-rw-r--r--apps/styx_web/src/styx_web_app.erl53
-rw-r--r--apps/styx_web/src/styx_web_error.erl17
-rw-r--r--apps/styx_web/src/styx_web_index.erl14
-rw-r--r--apps/styx_web/src/styx_web_kratos_flow.erl25
-rw-r--r--apps/styx_web/src/styx_web_launchpad.erl16
-rw-r--r--apps/styx_web/src/styx_web_logout.erl13
-rw-r--r--apps/styx_web/src/styx_web_sup.erl35
-rw-r--r--apps/styx_web/templates/account.dtl17
-rw-r--r--apps/styx_web/templates/error.dtl8
-rw-r--r--apps/styx_web/templates/form.dtl6
-rw-r--r--apps/styx_web/templates/form_input.dtl18
-rw-r--r--apps/styx_web/templates/form_submit.dtl3
-rw-r--r--apps/styx_web/templates/index.dtl4
-rw-r--r--apps/styx_web/templates/launchpad.dtl10
-rw-r--r--apps/styx_web/templates/layout.dtl21
-rw-r--r--apps/styx_web/templates/login.dtl21
-rw-r--r--apps/styx_web/templates/recovery.dtl17
-rw-r--r--apps/styx_web/templates/registration.dtl17
-rw-r--r--apps/styx_web/templates/verification.dtl11
44 files changed, 2330 insertions, 1 deletions
diff --git a/apps/ory/.gitignore b/apps/ory/.gitignore
new file mode 100644
index 0000000..f1c4554
--- /dev/null
+++ b/apps/ory/.gitignore
@@ -0,0 +1,19 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+_build
+.idea
+*.iml
+rebar3.crashdump
+*~
diff --git a/apps/ory/LICENSE b/apps/ory/LICENSE
new file mode 100644
index 0000000..e389eb2
--- /dev/null
+++ b/apps/ory/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2021, Jordan Bracco <href@random.sh>.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/apps/ory/README.md b/apps/ory/README.md
new file mode 100644
index 0000000..33d9d20
--- /dev/null
+++ b/apps/ory/README.md
@@ -0,0 +1,9 @@
+ory
+=====
+
+Ory library
+
+Build
+-----
+
+ $ rebar3 compile
diff --git a/apps/ory/rebar.config b/apps/ory/rebar.config
new file mode 100644
index 0000000..a5ac854
--- /dev/null
+++ b/apps/ory/rebar.config
@@ -0,0 +1,2 @@
+{erl_opts, [debug_info]}.
+{deps, [{hackney, "1.17.4"}, {jsone, "1.6.1"}]}.
diff --git a/apps/ory/src/ory.app.src b/apps/ory/src/ory.app.src
new file mode 100644
index 0000000..c4098ee
--- /dev/null
+++ b/apps/ory/src/ory.app.src
@@ -0,0 +1,16 @@
+{application, ory,
+ [{description, "Ory library"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib,
+ hackney,
+ jsone
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+ ]}.
diff --git a/apps/ory/src/ory.erl b/apps/ory/src/ory.erl
new file mode 100644
index 0000000..5c06bc9
--- /dev/null
+++ b/apps/ory/src/ory.erl
@@ -0,0 +1,3 @@
+-module(ory).
+
+-export([]).
diff --git a/apps/ory/src/ory_kratos.erl b/apps/ory/src/ory_kratos.erl
new file mode 100644
index 0000000..14afa82
--- /dev/null
+++ b/apps/ory/src/ory_kratos.erl
@@ -0,0 +1,76 @@
+-module(ory_kratos).
+
+-export([login_url/1, registration_url/1, settings_url/1, recovery_url/1, verification_url/1, url/0]).
+-export([registration_flow/2, login_flow/2, settings_flow/2, recovery_flow/2, verification_flow/2, logout_flow/1, whoami/1, error/1]).
+
+login_url(browser) ->
+ [url(), "/self-service/login/browser"].
+
+registration_url(browser) ->
+ [url(), "/self-service/registration/browser"].
+
+settings_url(browser) ->
+ [url(), "/self-service/settings/browser"].
+
+recovery_url(browser) ->
+ [url(), "/self-service/recovery/browser"].
+
+verification_url(browser) ->
+ [url(), "/self-service/verification/browser"].
+
+url() ->
+ {ok, Value} = application:get_env(ory, kratos_url),
+ Value.
+
+registration_flow(Cookie, Id) ->
+ Url = [url(), "/self-service/registration/flows?id=", Id],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+login_flow(Cookie, Id) ->
+ Url = [url(), "/self-service/login/flows?id=", Id],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+settings_flow(Cookie, Id) ->
+ Url = [url(), "/self-service/settings/flows?id=", Id],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+recovery_flow(Cookie, Id) ->
+ Url = [url(), "/self-service/recovery/flows?id=", Id],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+verification_flow(Cookie, Id) ->
+ Url = [url(), "/self-service/verification/flows?id=", Id],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+logout_flow(Cookie) ->
+ Url = [url(), "/self-service/logout/browser"],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+whoami(Cookie) ->
+ Url = [url(), "/sessions/whoami"],
+ Headers = [{<<"cookie">>, Cookie}, {"accept", "application/json"}],
+ api_response(hackney:request(get, Url, Headers, <<>>, [])).
+
+error(Id) ->
+ Url = [url(), "/self-service/errors?id=", Id],
+ {ok, 200, _, Client} = hackney:request(get, Url, [], <<>>, []),
+ {ok, Body} = hackney:body(Client),
+ {ok, jsone:decode(Body)}.
+
+api_response(Error = {error, Error}) ->
+ logger:error("ory_kratos hackney error: ~p", [Error]),
+ {error, #{<<"code">> => 503, <<"status">> => "Not Available", <<"message">> => "This service isn't available at the moment."}};
+api_response({ok, 200, _, Client}) ->
+ {ok, Body} = hackney:body(Client),
+ {ok, jsone:decode(Body)};
+api_response({ok, Code, _, Client}) ->
+ {ok, Body} = hackney:body(Client),
+ JSON = #{<<"error">> := Error} = jsone:decode(Body),
+ logger:debug("hydra error: ~p", [JSON]),
+ {error, Error}.
diff --git a/apps/styx/src/styx.app.src b/apps/styx/src/styx.app.src
index 92a1e55..25f091e 100644
--- a/apps/styx/src/styx.app.src
+++ b/apps/styx/src/styx.app.src
@@ -5,7 +5,11 @@
{mod, {styx_app, []}},
{applications,
[kernel,
- stdlib
+ stdlib,
+ sasl,
+ styx_service,
+ ory,
+ styx_web
]},
{env,[]},
{modules, []},
diff --git a/apps/styx/src/styx.erl b/apps/styx/src/styx.erl
new file mode 100644
index 0000000..1a582b8
--- /dev/null
+++ b/apps/styx/src/styx.erl
@@ -0,0 +1,9 @@
+-module(styx).
+
+-export([kratos_url/0, hydra_url/0]).
+
+kratos_url() ->
+ application:get_env(styx, kratos_url, undefined).
+
+hydra_url() ->
+ application:get_env(styx, hydra_url, undefined).
diff --git a/apps/styx_service/.gitignore b/apps/styx_service/.gitignore
new file mode 100644
index 0000000..f1c4554
--- /dev/null
+++ b/apps/styx_service/.gitignore
@@ -0,0 +1,19 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+_build
+.idea
+*.iml
+rebar3.crashdump
+*~
diff --git a/apps/styx_service/LICENSE b/apps/styx_service/LICENSE
new file mode 100644
index 0000000..e389eb2
--- /dev/null
+++ b/apps/styx_service/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2021, Jordan Bracco <href@random.sh>.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/apps/styx_service/README.md b/apps/styx_service/README.md
new file mode 100644
index 0000000..c73c5ca
--- /dev/null
+++ b/apps/styx_service/README.md
@@ -0,0 +1,9 @@
+styx_service
+=====
+
+Styx: child processes
+
+Build
+-----
+
+ $ rebar3 compile
diff --git a/apps/styx_service/rebar.config b/apps/styx_service/rebar.config
new file mode 100644
index 0000000..ba889f2
--- /dev/null
+++ b/apps/styx_service/rebar.config
@@ -0,0 +1,9 @@
+{erl_opts, [debug_info]}.
+{deps, [
+ {erlexec, "1.18.11"}
+]}.
+
+{shell, [
+ % {config, "config/sys.config"},
+ {apps, [styx_service]}
+]}.
diff --git a/apps/styx_service/src/styx_service.app.src b/apps/styx_service/src/styx_service.app.src
new file mode 100644
index 0000000..96115e7
--- /dev/null
+++ b/apps/styx_service/src/styx_service.app.src
@@ -0,0 +1,16 @@
+{application, styx_service,
+ [{description, "Styx: child processes"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {mod, {styx_service_app, []}},
+ {applications,
+ [kernel,
+ stdlib,
+ erlexec
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+ ]}.
diff --git a/apps/styx_service/src/styx_service.erl b/apps/styx_service/src/styx_service.erl
new file mode 100644
index 0000000..1e7ff88
--- /dev/null
+++ b/apps/styx_service/src/styx_service.erl
@@ -0,0 +1,25 @@
+-module(styx_service).
+
+-behaviour(gen_server).
+
+-export([start_link/2]).
+
+-export([init/1, handle_info/2]).
+
+start_link(Name, Cmd) ->
+ gen_server:start_link({local, Name}, ?MODULE, [Name, Cmd], []).
+
+init([Name, Cmd]) ->
+ Opts = [stdout, stderr],
+ {ok, Pid, OsPid} = exec:run_link(Cmd, Opts),
+ logger:info("Started system process ~p '~p', system pid = ~p", [Name, Cmd, OsPid]),
+ {ok, {Name, Pid, OsPid}}.
+
+handle_info({stdout, OsPid, Binary}, {Name, _Pid, OsPid} = State) ->
+ logger:info("~p[~p] ~p", [Name, OsPid, Binary]),
+ {noreply, State};
+handle_info({stderr, OsPid, Binary}, {Name, _Pid, OsPid} = State) ->
+ logger:warning("~p[~p] ~p", [Name, OsPid, Binary]),
+ {noreply, State};
+handle_info(Info, {Name, _, _} = State) ->
+ logger:warning("~p ~p", [Name, Info]).
diff --git a/apps/styx_service/src/styx_service_app.erl b/apps/styx_service/src/styx_service_app.erl
new file mode 100644
index 0000000..67e3414
--- /dev/null
+++ b/apps/styx_service/src/styx_service_app.erl
@@ -0,0 +1,18 @@
+%%%-------------------------------------------------------------------
+%% @doc styx_service public API
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(styx_service_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+start(_StartType, _StartArgs) ->
+ styx_service_sup:start_link().
+
+stop(_State) ->
+ ok.
+
+%% internal functions
diff --git a/apps/styx_service/src/styx_service_sup.erl b/apps/styx_service/src/styx_service_sup.erl
new file mode 100644
index 0000000..5461ab2
--- /dev/null
+++ b/apps/styx_service/src/styx_service_sup.erl
@@ -0,0 +1,52 @@
+%%%-------------------------------------------------------------------
+%% @doc styx_service top level supervisor.
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(styx_service_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+-define(EXEC_TIMEOUT, 5000).
+
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%% sup_flags() = #{strategy => strategy(), % optional
+%% intensity => non_neg_integer(), % optional
+%% period => pos_integer()} % optional
+%% child_spec() = #{id => child_id(), % mandatory
+%% start => mfargs(), % mandatory
+%% restart => restart(), % optional
+%% shutdown => shutdown(), % optional
+%% type => worker(), % optional
+%% modules => modules()} % optional
+init([]) ->
+ Env = application:get_all_env(styx_service),
+ SupFlags = #{strategy => one_for_one,
+ intensity => 0,
+ period => 1},
+ ChildSpecs = child_specs(Env, []),
+ {ok, {SupFlags, ChildSpecs}}.
+
+%% internal functions
+
+child_specs([{watch_assets, _Options} | Rest], Acc) ->
+ ExecArgs = [watch_assets, "npm --prefix /Users/href/dev/styx/assets/ run watch"],
+ Spec = {watch_assets, {styx_service, start_link, ExecArgs}, permanent, 5000, worker, [styx_service]},
+ child_specs(Rest, [Spec | Acc]);
+child_specs([{kratos, _Options} | Rest], Acc) ->
+ ExecArgs = [kratos, "kratos serve --watch-courier --config /Users/href/dev/styx/config/kratos.yml"],
+ Spec = {kratos, {styx_service, start_link, ExecArgs}, permanent, 5000, worker, [styx_service]},
+ child_specs(Rest, [Spec | Acc]);
+child_specs([{hydra, _Options} | Rest], Acc) ->
+ ExecArgs = [hydra, "hydra serve all --dangerous-allow-insecure-redirect-urls --dangerous-force-http --config /Users/href/dev/styx/config/hydra.yml"],
+ Spec = {hydra, {styx_service, start_link, ExecArgs}, permanent, 5000, worker, [styx_service]},
+ child_specs(Rest, [Spec | Acc]);
+child_specs([], Acc) ->
+ Acc.
diff --git a/apps/styx_web/.gitignore b/apps/styx_web/.gitignore
new file mode 100644
index 0000000..f1c4554
--- /dev/null
+++ b/apps/styx_web/.gitignore
@@ -0,0 +1,19 @@
+.rebar3
+_*
+.eunit
+*.o
+*.beam
+*.plt
+*.swp
+*.swo
+.erlang.cookie
+ebin
+log
+erl_crash.dump
+.rebar
+logs
+_build
+.idea
+*.iml
+rebar3.crashdump
+*~
diff --git a/apps/styx_web/LICENSE b/apps/styx_web/LICENSE
new file mode 100644
index 0000000..e389eb2
--- /dev/null
+++ b/apps/styx_web/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2021, Jordan Bracco <href@random.sh>.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/apps/styx_web/README.md b/apps/styx_web/README.md
new file mode 100644
index 0000000..68d41c2
--- /dev/null
+++ b/apps/styx_web/README.md
@@ -0,0 +1,9 @@
+styx_web
+=====
+
+Styx: web/html
+
+Build
+-----
+
+ $ rebar3 compile
diff --git a/apps/styx_web/priv/assets/app.css b/apps/styx_web/priv/assets/app.css
new file mode 100644
index 0000000..32d2d21
--- /dev/null
+++ b/apps/styx_web/priv/assets/app.css
@@ -0,0 +1,997 @@
+/*! tailwindcss v2.2.7 | MIT License | https://tailwindcss.com */
+
+/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
+
+/*
+Document
+========
+*/
+
+/**
+Use a better box model (opinionated).
+*/
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+}
+
+/**
+Use a more readable tab size (opinionated).
+*/
+
+html {
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+}
+
+/**
+1. Correct the line height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+*/
+
+html {
+ line-height: 1.15;
+ /* 1 */
+ -webkit-text-size-adjust: 100%;
+ /* 2 */
+}
+
+/*
+Sections
+========
+*/
+
+/**
+Remove the margin in all browsers.
+*/
+
+body {
+ margin: 0;
+}
+
+/**
+Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
+*/
+
+body {
+ font-family:
+ system-ui,
+ -apple-system, /* Firefox supports this but not yet `system-ui` */
+ 'Segoe UI',
+ Roboto,
+ Helvetica,
+ Arial,
+ sans-serif,
+ 'Apple Color Emoji',
+ 'Segoe UI Emoji';
+}
+
+/*
+Grouping content
+================
+*/
+
+/**
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+*/
+
+hr {
+ height: 0;
+ /* 1 */
+ color: inherit;
+ /* 2 */
+}
+
+/*
+Text-level semantics
+====================
+*/
+
+/**
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr[title] {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+}
+
+/**
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
+2. Correct the odd 'em' font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+ font-family:
+ ui-monospace,
+ SFMono-Regular,
+ Consolas,
+ 'Liberation Mono',
+ Menlo,
+ monospace;
+ /* 1 */
+ font-size: 1em;
+ /* 2 */
+}
+
+/**
+Add the correct font size in all browsers.
+*/
+
+small {
+ font-size: 80%;
+}
+
+/**
+Prevent 'sub' and 'sup' elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/*
+Tabular data
+============
+*/
+
+/**
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+*/
+
+table {
+ text-indent: 0;
+ /* 1 */
+ border-color: inherit;
+ /* 2 */
+}
+
+/*
+Forms
+=====
+*/
+
+/**
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit;
+ /* 1 */
+ font-size: 100%;
+ /* 1 */
+ line-height: 1.15;
+ /* 1 */
+ margin: 0;
+ /* 2 */
+}
+
+/**
+Remove the inheritance of text transform in Edge and Firefox.
+1. Remove the inheritance of text transform in Firefox.
+*/
+
+button,
+select {
+ /* 1 */
+ text-transform: none;
+}
+
+/**
+Correct the inability to style clickable types in iOS and Safari.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+ -webkit-appearance: button;
+}
+
+/**
+Remove the inner border and padding in Firefox.
+*/
+
+::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+Restore the focus styles unset by the previous rule.
+*/
+
+:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+Remove the additional ':invalid' styles in Firefox.
+See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
+*/
+
+:-moz-ui-invalid {
+ box-shadow: none;
+}
+
+/**
+Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
+*/
+
+legend {
+ padding: 0;
+}
+
+/**
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+ -webkit-appearance: textfield;
+ /* 1 */
+ outline-offset: -2px;
+ /* 2 */
+}
+
+/**
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to 'inherit' in Safari.
+*/
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button;
+ /* 1 */
+ font: inherit;
+ /* 2 */
+}
+
+/*
+Interactive
+===========
+*/
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+ display: list-item;
+}
+
+/**
+ * Manually forked from SUIT CSS Base: https://github.com/suitcss/base
+ * A thin layer on top of normalize.css that provides a starting point more
+ * suitable for web applications.
+ */
+
+/**
+ * Removes the default spacing and border for appropriate elements.
+ */
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+ margin: 0;
+}
+
+button {
+ background-color: transparent;
+ background-image: none;
+}
+
+fieldset {
+ margin: 0;
+ padding: 0;
+}
+
+ol,
+ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+/**
+ * Tailwind custom reset styles
+ */
+
+/**
+ * 1. Use the user's configured `sans` font-family (with Tailwind's default
+ * sans-serif font stack as a fallback) as a sane default.
+ * 2. Use Tailwind's default "normal" line-height so the user isn't forced
+ * to override it to ensure consistency even when using the default theme.
+ */
+
+html {
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ /* 1 */
+ line-height: 1.5;
+ /* 2 */
+}
+
+/**
+ * Inherit font-family and line-height from `html` so users can set them as
+ * a class directly on the `html` element.
+ */
+
+body {
+ font-family: inherit;
+ line-height: inherit;
+}
+
+/**
+ * 1. Prevent padding and border from affecting element width.
+ *
+ * We used to set this in the html element and inherit from
+ * the parent element for everything else. This caused issues
+ * in shadow-dom-enhanced elements like <details> where the content
+ * is wrapped by a div with box-sizing set to `content-box`.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/4
+ *
+ *
+ * 2. Allow adding a border to an element by just adding a border-width.
+ *
+ * By default, the way the browser specifies that an element should have no
+ * border is by setting it's border-style to `none` in the user-agent
+ * stylesheet.
+ *
+ * In order to easily add borders to elements by just setting the `border-width`
+ * property, we change the default border-style for all elements to `solid`, and
+ * use border-width to hide them instead. This way our `border` utilities only
+ * need to set the `border-width` property instead of the entire `border`
+ * shorthand, making our border utilities much more straightforward to compose.
+ *
+ * https://github.com/tailwindcss/tailwindcss/pull/116
+ */
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+ /* 1 */
+ border-width: 0;
+ /* 2 */
+ border-style: solid;
+ /* 2 */
+ border-color: currentColor;
+ /* 2 */
+}
+
+/*
+ * Ensure horizontal rules are visible by default
+ */
+
+hr {
+ border-top-width: 1px;
+}
+
+/**
+ * Undo the `border-style: none` reset that Normalize applies to images so that
+ * our `border-{width}` utilities have the expected effect.
+ *
+ * The Normalize reset is unnecessary for us since we default the border-width
+ * to 0 on all elements.
+ *
+ * https://github.com/tailwindcss/tailwindcss/issues/362
+ */
+
+img {
+ border-style: solid;
+}
+
+textarea {
+ resize: vertical;
+}
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+ opacity: 1;
+ color: #9ca3af;
+}
+
+input:-ms-input-placeholder, textarea:-ms-input-placeholder {
+ opacity: 1;
+ color: #9ca3af;
+}
+
+input::placeholder,
+textarea::placeholder {
+ opacity: 1;
+ color: #9ca3af;
+}
+
+button,
+[role="button"] {
+ cursor: pointer;
+}
+
+table {
+ border-collapse: collapse;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-size: inherit;
+ font-weight: inherit;
+}
+
+/**
+ * Reset links to optimize for opt-in styling instead of
+ * opt-out.
+ */
+
+a {
+ color: inherit;
+ text-decoration: inherit;
+}
+
+/**
+ * Reset form element properties that are easy to forget to
+ * style explicitly so you don't inadvertently introduce
+ * styles that deviate from your design system. These styles
+ * supplement a partial reset that is already applied by
+ * normalize.css.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ padding: 0;
+ line-height: inherit;
+ color: inherit;
+}
+
+/**
+ * Use the configured 'mono' font family for elements that
+ * are expected to be rendered with a monospace font, falling
+ * back to the system monospace stack if there is no configured
+ * 'mono' font family.
+ */
+
+pre,
+code,
+kbd,
+samp {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+/**
+ * 1. Make replaced elements `display: block` by default as that's
+ * the behavior you want almost all of the time. Inspired by
+ * CSS Remedy, with `svg` added as well.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/14
+ *
+ * 2. Add `vertical-align: middle` to align replaced elements more
+ * sensibly by default when overriding `display` by adding a
+ * utility like `inline`.
+ *
+ * This can trigger a poorly considered linting error in some
+ * tools but is included by design.
+ *
+ * https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210
+ */
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+ display: block;
+ /* 1 */
+ vertical-align: middle;
+ /* 2 */
+}
+
+/**
+ * Constrain images and videos to the parent width and preserve
+ * their intrinsic aspect ratio.
+ *
+ * https://github.com/mozdevs/cssremedy/issues/14
+ */
+
+img,
+video {
+ max-width: 100%;
+ height: auto;
+}
+
+/**
+ * Ensure the default browser behavior of the `hidden` attribute.
+ */
+
+[hidden] {
+ display: none;
+}
+
+*, ::before, ::after {
+ --tw-border-opacity: 1;
+ border-color: rgba(229, 231, 235, var(--tw-border-opacity));
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgba(59, 130, 246, 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+}
+
+.container {
+ width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+}
+
+@media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+}
+
+.mt-8 {
+ margin-top: 2rem;
+}
+
+.mt-6 {
+ margin-top: 1.5rem;
+}
+
+.mt-1 {
+ margin-top: 0.25rem;
+}
+
+.mt-2 {
+ margin-top: 0.5rem;
+}
+
+.mt-4 {
+ margin-top: 1rem;
+}
+
+.space-y-6 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
+}
+
+.text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.root {
+ display: flex;
+ min-height: 100vh;
+ --tw-bg-opacity: 1;
+ background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .root {
+ --tw-bg-opacity: 1;
+ background-color: rgba(17, 24, 39, var(--tw-bg-opacity));
+ }
+}
+
+.main {
+ display: flex;
+ flex: 1 1 0%;
+ flex-direction: column;
+ justify-content: center;
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+@media (min-width: 640px) {
+ .main {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+}
+
+@media (min-width: 1024px) {
+ .main {
+ flex: none;
+ }
+
+ .main {
+ padding-left: 5rem;
+ padding-right: 5rem;
+ }
+}
+
+@media (min-width: 1280px) {
+ .main {
+ padding-left: 6rem;
+ padding-right: 6rem;
+ }
+}
+
+.container {
+ margin-left: auto;
+ margin-right: auto;
+ width: 100%;
+ max-width: 24rem;
+}
+
+@media (min-width: 1024px) {
+ .container {
+ width: 24rem;
+ }
+}
+
+.container > h1 {
+ margin-bottom: 2rem;
+ width: auto;
+ font-size: 1.5rem;
+ line-height: 2rem;
+ font-weight: 800;
+ --tw-text-opacity: 1;
+ color: rgba(76, 29, 149, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .container > h1 {
+ --tw-text-opacity: 1;
+ color: rgba(167, 139, 250, var(--tw-text-opacity));
+ }
+}
+
+.container > .header > h2 {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ font-weight: 700;
+ --tw-text-opacity: 1;
+ color: rgba(17, 24, 39, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .container > .header > h2 {
+ --tw-text-opacity: 1;
+ color: rgba(156, 163, 175, var(--tw-text-opacity));
+ }
+}
+
+.container > .header > p {
+ margin-top: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ --tw-text-opacity: 1;
+ color: rgba(75, 85, 99, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .container > .header > p {
+ --tw-text-opacity: 1;
+ color: rgba(107, 114, 128, var(--tw-text-opacity));
+ }
+}
+
+.container > .header > p > a {
+ font-weight: 500;
+ --tw-text-opacity: 1;
+ color: rgba(79, 70, 229, var(--tw-text-opacity));
+}
+
+.container > .header > p > a:hover {
+ --tw-text-opacity: 1;
+ color: rgba(99, 102, 241, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .container > .header > p > a {
+ --tw-text-opacity: 1;
+ color: rgba(129, 140, 248, var(--tw-text-opacity));
+ }
+
+ .container > .header > p > a:hover {
+ --tw-text-opacity: 1;
+ color: rgba(165, 180, 252, var(--tw-text-opacity));
+ }
+}
+
+.background {
+ position: relative;
+ display: none;
+ width: 0px;
+ flex: 1 1 0%;
+}
+
+@media (min-width: 1024px) {
+ .background {
+ display: block;
+ }
+}
+
+.background > img {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ height: 100%;
+ width: 100%;
+ -o-object-fit: cover;
+ object-fit: cover;
+}
+
+.form-message {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 700;
+}
+
+.text-error, .text-alert {
+ --tw-text-opacity: 1;
+ color: rgba(153, 27, 27, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .text-error, .text-alert {
+ --tw-text-opacity: 1;
+ color: rgba(252, 165, 165, var(--tw-text-opacity));
+ }
+}
+
+.input {
+ display: block;
+ width: 100%;
+ border-radius: 0.375rem;
+ border-width: 1px;
+ --tw-border-opacity: 1;
+ border-color: rgba(209, 213, 219, var(--tw-border-opacity));
+ padding-left: 0.75rem;
+ padding-right: 0.75rem;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.input::-moz-placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(156, 163, 175, var(--tw-placeholder-opacity));
+}
+
+.input:-ms-input-placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(156, 163, 175, var(--tw-placeholder-opacity));
+}
+
+.input::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(156, 163, 175, var(--tw-placeholder-opacity));
+}
+
+.input {
+ --tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.input:focus {
+ --tw-border-opacity: 1;
+ border-color: rgba(99, 102, 241, var(--tw-border-opacity));
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgba(99, 102, 241, var(--tw-ring-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .input {
+ --tw-border-opacity: 1;
+ border-color: rgba(55, 65, 81, var(--tw-border-opacity));
+ }
+
+ .input {
+ --tw-border-opacity: 1;
+ border-color: rgba(107, 114, 128, var(--tw-border-opacity));
+ }
+
+ .input {
+ --tw-bg-opacity: 1;
+ background-color: rgba(156, 163, 175, var(--tw-bg-opacity));
+ }
+
+ .input::-moz-placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(31, 41, 55, var(--tw-placeholder-opacity));
+ }
+
+ .input:-ms-input-placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(31, 41, 55, var(--tw-placeholder-opacity));
+ }
+
+ .input::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgba(31, 41, 55, var(--tw-placeholder-opacity));
+ }
+
+ .input:focus {
+ --tw-border-opacity: 1;
+ border-color: rgba(99, 102, 241, var(--tw-border-opacity));
+ }
+
+ .input:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgba(99, 102, 241, var(--tw-ring-opacity));
+ }
+}
+
+@media (min-width: 640px) {
+ .input {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+}
+
+.btn-submit {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ border-radius: 0.375rem;
+ border-width: 1px;
+ border-color: transparent;
+ --tw-bg-opacity: 1;
+ background-color: rgba(79, 70, 229, var(--tw-bg-opacity));
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ --tw-text-opacity: 1;
+ color: rgba(255, 255, 255, var(--tw-text-opacity));
+ --tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.btn-submit:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgba(67, 56, 202, var(--tw-bg-opacity));
+}
+
+.btn-submit:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgba(99, 102, 241, var(--tw-ring-opacity));
+ --tw-ring-offset-width: 2px;
+}
+
+@media (prefers-color-scheme: dark) {
+ .btn-submit:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgba(99, 102, 241, var(--tw-bg-opacity));
+ }
+
+ .btn-submit:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgba(165, 180, 252, var(--tw-ring-opacity));
+ }
+}
+
+.label {
+ display: block;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ --tw-text-opacity: 1;
+ color: rgba(55, 65, 81, var(--tw-text-opacity));
+}
+
+@media (prefers-color-scheme: dark) {
+ .label {
+ --tw-text-opacity: 1;
+ color: rgba(209, 213, 219, var(--tw-text-opacity));
+ }
+}
diff --git a/apps/styx_web/priv/assets/app.js b/apps/styx_web/priv/assets/app.js
new file mode 100644
index 0000000..1627bc2
--- /dev/null
+++ b/apps/styx_web/priv/assets/app.js
@@ -0,0 +1,3 @@
+(() => {
+})();
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFtdLAogICJtYXBwaW5ncyI6ICIiLAogICJuYW1lcyI6IFtdCn0K
diff --git a/apps/styx_web/rebar.config b/apps/styx_web/rebar.config
new file mode 100644
index 0000000..42392a1
--- /dev/null
+++ b/apps/styx_web/rebar.config
@@ -0,0 +1,21 @@
+{erl_opts, [debug_info]}.
+{deps, [
+ {cowboy, "2.9.0"},
+ {trails, "2.3.0"},
+ {erlydtl, "0.14.0"}
+]}.
+
+{plugins, [
+ {rebar3_erlydtl_plugin, ".*", {git, "https://github.com/tsloughter/rebar3_erlydtl_plugin.git", {branch, "master"}}}
+]}.
+
+{provider_hooks, [
+ {pre, [{compile, {erlydtl, compile}}]}
+ ]}.
+
+{shell, [
+ % {config, "config/sys.config"},
+ {apps, [styx_web]}
+]}.
+
+{erlydtl_opts, [{doc_root, "templates"}]}.
diff --git a/apps/styx_web/src/styx_web.app.src b/apps/styx_web/src/styx_web.app.src
new file mode 100644
index 0000000..9e82554
--- /dev/null
+++ b/apps/styx_web/src/styx_web.app.src
@@ -0,0 +1,18 @@
+{application, styx_web,
+ [{description, "Styx: web/html"},
+ {vsn, "0.1.0"},
+ {registered, []},
+ {mod, {styx_web_app, []}},
+ {applications,
+ [kernel,
+ stdlib,
+ erlydtl,
+ cowboy,
+ trails
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {licenses, ["Apache 2.0"]},
+ {links, []}
+ ]}.
diff --git a/apps/styx_web/src/styx_web.erl b/apps/styx_web/src/styx_web.erl
new file mode 100644
index 0000000..cf565bd
--- /dev/null
+++ b/apps/styx_web/src/styx_web.erl
@@ -0,0 +1,77 @@
+-module(styx_web).
+
+-export([render/3, render_form/1, reply_html/3, reply_html/4, temporary_redirect/2, req_param/2, identity_name/1]).
+
+identity_name(#{<<"identity">> := Identity}) ->
+ identity_name(Identity);
+identity_name(#{<<"traits">> := #{<<"name">> := #{<<"first">> := F, <<"last">> := L}}}) when is_binary(F), is_binary(L) ->
+ [F, " ", L];
+identity_name(#{<<"traits">> := #{<<"name">> := N}}) when is_binary(N) ->
+ N;
+identity_name(#{<<"traits">> := #{<<"username">> := U}}) when is_binary(U) ->
+ U;
+identity_name(#{<<"traits">> := #{<<"email">> := E}}) when is_binary(E) ->
+ E;
+identity_name(#{<<"id">> := Id}) ->
+ Id.
+
+render(Req, InnerModule, Assigns) ->
+ {ok, InnerHtml} = InnerModule:render(Assigns),
+ render_layout(Req, InnerHtml, Assigns).
+
+render_form(UI = #{<<"action">> := Action, <<"nodes">> := Nodes}) ->
+ Inputs = render_node(Nodes, []),
+ Msgs = maps:get(<<"messages">>, UI, []),
+ {ok, Html} = form_dtl:render([{"action", Action}, {"inputs", Inputs}, {"messages", Msgs}]),
+ Html.
+
+render_layout(_Req, InnerHtml, Assigns0) ->
+ Assigns = [{"site_title", application:get_env(?MODULE, site_title, <<"Styx SSO">>)},
+ {"background_image_url", application:get_env(?MODULE, background_image_url, <<"https://images.unsplash.com/photo-1505904267569-f02eaeb45a4c?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1908&q=80">>)},
+ {"inner", InnerHtml}
+ | Assigns0],
+ {ok, Html} = layout_dtl:render(Assigns),
+ Html.
+
+reply_html(Req, Code, Html) ->
+ reply_html(Req, Code, Html, #{}).
+
+reply_html(Req, Code, Html, Headers0) ->
+ Headers = maps:put(<<"content-type">>, <<"text/html">>, Headers0),
+ cowboy_req:reply(Code, Headers, Html, Req).
+
+temporary_redirect(Req0, Url) ->
+ cowboy_req:reply(307, #{<<"location">> => Url}, Req0).
+
+req_param(Req, Param) ->
+ Qs = cowboy_req:parse_qs(Req),
+ case lists:keyfind(Param, 1, Qs) of
+ {_, Value} ->
+ {ok, Value};
+ _ ->
+ {error, {missing_param, Param}}
+ end.
+
+render_node([#{<<"attributes">> := Attrs = #{<<"name">> := AttrName, <<"type">> := AttrType}, <<"type">> := <<"input">>, <<"messages">> := Msgs, <<"meta">> := Meta} | Rest], Acc) ->
+ Assigns0 = [{"input_name", AttrName},
+ {"input_type", AttrType},
+ {"input_value", maps:get(<<"value">>, Attrs, undefined)},
+ {"input_required", maps:get(<<"required">>, Attrs, false)},
+ {"input_disabled", maps:get(<<"disabled">>, Attrs, false)}],
+ Assigns1 = case maps:get(<<"label">>, Meta, false) of
+ #{<<"text">> := Text, <<"type">> := LType} -> [{"label", Text}, {"label_type", LType} | Assigns0];
+ _ -> Assigns0
+ end,
+ Assigns2 = case AttrName of
+ <<"traits.email">> -> [{"autocomplete", "email"} | Assigns1];
+ <<"password">> -> [{"autocomplete", "password"} | Assigns1];
+ _ -> Assigns1
+ end,
+ Assigns = Assigns2,
+ {ok, Html} = case AttrType of
+ <<"submit">> -> form_submit_dtl:render(Assigns);
+ _ -> form_input_dtl:render(Assigns)
+ end,
+ render_node(Rest, Acc ++ Html);
+render_node([], Acc) ->
+ Acc.
diff --git a/apps/styx_web/src/styx_web_app.erl b/apps/styx_web/src/styx_web_app.erl
new file mode 100644
index 0000000..1386e71
--- /dev/null
+++ b/apps/styx_web/src/styx_web_app.erl
@@ -0,0 +1,53 @@
+%%%-------------------------------------------------------------------
+%% @doc styx_web public API
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(styx_web_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+start(_StartType, _StartArgs) ->
+ Port = 5000,
+ CowboyOpts = [{port, Port}],
+ CowboyEnv = #{env => #{
+ dispatch => routes()
+ }},
+ {ok, _} = cowboy:start_clear(styx_web_listener, CowboyOpts, CowboyEnv),
+ logger:notice("listening on ~p", [Port]),
+ styx_web_sup:start_link().
+
+stop(_State) ->
+ ok.
+
+%% internal functions
+
+routes() ->
+ Trails = [
+ %% App
+ {"/", styx_web_index, undefined},
+ {"/launchpad", styx_web_launchpad, undefined},
+
+ %% Kratos
+ {"/login", styx_web_kratos_flow, #{page_title => "Login", template => login_dtl, getflowmf => {ory_kratos, login_flow}, initflowmf => {ory_kratos, login_url}}},
+ {"/register", styx_web_kratos_flow, #{page_title => "Register", template => registration_dtl, getflowmf => {ory_kratos, registration_flow}, initflowmf => {ory_kratos, registration_url}}},
+ {"/account", styx_web_kratos_flow, #{page_title => "Account", template => account_dtl, getflowmf => {ory_kratos, settings_flow}, initflowmf => {ory_kratos, settings_url}}},
+ {"/account/recovery", styx_web_kratos_flow, #{page_title => "Recover Account", template => recovery_dtl, getflowmf => {ory_kratos, recovery_flow}, initflowmf => {ory_kratos, recovery_url}}},
+ {"/account/verification", styx_web_kratos_flow, #{page_title => "Verify Account", template => verification_dtl, getflowmf => {ory_kratos, verification_flow}, initflowmf => {ory_kratos, verification_url}}},
+ {"/account/error", styx_web_error, undefined},
+ {"/account/logout", styx_web_logout, undefined},
+
+ %% Hydra
+ {"/account/oauth2/login", styx_web_oauth2_login, undefined},
+ {"/account/oauth2/consent", styx_web_oauth2_consent, undefined},
+
+ %% Static
+ {"/account/app.css", cowboy_static, {priv_file, styx_web, "assets/app.css"}},
+ {"/account/app.js", cowboy_static, {priv_file, styx_web, "assets/app.js"}},
+
+ %% 404 Catch all
+ {'_', styx_web_error, #{code => 404, status => <<"Not found">>}}
+ ],
+ trails:single_host_compile(Trails).
diff --git a/apps/styx_web/src/styx_web_error.erl b/apps/styx_web/src/styx_web_error.erl
new file mode 100644
index 0000000..8b40371
--- /dev/null
+++ b/apps/styx_web/src/styx_web_error.erl
@@ -0,0 +1,17 @@
+-module(styx_web_error).
+-behaviour(cowboy_handler).
+-export([init/2]).
+
+init(Req, State = #{code := Code, status := Status}) ->
+ reply(Req, Code, Status, maps:get(message, State, undefined));
+init(Req = #{method := <<"GET">>}, State) ->
+ {ok, ErrorId} = styx_web:req_param(Req, <<"id">>),
+ {ok, Error} = ory_kratos:error(ErrorId),
+ {ok, #{<<"error">> := #{<<"status">> := Status, <<"code">> := Code, <<"message">> := Msg}}} = ory_kratos:error(ErrorId),
+ reply(Req, Code, Status, Msg).
+
+reply(Req0, Code, Status, Msg) ->
+ Assigns = [{"message", Msg}, {"status", Status}],
+ Html = styx_web:render(Req0, error_dtl, Assigns),
+ Req = styx_web:reply_html(Req0, Code, Html),
+ {ok, Req, undefined}.
diff --git a/apps/styx_web/src/styx_web_index.erl b/apps/styx_web/src/styx_web_index.erl
new file mode 100644
index 0000000..377a5de
--- /dev/null
+++ b/apps/styx_web/src/styx_web_index.erl
@@ -0,0 +1,14 @@
+-module(styx_web_index).
+-behaviour(cowboy_handler).
+-export([init/2]).
+
+init(Req0, State) ->
+ Cookie = cowboy_req:header(<<"cookie">>, Req0),
+ Req = case ory_kratos:whoami(Cookie) of
+ {ok, #{<<"active">> := true}} ->
+ styx_web:temporary_redirect(Req0, <<"/launchpad">>);
+ _ ->
+ Html = styx_web:render(Req0, index_dtl, []),
+ styx_web:reply_html(Req0, 200, Html)
+ end,
+ {ok, Req, State}.
diff --git a/apps/styx_web/src/styx_web_kratos_flow.erl b/apps/styx_web/src/styx_web_kratos_flow.erl
new file mode 100644
index 0000000..c889794
--- /dev/null
+++ b/apps/styx_web/src/styx_web_kratos_flow.erl
@@ -0,0 +1,25 @@
+-module(styx_web_kratos_flow).
+-behaviour(cowboy_handler).
+-export([init/2]).
+
+init(Req = #{method := <<"GET">>}, State = #{page_title := _, template := _, getflowmf := _, initflowmf := _}) ->
+ get(Req, State, styx_web:req_param(Req, <<"flow">>));
+init(Req, _) ->
+ styx_web_error:init(Req, #{code => 404, status => <<"Not Found">>}).
+
+get(Req, State = #{getflowmf := {Mod, Fun}}, {ok, FlowId}) ->
+ Cookie = cowboy_req:header(<<"cookie">>, Req),
+ get_(Req, State, Mod:Fun(Cookie, FlowId));
+get(Req0, State = #{initflowmf := {Mod, Fun}}, {error, {missing_param, _}}) ->
+ Req = styx_web:temporary_redirect(Req0, Mod:Fun(browser)),
+ {ok, Req, State}.
+
+get_(Req0, State = #{page_title := PageTitle, template := Template}, {ok, Flow = #{<<"ui">> := UI}}) ->
+ logger:debug("Flow = ~p", [Flow]),
+ FormHtml = styx_web:render_form(UI),
+ Assigns = [{"page_title", PageTitle}, {"form", FormHtml}],
+ Html = styx_web:render(Req0, Template, Assigns),
+ Req = styx_web:reply_html(Req0, 200, Html),
+ {ok, Req, State};
+get_(Req, State, {error, Error = #{<<"code">> := Code, <<"status">> := Status, <<"message">> := Msg}}) ->
+ styx_web_error:init(Req, #{code => Code, status => Status, message => maps:get(<<"reason">>, Error, Msg)}).
diff --git a/apps/styx_web/src/styx_web_launchpad.erl b/apps/styx_web/src/styx_web_launchpad.erl
new file mode 100644
index 0000000..382c1cd
--- /dev/null
+++ b/apps/styx_web/src/styx_web_launchpad.erl
@@ -0,0 +1,16 @@
+-module(styx_web_launchpad).
+-behaviour(cowboy_handler).
+-export([init/2]).
+
+init(Req0, State) ->
+ Cookie = cowboy_req:header(<<"cookie">>, Req0),
+ case ory_kratos:whoami(Cookie) of
+ {ok, Session = #{<<"active">> := true}} ->
+ logger:debug("Session ~p", [Session]),
+ Html = styx_web:render(Req0, launchpad_dtl, [{"session", Session}, {"name", styx_web:identity_name(Session)}]),
+ {ok, styx_web:reply_html(Req0, 200, Html), State};
+ {error, #{<<"code">> := 401}} ->
+ {ok, styx_web:temporary_redirect(Req0, <<"/login">>), State};
+ {error, Error = #{<<"code">> := Code, <<"status">> := Status, <<"message">> := Msg}} ->
+ styx_web_error:init(Req0, #{code => Code, status => Status, message => maps:get(<<"reason">>, Error, Msg)})
+ end.
diff --git a/apps/styx_web/src/styx_web_logout.erl b/apps/styx_web/src/styx_web_logout.erl
new file mode 100644
index 0000000..80ef57e
--- /dev/null
+++ b/apps/styx_web/src/styx_web_logout.erl
@@ -0,0 +1,13 @@
+-module(styx_web_logout).
+-behaviour(cowboy_handler).
+-export([init/2]).
+
+init(Req0, State) ->
+ Cookie = cowboy_req:header(<<"cookie">>, Req0),
+ case ory_kratos:logout_flow(Cookie) of
+ {ok, #{<<"logout_url">> := URL}} ->
+ Req = styx_web:temporary_redirect(Req0, URL),
+ {ok, Req, State};
+ _ ->
+ styx_web_error:init(Req0, #{code => 404, status => <<"Not Found">>})
+ end.
diff --git a/apps/styx_web/src/styx_web_sup.erl b/apps/styx_web/src/styx_web_sup.erl
new file mode 100644
index 0000000..8e861b4
--- /dev/null
+++ b/apps/styx_web/src/styx_web_sup.erl
@@ -0,0 +1,35 @@
+%%%-------------------------------------------------------------------
+%% @doc styx_web top level supervisor.
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(styx_web_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%% sup_flags() = #{strategy => strategy(), % optional
+%% intensity => non_neg_integer(), % optional
+%% period => pos_integer()} % optional
+%% child_spec() = #{id => child_id(), % mandatory
+%% start => mfargs(), % mandatory
+%% restart => restart(), % optional
+%% shutdown => shutdown(), % optional
+%% type => worker(), % optional
+%% modules => modules()} % optional
+init([]) ->
+ SupFlags = #{strategy => one_for_all,
+ intensity => 0,
+ period => 1},
+ ChildSpecs = [],
+ {ok, {SupFlags, ChildSpecs}}.
+
+%% internal functions
diff --git a/apps/styx_web/templates/account.dtl b/apps/styx_web/templates/account.dtl
new file mode 100644
index 0000000..c146b32
--- /dev/null
+++ b/apps/styx_web/templates/account.dtl
@@ -0,0 +1,17 @@
+<div class="header">
+ <h2>
+ Your Account
+ </h2>
+ <p>
+ Or
+ <a href="/account/logout">
+ logout
+ </a>
+ </p>
+</div>
+
+<div class="mt-8">
+
+ <div class="mt-6">{{form | safe}}</div>
+
+</div>
diff --git a/apps/styx_web/templates/error.dtl b/apps/styx_web/templates/error.dtl
new file mode 100644
index 0000000..c0177f2
--- /dev/null
+++ b/apps/styx_web/templates/error.dtl
@@ -0,0 +1,8 @@
+<div class="header">
+ <h2>
+ {{ status }}
+ </h2>
+ <p class="text-error">
+ {{ message }}
+ </p>
+</div>
diff --git a/apps/styx_web/templates/form.dtl b/apps/styx_web/templates/form.dtl
new file mode 100644
index 0000000..87135b5
--- /dev/null
+++ b/apps/styx_web/templates/form.dtl
@@ -0,0 +1,6 @@
+<form action="{{action}}" method="POST" class="space-y-6">
+ <div>{% for m in messages %}
+ <div class="form-message text-{{m.type}}">{{m.text}}</div>
+ {% endfor %}</div>
+ {{inputs | safe }}
+</form> \ No newline at end of file
diff --git a/apps/styx_web/templates/form_input.dtl b/apps/styx_web/templates/form_input.dtl
new file mode 100644
index 0000000..c5b9876
--- /dev/null
+++ b/apps/styx_web/templates/form_input.dtl
@@ -0,0 +1,18 @@
+<div>
+ {% if label %}
+ <label for="{{input_name}}" class="label">
+ {{label}}
+ {% if input_required %}<span class="text-alert">*</span>{% endif %}
+ </label>
+ {% endif %}
+
+ <div class="mt-1">
+ <input name="{{input_name}}"
+ type="{{input_type}}"
+ {% if autocomplete %}autocomplete="{{autocomplete}}"{% endif %}
+ {% if input_value %}value="{{input_value}}"{% endif %}
+ {% if input_required %}required{% endif %}
+ {% if input_disabled %}disabled{% endif %}
+ class="input">
+ </div>
+</div> \ No newline at end of file
diff --git a/apps/styx_web/templates/form_submit.dtl b/apps/styx_web/templates/form_submit.dtl
new file mode 100644
index 0000000..5e3556e
--- /dev/null
+++ b/apps/styx_web/templates/form_submit.dtl
@@ -0,0 +1,3 @@
+<div>
+ <button type="{{input_type}}" class="btn-submit" name="{{input_name}}" value="{{input_value}}">{{label}}</button>
+</div>
diff --git a/apps/styx_web/templates/index.dtl b/apps/styx_web/templates/index.dtl
new file mode 100644
index 0000000..ab050ed
--- /dev/null
+++ b/apps/styx_web/templates/index.dtl
@@ -0,0 +1,4 @@
+<div class="mt-8">
+ <a href="/login" class="btn-submit">Login</a>
+ <a href="/register" class="btn-submit mt-2">Register</a>
+</div> \ No newline at end of file
diff --git a/apps/styx_web/templates/launchpad.dtl b/apps/styx_web/templates/launchpad.dtl
new file mode 100644
index 0000000..588ad48
--- /dev/null
+++ b/apps/styx_web/templates/launchpad.dtl
@@ -0,0 +1,10 @@
+<div class="header">
+ <h2>
+ {{name}}
+ </h2>
+</div>
+
+<div class="mt-8">
+ <a href="/account" class="btn-submit">Edit account</a>
+ <a href="/account/logout" class="btn-submit mt-2">Logout</a>
+</div>
diff --git a/apps/styx_web/templates/layout.dtl b/apps/styx_web/templates/layout.dtl
new file mode 100644
index 0000000..f648f09
--- /dev/null
+++ b/apps/styx_web/templates/layout.dtl
@@ -0,0 +1,21 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>{% if page_title %}{{ page_title }} - {% endif %}{{site_title}}</title>
+ <link rel="stylesheet" href="/account/app.css">
+</head>
+<body>
+ <div class="root">
+ <div class="main">
+ <div class="container">
+ <h1><a href="/">{{site_title}}</a></h1>
+ {{ inner | safe }}
+ </div>
+ </div>
+ <div class="background">
+ <img src="{{background_image_url}}" alt="">
+ </div>
+ </div>
+</body>
+</html>
diff --git a/apps/styx_web/templates/login.dtl b/apps/styx_web/templates/login.dtl
new file mode 100644
index 0000000..9f0f9e9
--- /dev/null
+++ b/apps/styx_web/templates/login.dtl
@@ -0,0 +1,21 @@
+<div class="header">
+ <h2>
+ Sign in
+ </h2>
+ <p>
+ Or
+ <a href="/register">
+ register an account
+ </a>
+ </p>
+</div>
+
+<div class="mt-8">
+
+ <div class="mt-6">{{form | safe}}</div>
+
+ <p class="mt-4 text-sm">
+ <a href="/account/recovery">Forgot your password ?</a>
+ </p>
+
+</div>
diff --git a/apps/styx_web/templates/recovery.dtl b/apps/styx_web/templates/recovery.dtl
new file mode 100644
index 0000000..9287aac
--- /dev/null
+++ b/apps/styx_web/templates/recovery.dtl
@@ -0,0 +1,17 @@
+<div class="header">
+ <h2>
+ Recover Account
+ </h2>
+ <p>
+ Or
+ <a href="/login">
+ login
+ </a>
+ </p>
+</div>
+
+<div class="mt-8">
+
+ <div class="mt-6">{{form | safe}}</div>
+
+</div>
diff --git a/apps/styx_web/templates/registration.dtl b/apps/styx_web/templates/registration.dtl
new file mode 100644
index 0000000..df564e3
--- /dev/null
+++ b/apps/styx_web/templates/registration.dtl
@@ -0,0 +1,17 @@
+<div class="header">
+ <h2>
+ Register an account
+ </h2>
+ <p>
+ Or
+ <a href="/login">
+ sign in
+ </a>
+ </p>
+</div>
+
+<div class="mt-8">
+
+ <div class="mt-6">{{form | safe}}</div>
+
+</div>
diff --git a/apps/styx_web/templates/verification.dtl b/apps/styx_web/templates/verification.dtl
new file mode 100644
index 0000000..d63d457
--- /dev/null
+++ b/apps/styx_web/templates/verification.dtl
@@ -0,0 +1,11 @@
+<div class="header">
+ <h2>
+ Verify Account
+ </h2>
+</div>
+
+<div class="mt-8">
+
+ <div class="mt-6">{{form | safe}}</div>
+
+</div>