initial release

This commit is contained in:
Andreas Knuth 2024-02-29 15:32:49 -06:00
commit 566453191d
116 changed files with 8575 additions and 0 deletions

11
.editorconfig Normal file
View File

@ -0,0 +1,11 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules/
# misc
.DS_Store
dist

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
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
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,8 @@
{
"themes": [
{
"name": "keywind",
"types": ["login"]
}
]
}

114
README.md Normal file
View File

@ -0,0 +1,114 @@
# :wind_face: Keywind
Keywind is a component-based Keycloak Login Theme built with [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) and [Alpine.js](https://github.com/alpinejs/alpine).
![Preview](./preview.png)
### Styled Pages
- Error
- Login
- Login Config TOTP
- Login IDP Link Confirm
- Login OAuth Grant
- Login OTP
- Login Page Expired
- Login Password
- Login Recovery Authn Code Config
- Login Recovery Authn Code Input
- Login Reset Password
- Login Update Password
- Login Update Profile
- Login Username
- Login X.509 Info
- Logout Confirm
- Register
- Select Authenticator
- Terms and Conditions
- WebAuthn Authenticate
- WebAuthn Error
- WebAuthn Register
### Identity Provider Icons
- Apple
- Bitbucket
- Discord
- Facebook
- GitHub
- GitLab
- Google
- Instagram
- LinkedIn
- Microsoft
- OpenID
- Red Hat OpenShift
- PayPal
- Slack
- Stack Overflow
- Twitter
## Installation
Keywind has been designed with component-based architecture from the start, and **you can customize as little or as much Keywind as you need**:
1. [Deploy Keywind Login Theme](https://www.keycloak.org/docs/latest/server_development/#deploying-themes)
2. [Create your own Login Theme](https://www.keycloak.org/docs/latest/server_development/#creating-a-theme)
3. Specify parent theme in [theme properties](https://www.keycloak.org/docs/latest/server_development/#theme-properties):
```
parent=keywind
```
4. Brand and customize components with [FreeMarker](https://freemarker.apache.org/docs/dgui_quickstart_template.html)
## Customization
### Theme
When you do need to customize a palette, you can configure your colors under the `colors` key in the `theme` section of Tailwind config file:
`tailwind.config.js`
```js
module.exports = {
theme: {
extend: {
colors: {
primary: colors.red,
},
},
},
};
```
Read more about Tailwind CSS configuration in the [documentation](https://tailwindcss.com/docs/configuration).
### Components
You can update Keywind components in your own child theme. For example, create a copy of the `body` component and change the background:
`theme/mytheme/login/components/atoms/body.ftl`
```
<#macro kw>
<body class="bg-primary-100">
<#nested>
</body>
</#macro>
```
## Build
When you're ready to deploy your own theme, run the build command to generate a static production build.
```bash
pnpm install
pnpm build
```
To deploy a theme as an archive, create a JAR archive with the theme resources.
```bash
pnpm build:jar
```

BIN
dump.rdb Normal file

Binary file not shown.

49
html/login/error.html Normal file
View File

@ -0,0 +1,49 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">We are sorry...</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,76 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Mobile Authenticator Setup</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<ol class="list-decimal pl-4 space-y-2">
<li class="space-y-2">
<p>Install one of the following applications on your mobile:</p>
<ul class="list-disc pl-4">
<li>FreeOTP</li>
</ul></li>
<li>
<p>Open the application and scan the barcode:</p><img alt="Figure: Barcode" class="mx-auto" src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABgAAAAYADwa0LPAAAF70lEQVR42u3d0W3DOBQAwfhwPcj9V2e4CV8D96EgBPVWnilAphRlwY8H6vH5fD4/AAH/XL0AgLMEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIx/r17A/zmO4+f9fl+9jEt8Pp8l13k8HrnfOnOdM1bdl/dwHjssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIGDk4esbr9fo5juPqZfzKqkHEaUOhO6+zagB11fP55vfwCnZYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQkR0cPWPVsOIZ005o3Hnvq+w8lXSnb34PV7PDAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CAjFsPjt7VtAHLaaeb3n148pvZYQEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIbB0aCdn2Ivrrn4fDjHDgvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjJuPTj6zcOBq4YnV13nm4dCp62nzA4LyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIysoOjz+fz6iWMtnMo9K7XOcN7uJcdFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZDw+jkPMOTMYeca0U0CLp4mylx0WkCFYQIZgARmCBWQIFpAhWECGYAEZggVkjDxxdOdg5M5hxbsOfBaf86o1r7JzPeXhWzssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIGDk4esaq4bedQ4/ThhXPcAro3+992iB0mR0WkCFYQIZgARmCBWQIFpAhWECGYAEZggVkZD9Vbxhvj2mvh9NE//5bZXZYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQkR0cPXVzwWG84pqn3dddh0tXKZ8ia4cFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ2U/VTxtE/GbThl13XmfnEKb30A4LCBEsIEOwgAzBAjIEC8gQLCBDsIAMwQIysoOjd/3896o1TxsynDagu3M9O4dLpz3n1eywgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+Tg6KpBu50nPU4b2CsOu057hjvXXHzHrmCHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGY/PzgnDlQsfNmg3bUh1528Vn+FO0X+xkeywgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgI3vi6CrTPlm+8zqr7n3aqZvTTLuvaev5DTssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIyJ44+s12DnMWBxrvel/YYQEhggVkCBaQIVhAhmABGYIFZAgWkCFYQMbIE0eP4/h5v99XL+MSd/1c+86h0Glr3vlbO0+jvYIdFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZIwcHD3j9Xr9HMdx9TJ+ZdVA7DeflvnNw5PThoGvYIcFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ2cHRM3YO2hWHDIunZd71RNZvHoj9DTssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIuPXg6F1N+6z5zjWvUhxALQ98rmKHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGQZHg+46QLjzRM1pA5/T7n3qO2aHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGbceHJ06/PZX04YMV5n29yquZ+ff6wp2WECGYAEZggVkCBaQIVhAhmABGYIFZAgWkJEdHH0+n1cvYbRpp1yeUT4J86/3xTl2WECGYAEZggVkCBaQIVhAhmABGYIFZAgWkPH4FCfxgK9khwVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVk/AeoXLE8BnySdAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wOS0yNVQyMDoxMjo1OSswMDowMLyvm1kAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDktMjVUMjA6MTI6NTkrMDA6MDDN8iPlAAAAAElFTkSuQmCC"> <a class="text-primary-600 hover:text-primary-500 inline-flex" href="manualUrl"> Unable to scan? </a></li>
<li>Enter the one-time code provided by the application and click Submit to finish the setup.</li>
<li>Provide a Device Name to help you manage your OTP devices.</li>
</ol>
<form class="m-0 space-y-4" method="post" action="loginAction">
<input name="totpSecret" type="hidden" value="totpSecret">
<div>
<label class="sr-only" for="totp"> One-time code * </label> <input autofocus aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="totp" name="totp" placeholder="One-time code *" type="text" autocomplete="off">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="userLabel"> Device Name * </label> <input aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="userLabel" name="userLabel" placeholder="Device Name *" type="text" autocomplete="off">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Submit </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,52 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Account already exists</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="submitAction" type="submit" value="updateProfile"> Review profile </button> <button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="submitAction" type="submit" value="linkAccount"> Add to existing account </button>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,59 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">
<p>Grant Access to null</p></h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<h3>Do you grant these access privileges?</h3>
<ul class="list-disc pl-4">
</ul>
<form class="m-0 space-y-4" method="post" action="oauthAction">
<input name="code" type="hidden" value="code">
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="accept" type="submit"> Yes </button> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="cancel" type="submit"> No </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

59
html/login/login-otp.html Normal file
View File

@ -0,0 +1,59 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Sign In</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<div>
<label class="sr-only" for="otp"> One-time code * </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="otp" name="otp" placeholder="One-time code *" type="text" autocomplete="off">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="submitAction" type="submit"> Sign In </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,52 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Page has expired</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div class="flex flex-col pt-4 space-y-2">
<a class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" href="loginRestartFlowUrl"> Try again </a> <a class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" href="loginAction"> Continue </a>
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,75 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Sign In</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction" onsubmit="login.disabled = true; return true;">
<div>
<label class="sr-only" for="password"> Password </label>
<div class="relative" x-data="{ show: false }">
<input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="password" name="password" placeholder="Password" :type="show ? 'text' : 'password'"> <button @click="show = !show" aria-controls="password" :aria-expanded="show" class="absolute text-secondary-400 right-3 top-3 sm:top-2" type="button">
<div x-show="!show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C11.3807 12.5 12.5 11.3807 12.5 10C12.5 8.61929 11.3807 7.5 10 7.5C8.61929 7.5 7.5 8.61929 7.5 10C7.5 11.3807 8.61929 12.5 10 12.5Z" /> <path clip-rule="evenodd" d="M0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C2.10878 5.65788 5.7433 3 9.99859 3C14.256 3 17.892 5.66051 19.3347 9.40962C19.4816 9.79127 19.4814 10.2144 19.3344 10.5959C17.8902 14.3421 14.2557 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904ZM14.0004 10C14.0004 12.2091 12.2095 14 10.0004 14C7.79123 14 6.00037 12.2091 6.00037 10C6.00037 7.79086 7.79123 6 10.0004 6C12.2095 6 14.0004 7.79086 14.0004 10Z" fill-rule="evenodd" />
</svg>
</div>
<div x-cloak x-show="show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L16.7197 17.7803C17.0126 18.0732 17.4874 18.0732 17.7803 17.7803C18.0732 17.4874 18.0732 17.0126 17.7803 16.7197L16.0352 14.9745C17.5064 13.8594 18.6595 12.3465 19.3344 10.5959C19.4814 10.2144 19.4816 9.79127 19.3347 9.40962C17.892 5.66051 14.256 3 9.99859 3C8.28207 3 6.66657 3.43249 5.2551 4.19444L3.28033 2.21967ZM7.75194 6.69128L8.84367 7.78301C9.18951 7.60223 9.58291 7.5 10.0002 7.5C11.3809 7.5 12.5002 8.61929 12.5002 10C12.5002 10.4173 12.398 10.8107 12.2172 11.1565L13.3091 12.2484C13.7454 11.6077 14.0004 10.8336 14.0004 10C14.0004 7.79086 12.2095 6 10.0004 6C9.16675 6 8.39268 6.25501 7.75194 6.69128Z" fill-rule="evenodd" /> <path d="M10.7484 13.9302L13.2711 16.4529C12.2462 16.8074 11.1458 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C1.15603 8.12932 1.90108 6.98057 2.83791 6.01969L6.0702 9.25198C6.02436 9.4943 6.00037 9.74435 6.00037 10C6.00037 12.2091 7.79123 14 10.0004 14C10.256 14 10.5061 13.976 10.7484 13.9302Z" />
</svg>
</div></button>
</div>
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex items-center justify-between">
<a class="text-primary-600 hover:text-primary-500 text-sm inline-flex" href="loginResetCredentialsUrl"> Forgot Password? </a>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="login" type="submit"> Sign In </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,84 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/dist/recoveryCodes.js" type="module"></script>
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Recovery Authentication Codes</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div class="space-y-6" x-data="recoveryCodes">
<div class="bg-orange-100 text-orange-600 p-4 rounded-lg text-sm" role="alert">
<div class="space-y-2">
<h4 class="font-medium">These recovery codes wont appear again after leaving this page</h4>
<p>Make sure to print, download, or copy them to a password manager and keep them save. Canceling this setup will remove these recovery codes from your account.</p>
</div>
</div>
<ul class="columns-2 font-mono text-center" x-ref="codeList">
<li>0000-0000-0000</li>
<li>1111-1111-1111</li>
</ul>
<div class="flex items-stretch space-x-4 mb-4">
<button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-2 py-1 text-xs flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" @click="print" type="button"> Print </button> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-2 py-1 text-xs flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" @click="download" type="button"> Download </button> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-2 py-1 text-xs flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" @click="copy" type="button"> Copy </button>
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<input name="generatedRecoveryAuthnCodes" type="hidden" value="generatedRecoveryAuthnCodesAsString"> <input name="generatedAt" type="hidden" value="&quot;generatedAt&quot;"> <input name="userLabel" type="hidden" value="Recovery codes">
<div class="flex items-center">
<input class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50" id="kcRecoveryCodesConfirmationCheck" name="kcRecoveryCodesConfirmationCheck" type="checkbox" required x-ref="confirmationCheck"> <label class="ml-2 text-secondary-600 text-sm" for="kcRecoveryCodesConfirmationCheck"> I have saved these codes somewhere safe </label>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Complete setup </button>
</div>
</form>
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('recoveryCodes', {
downloadFileDate: 'These codes were generated on',
downloadFileDescription: 'Recovery codes are single-use passcodes that allow you to sign in to your account if you do not have access to your authenticator.',
downloadFileHeader: 'Keep these recovery codes somewhere safe.',
downloadFileName: 'kc-download-recovery-codes',
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,59 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Login with a recovery authentication code</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<div>
<label class="sr-only" for="recoveryCodeInput"> Recovery code #"codeNumber" </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="recoveryCodeInput" name="recoveryCodeInput" placeholder="Recovery code #&quot;codeNumber&quot;" type="text" autocomplete="off">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="login" type="submit"> Sign In </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,63 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Forgot Your Password?</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<div>
<label class="sr-only" for="username"> Email </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="username" name="username" placeholder="Email" type="text" autocomplete="email" value="Attempted Username">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Submit </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
<div class="space-y-4">
Enter your username and we will send you instructions on how to create a new password.
</div>
</div>
<div class="flex justify-around">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="loginUrl"> « Back to Login </a>
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,91 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Update password</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<input autocomplete="username" name="username" type="hidden" value="Username"> <input autocomplete="current-password" name="password" type="hidden">
<div>
<label class="sr-only" for="password-new"> New Password </label>
<div class="relative" x-data="{ show: false }">
<input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="password-new" name="password-new" placeholder="New Password" :type="show ? 'text' : 'password'" autocomplete="new-password"> <button @click="show = !show" aria-controls="password-new" :aria-expanded="show" class="absolute text-secondary-400 right-3 top-3 sm:top-2" type="button">
<div x-show="!show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C11.3807 12.5 12.5 11.3807 12.5 10C12.5 8.61929 11.3807 7.5 10 7.5C8.61929 7.5 7.5 8.61929 7.5 10C7.5 11.3807 8.61929 12.5 10 12.5Z" /> <path clip-rule="evenodd" d="M0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C2.10878 5.65788 5.7433 3 9.99859 3C14.256 3 17.892 5.66051 19.3347 9.40962C19.4816 9.79127 19.4814 10.2144 19.3344 10.5959C17.8902 14.3421 14.2557 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904ZM14.0004 10C14.0004 12.2091 12.2095 14 10.0004 14C7.79123 14 6.00037 12.2091 6.00037 10C6.00037 7.79086 7.79123 6 10.0004 6C12.2095 6 14.0004 7.79086 14.0004 10Z" fill-rule="evenodd" />
</svg>
</div>
<div x-cloak x-show="show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L16.7197 17.7803C17.0126 18.0732 17.4874 18.0732 17.7803 17.7803C18.0732 17.4874 18.0732 17.0126 17.7803 16.7197L16.0352 14.9745C17.5064 13.8594 18.6595 12.3465 19.3344 10.5959C19.4814 10.2144 19.4816 9.79127 19.3347 9.40962C17.892 5.66051 14.256 3 9.99859 3C8.28207 3 6.66657 3.43249 5.2551 4.19444L3.28033 2.21967ZM7.75194 6.69128L8.84367 7.78301C9.18951 7.60223 9.58291 7.5 10.0002 7.5C11.3809 7.5 12.5002 8.61929 12.5002 10C12.5002 10.4173 12.398 10.8107 12.2172 11.1565L13.3091 12.2484C13.7454 11.6077 14.0004 10.8336 14.0004 10C14.0004 7.79086 12.2095 6 10.0004 6C9.16675 6 8.39268 6.25501 7.75194 6.69128Z" fill-rule="evenodd" /> <path d="M10.7484 13.9302L13.2711 16.4529C12.2462 16.8074 11.1458 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C1.15603 8.12932 1.90108 6.98057 2.83791 6.01969L6.0702 9.25198C6.02436 9.4943 6.00037 9.74435 6.00037 10C6.00037 12.2091 7.79123 14 10.0004 14C10.256 14 10.5061 13.976 10.7484 13.9302Z" />
</svg>
</div></button>
</div>
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="password-confirm"> Confirm password </label>
<div class="relative" x-data="{ show: false }">
<input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="password-confirm" name="password-confirm" placeholder="Confirm password" :type="show ? 'text' : 'password'" autocomplete="new-password"> <button @click="show = !show" aria-controls="password-confirm" :aria-expanded="show" class="absolute text-secondary-400 right-3 top-3 sm:top-2" type="button">
<div x-show="!show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C11.3807 12.5 12.5 11.3807 12.5 10C12.5 8.61929 11.3807 7.5 10 7.5C8.61929 7.5 7.5 8.61929 7.5 10C7.5 11.3807 8.61929 12.5 10 12.5Z" /> <path clip-rule="evenodd" d="M0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C2.10878 5.65788 5.7433 3 9.99859 3C14.256 3 17.892 5.66051 19.3347 9.40962C19.4816 9.79127 19.4814 10.2144 19.3344 10.5959C17.8902 14.3421 14.2557 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904ZM14.0004 10C14.0004 12.2091 12.2095 14 10.0004 14C7.79123 14 6.00037 12.2091 6.00037 10C6.00037 7.79086 7.79123 6 10.0004 6C12.2095 6 14.0004 7.79086 14.0004 10Z" fill-rule="evenodd" />
</svg>
</div>
<div x-cloak x-show="show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L16.7197 17.7803C17.0126 18.0732 17.4874 18.0732 17.7803 17.7803C18.0732 17.4874 18.0732 17.0126 17.7803 16.7197L16.0352 14.9745C17.5064 13.8594 18.6595 12.3465 19.3344 10.5959C19.4814 10.2144 19.4816 9.79127 19.3347 9.40962C17.892 5.66051 14.256 3 9.99859 3C8.28207 3 6.66657 3.43249 5.2551 4.19444L3.28033 2.21967ZM7.75194 6.69128L8.84367 7.78301C9.18951 7.60223 9.58291 7.5 10.0002 7.5C11.3809 7.5 12.5002 8.61929 12.5002 10C12.5002 10.4173 12.398 10.8107 12.2172 11.1565L13.3091 12.2484C13.7454 11.6077 14.0004 10.8336 14.0004 10C14.0004 7.79086 12.2095 6 10.0004 6C9.16675 6 8.39268 6.25501 7.75194 6.69128Z" fill-rule="evenodd" /> <path d="M10.7484 13.9302L13.2711 16.4529C12.2462 16.8074 11.1458 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C1.15603 8.12932 1.90108 6.98057 2.83791 6.01969L6.0702 9.25198C6.02436 9.4943 6.00037 9.74435 6.00037 10C6.00037 12.2091 7.79123 14 10.0004 14C10.256 14 10.5061 13.976 10.7484 13.9302Z" />
</svg>
</div></button>
</div>
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Submit </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,74 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Update Account Information</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<div>
<label class="sr-only" for="username"> Username </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="username" name="username" placeholder="Username" type="text" autocomplete="username" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="email"> Email </label> <input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="email" name="email" placeholder="Email" type="email" autocomplete="email" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="firstName"> First name </label> <input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="firstName" name="firstName" placeholder="First name" type="text" autocomplete="given-name" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="lastName"> Last name </label> <input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="lastName" name="lastName" placeholder="Last name" type="text" autocomplete="family-name" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Submit </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,90 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Sign in to your account</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction" onsubmit="login.disabled = true; return true;">
<div>
<label class="sr-only" for="username"> Email </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="username" name="username" placeholder="Email" type="text" autocomplete="email" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input checked class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50" id="rememberMe" name="rememberMe" type="checkbox"> <label class="ml-2 text-secondary-600 text-sm" for="rememberMe"> Remember me </label>
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="login" type="submit"> Sign In </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
<div class="pt-4 separate text-secondary-600 text-sm">
Or sign in with
</div>
<div class="gap-4 grid grid-cols-3">
<a class="hover:bg-provider-facebook/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="facebook" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Facebook</title><path d="M24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 17.9895 4.38822 22.954 10.125 23.8542V15.4688H7.07813V12H10.125V9.35626C10.125 6.34875 11.9165 4.68751 14.6576 4.68751C15.9705 4.68751 17.3438 4.92188 17.3438 4.92188V7.875H15.8306C14.3399 7.875 13.875 8.80002 13.875 9.74901V12H17.2031L16.6711 15.4688H13.875V23.8542C19.6118 22.954 24 17.9895 24 12Z" fill="#1877F2" /> <path d="M16.6711 15.4688L17.2031 12H13.875V9.74901C13.875 8.80002 14.3399 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9705 4.68751 14.6576 4.68751C11.9165 4.68751 10.125 6.34875 10.125 9.35626V12H7.07813V15.4688H10.125V23.8542C10.7453 23.9514 11.3722 24.0002 12 24C12.6379 24 13.2641 23.9501 13.875 23.8542V15.4688H16.6711Z" fill="white" />
</svg>
</div></a> <a class="hover:bg-provider-github/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="github" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>GitHub</title><path d="M11.8452 0C5.13387 0 0 5.09516 0 11.8065C0 17.1726 3.37742 21.7645 8.20161 23.3806C8.82097 23.4919 9.03871 23.1097 9.03871 22.7952C9.03871 22.4952 9.02419 20.8403 9.02419 19.8242C9.02419 19.8242 5.6371 20.55 4.92581 18.3823C4.92581 18.3823 4.37419 16.9742 3.58065 16.6113C3.58065 16.6113 2.47258 15.8516 3.65806 15.8661C3.65806 15.8661 4.8629 15.9629 5.52581 17.1145C6.58548 18.9823 8.36129 18.4452 9.05323 18.1258C9.16452 17.3516 9.47903 16.8145 9.82742 16.4952C7.12258 16.1952 4.39355 15.8032 4.39355 11.1484C4.39355 9.81774 4.76129 9.15 5.53548 8.29839C5.40968 7.98387 4.99839 6.6871 5.66129 5.0129C6.67258 4.69839 9 6.31936 9 6.31936C9.96774 6.04839 11.0081 5.90806 12.0387 5.90806C13.0694 5.90806 14.1097 6.04839 15.0774 6.31936C15.0774 6.31936 17.4048 4.69355 18.4161 5.0129C19.079 6.69194 18.6677 7.98387 18.5419 8.29839C19.3161 9.15484 19.7903 9.82258 19.7903 11.1484C19.7903 15.8177 16.9403 16.1903 14.2355 16.4952C14.6806 16.8774 15.0581 17.6032 15.0581 18.7403C15.0581 20.371 15.0435 22.3887 15.0435 22.7855C15.0435 23.1 15.2661 23.4823 15.8806 23.371C20.7194 21.7645 24 17.1726 24 11.8065C24 5.09516 18.5565 0 11.8452 0Z" fill="#181717" />
</svg>
</div></a> <a class="hover:bg-provider-google/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="google" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Google</title><path d="M23.76 12.2727C23.76 11.4218 23.6836 10.6036 23.5418 9.81818H12.24V14.46H18.6982C18.42 15.96 17.5745 17.2309 16.3036 18.0818L18.2427 19.5873L20.1818 21.0927C22.4509 19.0036 23.76 15.9273 23.76 12.2727Z" fill="#4285F4" /> <path d="M12.24 24C15.48 24 18.1964 22.9255 20.1818 21.0927L16.3036 18.0818C15.2291 18.8018 13.8545 19.2273 12.24 19.2273C9.11455 19.2273 6.46909 17.1164 5.52545 14.28L3.52091 15.8345L1.51636 17.3891C3.49091 21.3109 7.54909 24 12.24 24Z" fill="#34A853" /> <path d="M5.52545 14.28C5.28545 13.56 5.14909 12.7909 5.14909 12C5.14909 11.2091 5.28545 10.44 5.52545 9.72L3.52091 8.16546L1.51636 6.61091C0.703637 8.23091 0.240001 10.0636 0.240001 12C0.240001 13.9364 0.703637 15.7691 1.51636 17.3891L5.52545 14.28Z" fill="#FBBC05" /> <path d="M12.24 4.77273C14.0018 4.77273 15.5836 5.37818 16.8273 6.56727L20.2691 3.12545C18.1909 1.18909 15.4745 0 12.24 0C7.54909 0 3.49091 2.68909 1.51636 6.61091L5.52545 9.72C6.46909 6.88364 9.11455 4.77273 12.24 4.77273Z" fill="#EA4335" />
</svg>
</div></a>
</div>
</div>
<div class="space-y-4">
<div class="text-center">
New user? <a class="text-primary-600 hover:text-primary-500 inline-flex" href="registrationUrl"> Register </a>
</div>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,65 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Sign In</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div>
<div>
X509 client certificate:
</div>
<div class="text-secondary-600">
CN=User, C=US, O=Keywind
</div>
</div>
<div>
<span>You will be logged in as:</span> <b>Username</b>
</div>
<form class="m-0 space-y-4" method="post" action="loginAction">
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="login" type="submit"> Continue </button> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="cancel" type="submit"> Ignore </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

109
html/login/login.html Normal file
View File

@ -0,0 +1,109 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Sign in to your account</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="loginAction" onsubmit="login.disabled = true; return true;">
<input name="credentialId" type="hidden" value="">
<div>
<label class="sr-only" for="username"> Email </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="username" name="username" placeholder="Email" type="text" autocomplete="email" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="password"> Password </label>
<div class="relative" x-data="{ show: false }">
<input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="password" name="password" placeholder="Password" :type="show ? 'text' : 'password'"> <button @click="show = !show" aria-controls="password" :aria-expanded="show" class="absolute text-secondary-400 right-3 top-3 sm:top-2" type="button">
<div x-show="!show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C11.3807 12.5 12.5 11.3807 12.5 10C12.5 8.61929 11.3807 7.5 10 7.5C8.61929 7.5 7.5 8.61929 7.5 10C7.5 11.3807 8.61929 12.5 10 12.5Z" /> <path clip-rule="evenodd" d="M0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C2.10878 5.65788 5.7433 3 9.99859 3C14.256 3 17.892 5.66051 19.3347 9.40962C19.4816 9.79127 19.4814 10.2144 19.3344 10.5959C17.8902 14.3421 14.2557 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904ZM14.0004 10C14.0004 12.2091 12.2095 14 10.0004 14C7.79123 14 6.00037 12.2091 6.00037 10C6.00037 7.79086 7.79123 6 10.0004 6C12.2095 6 14.0004 7.79086 14.0004 10Z" fill-rule="evenodd" />
</svg>
</div>
<div x-cloak x-show="show">
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L16.7197 17.7803C17.0126 18.0732 17.4874 18.0732 17.7803 17.7803C18.0732 17.4874 18.0732 17.0126 17.7803 16.7197L16.0352 14.9745C17.5064 13.8594 18.6595 12.3465 19.3344 10.5959C19.4814 10.2144 19.4816 9.79127 19.3347 9.40962C17.892 5.66051 14.256 3 9.99859 3C8.28207 3 6.66657 3.43249 5.2551 4.19444L3.28033 2.21967ZM7.75194 6.69128L8.84367 7.78301C9.18951 7.60223 9.58291 7.5 10.0002 7.5C11.3809 7.5 12.5002 8.61929 12.5002 10C12.5002 10.4173 12.398 10.8107 12.2172 11.1565L13.3091 12.2484C13.7454 11.6077 14.0004 10.8336 14.0004 10C14.0004 7.79086 12.2095 6 10.0004 6C9.16675 6 8.39268 6.25501 7.75194 6.69128Z" fill-rule="evenodd" /> <path d="M10.7484 13.9302L13.2711 16.4529C12.2462 16.8074 11.1458 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C1.15603 8.12932 1.90108 6.98057 2.83791 6.01969L6.0702 9.25198C6.02436 9.4943 6.00037 9.74435 6.00037 10C6.00037 12.2091 7.79123 14 10.0004 14C10.256 14 10.5061 13.976 10.7484 13.9302Z" />
</svg>
</div></button>
</div>
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input checked class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50" id="rememberMe" name="rememberMe" type="checkbox"> <label class="ml-2 text-secondary-600 text-sm" for="rememberMe"> Remember me </label>
</div><a class="text-primary-600 hover:text-primary-500 text-sm inline-flex" href="loginResetCredentialsUrl"> Forgot Password? </a>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="login" type="submit"> Sign In </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
<div class="pt-4 separate text-secondary-600 text-sm">
Or sign in with
</div>
<div class="gap-4 grid grid-cols-3">
<a class="hover:bg-provider-facebook/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="facebook" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Facebook</title><path d="M24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 17.9895 4.38822 22.954 10.125 23.8542V15.4688H7.07813V12H10.125V9.35626C10.125 6.34875 11.9165 4.68751 14.6576 4.68751C15.9705 4.68751 17.3438 4.92188 17.3438 4.92188V7.875H15.8306C14.3399 7.875 13.875 8.80002 13.875 9.74901V12H17.2031L16.6711 15.4688H13.875V23.8542C19.6118 22.954 24 17.9895 24 12Z" fill="#1877F2" /> <path d="M16.6711 15.4688L17.2031 12H13.875V9.74901C13.875 8.80002 14.3399 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9705 4.68751 14.6576 4.68751C11.9165 4.68751 10.125 6.34875 10.125 9.35626V12H7.07813V15.4688H10.125V23.8542C10.7453 23.9514 11.3722 24.0002 12 24C12.6379 24 13.2641 23.9501 13.875 23.8542V15.4688H16.6711Z" fill="white" />
</svg>
</div></a> <a class="hover:bg-provider-github/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="github" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>GitHub</title><path d="M11.8452 0C5.13387 0 0 5.09516 0 11.8065C0 17.1726 3.37742 21.7645 8.20161 23.3806C8.82097 23.4919 9.03871 23.1097 9.03871 22.7952C9.03871 22.4952 9.02419 20.8403 9.02419 19.8242C9.02419 19.8242 5.6371 20.55 4.92581 18.3823C4.92581 18.3823 4.37419 16.9742 3.58065 16.6113C3.58065 16.6113 2.47258 15.8516 3.65806 15.8661C3.65806 15.8661 4.8629 15.9629 5.52581 17.1145C6.58548 18.9823 8.36129 18.4452 9.05323 18.1258C9.16452 17.3516 9.47903 16.8145 9.82742 16.4952C7.12258 16.1952 4.39355 15.8032 4.39355 11.1484C4.39355 9.81774 4.76129 9.15 5.53548 8.29839C5.40968 7.98387 4.99839 6.6871 5.66129 5.0129C6.67258 4.69839 9 6.31936 9 6.31936C9.96774 6.04839 11.0081 5.90806 12.0387 5.90806C13.0694 5.90806 14.1097 6.04839 15.0774 6.31936C15.0774 6.31936 17.4048 4.69355 18.4161 5.0129C19.079 6.69194 18.6677 7.98387 18.5419 8.29839C19.3161 9.15484 19.7903 9.82258 19.7903 11.1484C19.7903 15.8177 16.9403 16.1903 14.2355 16.4952C14.6806 16.8774 15.0581 17.6032 15.0581 18.7403C15.0581 20.371 15.0435 22.3887 15.0435 22.7855C15.0435 23.1 15.2661 23.4823 15.8806 23.371C20.7194 21.7645 24 17.1726 24 11.8065C24 5.09516 18.5565 0 11.8452 0Z" fill="#181717" />
</svg>
</div></a> <a class="hover:bg-provider-google/10 border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent" data-provider="google" href="loginUrl" type="button">
<div class="h-6 w-6">
<svg viewbox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Google</title><path d="M23.76 12.2727C23.76 11.4218 23.6836 10.6036 23.5418 9.81818H12.24V14.46H18.6982C18.42 15.96 17.5745 17.2309 16.3036 18.0818L18.2427 19.5873L20.1818 21.0927C22.4509 19.0036 23.76 15.9273 23.76 12.2727Z" fill="#4285F4" /> <path d="M12.24 24C15.48 24 18.1964 22.9255 20.1818 21.0927L16.3036 18.0818C15.2291 18.8018 13.8545 19.2273 12.24 19.2273C9.11455 19.2273 6.46909 17.1164 5.52545 14.28L3.52091 15.8345L1.51636 17.3891C3.49091 21.3109 7.54909 24 12.24 24Z" fill="#34A853" /> <path d="M5.52545 14.28C5.28545 13.56 5.14909 12.7909 5.14909 12C5.14909 11.2091 5.28545 10.44 5.52545 9.72L3.52091 8.16546L1.51636 6.61091C0.703637 8.23091 0.240001 10.0636 0.240001 12C0.240001 13.9364 0.703637 15.7691 1.51636 17.3891L5.52545 14.28Z" fill="#FBBC05" /> <path d="M12.24 4.77273C14.0018 4.77273 15.5836 5.37818 16.8273 6.56727L20.2691 3.12545C18.1909 1.18909 15.4745 0 12.24 0C7.54909 0 3.49091 2.68909 1.51636 6.61091L5.52545 9.72C6.46909 6.88364 9.11455 4.77273 12.24 4.77273Z" fill="#EA4335" />
</svg>
</div></a>
</div>
</div>
<div class="space-y-4">
<div class="text-center">
New user? <a class="text-primary-600 hover:text-primary-500 inline-flex" href="registrationUrl"> Register </a>
</div>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,53 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Logging out</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<p>Do you want to log out?</p>
<form class="m-0 space-y-4" method="post" action="logoutConfirmAction">
<input name="session_code" type="hidden" value="code"> <button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" name="confirmLogout" type="submit" value="Logout"> Logout </button>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

70
html/login/register.html Normal file
View File

@ -0,0 +1,70 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Register</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<form class="m-0 space-y-4" method="post" action="registrationAction">
<div>
<label class="sr-only" for="firstName"> First name </label> <input autofocus required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="firstName" name="firstName" placeholder="First name" type="text" autocomplete="given-name" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="lastName"> Last name </label> <input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="lastName" name="lastName" placeholder="Last name" type="text" autocomplete="family-name" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div>
<label class="sr-only" for="email"> Email </label> <input required aria-invalid="false" class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm" id="email" name="email" placeholder="Email" type="email" autocomplete="email" value="">
<div class="mt-2 text-red-600 text-sm">
</div>
</div>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Register </button>
</div>
</form>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="loginUrl"> « Back to Login </a>
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,60 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Select login method</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div x-data>
<form class="m-0 space-y-4" method="post" action="loginAction" x-ref="selectCredentialForm">
<input name="authenticationExecution" type="hidden" x-ref="authExecInput">
<div>
<button class="text-primary-600 hover:text-primary-500 inline-flex" @click="$refs.authExecInput.value = 'authExecId'; $refs.selectCredentialForm.submit()" type="button"> Security Key </button>
<div class="text-sm">
Use your security key to sign in.
</div>
</div>
</form>
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,70 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/dist/webAuthnAuthenticate.js" type="module"></script>
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Security Key login</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div x-data="webAuthnAuthenticate">
<form action="loginAction" method="post" x-ref="webAuthnForm">
<input name="authenticatorData" type="hidden" x-ref="authenticatorDataInput"> <input name="clientDataJSON" type="hidden" x-ref="clientDataJSONInput"> <input name="credentialId" type="hidden" x-ref="credentialIdInput"> <input name="error" type="hidden" x-ref="errorInput"> <input name="signature" type="hidden" x-ref="signatureInput"> <input name="userHandle" type="hidden" x-ref="userHandleInput">
</form>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" @click="webAuthnAuthenticate" type="button"> Sign in with Security Key </button>
</div>
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('webAuthnAuthenticate', {
challenge: 'challenge',
createTimeout: '60000',
isUserIdentified: 'true',
rpId: 'https://webauthn.me',
unsupportedBrowserText: 'WebAuthn is not supported by this browser. Try another one or contact your administrator.',
userVerification: 'preferred',
})
})
</script>
</body>
</html>

View File

@ -0,0 +1,57 @@
<html lang="en">
<head>
<title>Sign in to </title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://localhost:5173/src/index.css" rel="stylesheet">
<script defer src="http://localhost:5173/src/index.ts" type="module"></script>
</head>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<div class="max-w-md space-y-6 w-full">
<div class="bg-white p-8 rounded-lg space-y-6">
<div class="space-y-4">
<div class="font-bold text-center text-2xl">
Keywind
</div>
<h1 class="text-center text-xl">Security Key Error</h1>
</div>
<div class="space-y-4">
<div class="bg-red-100 text-red-600 p-4 rounded-lg text-sm" role="alert">
Example of an error message
</div>
<div x-data>
<form action="loginAction" method="post" x-ref="errorCredentialForm">
<input name="authenticationExecution" type="hidden" x-ref="executionValueInput"> <input name="isSetRetry" type="hidden" x-ref="isSetRetryInput">
</form>
<div class="flex flex-col pt-4 space-y-2">
<button class="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" @click="$refs.executionValueInput.value = 'execution'; $refs.isSetRetryInput.value = 'retry'; $refs.errorCredentialForm.submit()" tabindex="4" name="try-again" type="button"> Try again </button>
</div>
</div>
<form action="loginAction" method="post">
<input name="tryAnotherWay" type="hidden" value="on"> <button class="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900 px-4 py-2 text-sm flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2" type="submit"> Try Another Way </button>
</form>
</div>
</div>
<div class="flex justify-around">
<div class="relative" x-data="{ open: false }">
<button class="text-secondary-600 hover:text-secondary-900 inline-flex" @click="open = true" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">English</span>
<svg class="h-5 w-5" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</div></button>
<div @click.away="open = false" class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg" x-cloak x-show="open">
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Deutsch </a>
</div>
<div class="px-4 py-2">
<a class="text-secondary-600 hover:text-secondary-900 text-sm inline-flex" href="url"> Français </a>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

BIN
out/keywind.jar Normal file

Binary file not shown.

2562
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "keywind",
"scripts": {
"build": "tsc && vite build",
"build:jar": "vite-node scripts/build",
"dev": "vite dev --host",
"test": "mvn test"
},
"dependencies": {
"alpinejs": "^3.13.0",
"rfc4648": "^1.5.2"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.6",
"@types/alpinejs": "^3.13.2",
"@types/archiver": "^5.3.3",
"@types/node": "^20.6.5",
"archiver": "^6.0.1",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.30",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-node": "^0.34.5"
}
}

1261
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

71
pom.xml Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.keywind.theme</groupId>
<artifactId>keywind</artifactId>
<version>1.0-SNAPSHOT</version>
<name>keywind</name>
<url>https://keywind.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>20</maven.compiler.source>
<maven.compiler.target>20</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.16.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>22.0.1</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>22.0.1</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-themes</artifactId>
<version>22.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.3.1</version>
<executions>
<execution>
<id>auto-clean</id>
<phase>test</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

BIN
preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

25
scripts/build.ts Normal file
View File

@ -0,0 +1,25 @@
import archiver from 'archiver';
import { createWriteStream, existsSync, mkdirSync } from 'fs';
import { name } from '../package.json';
const dir = 'out';
const file = `${name}.jar`;
const path = `${dir}/${file}`;
!existsSync(dir) && mkdirSync(dir);
const output = createWriteStream(`${__dirname}/../${path}`);
const archive = archiver('zip');
archive.on('error', (error) => {
throw error;
});
archive.pipe(output);
archive.directory('META-INF', 'META-INF');
archive.directory('theme', 'theme');
archive.finalize();

59
src/data/recoveryCodes.ts Normal file
View File

@ -0,0 +1,59 @@
import Alpine from 'alpinejs';
type DataType = {
$refs: RefsType;
$store: StoreType;
};
type RefsType = {
codeList: HTMLUListElement;
};
type StoreType = {
recoveryCodes: {
downloadFileDate: string;
downloadFileDescription: string;
downloadFileHeader: string;
downloadFileName: string;
};
};
document.addEventListener('alpine:init', () => {
Alpine.data('recoveryCodes', function (this: DataType) {
const { codeList } = this.$refs;
const { downloadFileDate, downloadFileDescription, downloadFileHeader, downloadFileName } =
this.$store.recoveryCodes;
const date = new Date().toLocaleString(navigator.language);
const codeElements = codeList.getElementsByTagName('li');
const codes = Array.from(codeElements)
.map((codeElement) => codeElement.innerText)
.join('\n');
return {
copy: () => navigator.clipboard.writeText(codes),
download: () => {
const element = document.createElement('a');
const text = `${downloadFileHeader}\n\n${codes}\n\n${downloadFileDescription}\n\n${downloadFileDate} ${date}`;
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', `${downloadFileName}.txt`);
element.click();
},
print: () => {
const codeListHTML = codeList.innerHTML;
const styles = 'div { font-family: monospace; list-style-type: none }';
const content = `<html><style>${styles}</style><body><title>${downloadFileName}</title><p>${downloadFileHeader}</p><div>${codeListHTML}</div><p>${downloadFileDescription}</p><p>${downloadFileDate} ${date}</p></body></html>`;
const printWindow = window.open();
if (printWindow) {
printWindow.document.write(content);
printWindow.print();
printWindow.close();
}
},
};
});
});

View File

@ -0,0 +1,147 @@
import Alpine from 'alpinejs';
import { base64url } from 'rfc4648';
type DataType = {
$refs: RefsType;
$store: StoreType;
};
type RefsType = {
authenticatorDataInput: HTMLInputElement;
authnSelectForm?: HTMLFormElement;
clientDataJSONInput: HTMLInputElement;
credentialIdInput: HTMLInputElement;
errorInput: HTMLInputElement;
signatureInput: HTMLInputElement;
userHandleInput: HTMLInputElement;
webAuthnForm: HTMLFormElement;
};
type StoreType = {
webAuthnAuthenticate: {
challenge: string;
createTimeout: string;
isUserIdentified: string;
rpId: string;
unsupportedBrowserText: string;
userVerification: UserVerificationRequirement | 'not specified';
};
};
document.addEventListener('alpine:init', () => {
Alpine.data('webAuthnAuthenticate', function (this: DataType) {
const {
authenticatorDataInput,
authnSelectForm,
clientDataJSONInput,
credentialIdInput,
errorInput,
signatureInput,
userHandleInput,
webAuthnForm,
} = this.$refs;
const {
challenge,
createTimeout,
isUserIdentified,
rpId,
unsupportedBrowserText,
userVerification,
} = this.$store.webAuthnAuthenticate;
const doAuthenticate = (allowCredentials: PublicKeyCredentialDescriptor[]) => {
if (!window.PublicKeyCredential) {
errorInput.value = unsupportedBrowserText;
webAuthnForm.submit();
return;
}
const publicKey: PublicKeyCredentialRequestOptions = {
challenge: base64url.parse(challenge, { loose: true }),
rpId: rpId,
};
if (allowCredentials.length) {
publicKey.allowCredentials = allowCredentials;
}
if (parseInt(createTimeout) !== 0) publicKey.timeout = parseInt(createTimeout) * 1000;
if (userVerification !== 'not specified') publicKey.userVerification = userVerification;
navigator.credentials
.get({ publicKey })
.then((result) => {
if (
result instanceof PublicKeyCredential &&
result.response instanceof AuthenticatorAssertionResponse
) {
window.result = result;
authenticatorDataInput.value = base64url.stringify(
new Uint8Array(result.response.authenticatorData),
{ pad: false }
);
clientDataJSONInput.value = base64url.stringify(
new Uint8Array(result.response.clientDataJSON),
{ pad: false }
);
signatureInput.value = base64url.stringify(new Uint8Array(result.response.signature), {
pad: false,
});
credentialIdInput.value = result.id;
if (result.response.userHandle) {
userHandleInput.value = base64url.stringify(
new Uint8Array(result.response.userHandle),
{ pad: false }
);
}
webAuthnForm.submit();
}
})
.catch((error) => {
errorInput.value = error;
webAuthnForm.submit();
});
};
const checkAllowCredentials = () => {
const allowCredentials: PublicKeyCredentialDescriptor[] = [];
if (authnSelectForm) {
const authnSelectFormElements = Array.from(authnSelectForm.elements);
if (authnSelectFormElements.length) {
authnSelectFormElements.forEach((element) => {
if (element instanceof HTMLInputElement) {
allowCredentials.push({
id: base64url.parse(element.value, { loose: true }),
type: 'public-key',
});
}
});
}
}
doAuthenticate(allowCredentials);
};
return {
webAuthnAuthenticate: () => {
if (!isUserIdentified) {
doAuthenticate([]);
return;
}
checkAllowCredentials();
},
};
});
});

View File

@ -0,0 +1,225 @@
import Alpine from 'alpinejs';
import { base64url } from 'rfc4648';
type DataType = {
$refs: RefsType;
$store: StoreType;
};
type RefsType = {
attestationObjectInput: HTMLInputElement;
authenticatorLabelInput: HTMLInputElement;
clientDataJSONInput: HTMLInputElement;
errorInput: HTMLInputElement;
publicKeyCredentialIdInput: HTMLInputElement;
registerForm: HTMLFormElement;
transportsInput: HTMLInputElement;
};
type StoreType = {
webAuthnRegister: {
attestationConveyancePreference: AttestationConveyancePreference | 'not specified';
authenticatorAttachment: AuthenticatorAttachment | 'not specified';
challenge: string;
createTimeout: string;
excludeCredentialIds: string;
requireResidentKey: string;
rpEntityName: string;
rpId: string;
signatureAlgorithms: string;
unsupportedBrowserText: string;
userId: string;
userVerificationRequirement: UserVerificationRequirement | 'not specified';
username: string;
};
};
document.addEventListener('alpine:init', () => {
Alpine.data('webAuthnRegister', function (this: DataType) {
const {
attestationObjectInput,
authenticatorLabelInput,
clientDataJSONInput,
errorInput,
publicKeyCredentialIdInput,
registerForm,
transportsInput,
} = this.$refs;
const {
attestationConveyancePreference,
authenticatorAttachment,
challenge,
createTimeout,
excludeCredentialIds,
requireResidentKey,
rpEntityName,
rpId,
signatureAlgorithms,
unsupportedBrowserText,
userId,
userVerificationRequirement,
username,
} = this.$store.webAuthnRegister;
const getPubKeyCredParams = (signatureAlgorithms: string) => {
const pubKeyCredParams: PublicKeyCredentialParameters[] = [];
if (signatureAlgorithms === '') {
pubKeyCredParams.push({ alg: -7, type: 'public-key' });
return pubKeyCredParams;
}
const signatureAlgorithmsList = signatureAlgorithms.split(',');
signatureAlgorithmsList.forEach((value) => {
pubKeyCredParams.push({
alg: parseInt(value),
type: 'public-key',
});
});
return pubKeyCredParams;
};
const getExcludeCredentials = (excludeCredentialIds: string) => {
const excludeCredentials: PublicKeyCredentialDescriptor[] = [];
if (excludeCredentialIds === '') return excludeCredentials;
const excludeCredentialIdsList = excludeCredentialIds.split(',');
excludeCredentialIdsList.forEach((value) => {
excludeCredentials.push({
id: base64url.parse(value, { loose: true }),
type: 'public-key',
});
});
return excludeCredentials;
};
const getTransportsAsString = (transportsList: string | string[]) => {
if (transportsList === '' || transportsList.constructor !== Array) return '';
let transportsString = '';
transportsList.forEach((value) => {
transportsString += value + ',';
});
return transportsString.slice(0, -1);
};
return {
registerSecurityKey: () => {
if (!window.PublicKeyCredential) {
errorInput.value = unsupportedBrowserText;
registerForm.submit();
return;
}
const publicKey: PublicKeyCredentialCreationOptions = {
challenge: base64url.parse(challenge, { loose: true }),
pubKeyCredParams: getPubKeyCredParams(signatureAlgorithms),
rp: {
id: rpId,
name: rpEntityName,
},
user: {
displayName: username,
id: base64url.parse(userId, { loose: true }),
name: username,
},
};
if (attestationConveyancePreference !== 'not specified')
publicKey.attestation = attestationConveyancePreference;
const authenticatorSelection: AuthenticatorSelectionCriteria = {};
let isAuthenticatorSelectionSpecified = false;
if (authenticatorAttachment !== 'not specified') {
authenticatorSelection.authenticatorAttachment = authenticatorAttachment;
isAuthenticatorSelectionSpecified = true;
}
if (requireResidentKey !== 'not specified') {
if (requireResidentKey === 'Yes') authenticatorSelection.requireResidentKey = true;
else authenticatorSelection.requireResidentKey = false;
isAuthenticatorSelectionSpecified = true;
}
if (userVerificationRequirement !== 'not specified') {
authenticatorSelection.userVerification = userVerificationRequirement;
isAuthenticatorSelectionSpecified = true;
}
if (isAuthenticatorSelectionSpecified)
publicKey.authenticatorSelection = authenticatorSelection;
const excludeCredentials = getExcludeCredentials(excludeCredentialIds);
if (excludeCredentials.length > 0) publicKey.excludeCredentials = excludeCredentials;
if (parseInt(createTimeout) !== 0) publicKey.timeout = parseInt(createTimeout) * 1000;
navigator.credentials
.create({ publicKey })
.then((result) => {
if (
result instanceof PublicKeyCredential &&
result.response instanceof AuthenticatorAttestationResponse
) {
const { getTransports } = result.response;
window.result = result;
const publicKeyCredentialId = result.rawId;
attestationObjectInput.value = base64url.stringify(
new Uint8Array(result.response.attestationObject),
{ pad: false }
);
clientDataJSONInput.value = base64url.stringify(
new Uint8Array(result.response.clientDataJSON),
{ pad: false }
);
publicKeyCredentialIdInput.value = base64url.stringify(
new Uint8Array(publicKeyCredentialId),
{ pad: false }
);
if (typeof getTransports === 'function') {
const transports = getTransports();
if (transports) {
transportsInput.value = getTransportsAsString(transports);
}
} else {
console.log(
'Your browser is not able to recognize supported transport media for the authenticator.'
);
}
const initLabel = 'WebAuthn Authenticator (Default Label)';
let labelResult = window.prompt(
"Please input your registered authenticator's label",
initLabel
);
if (labelResult === null) labelResult = initLabel;
authenticatorLabelInput.value = labelResult;
registerForm.submit();
}
})
.catch(function (error) {
error.value = error;
registerForm.submit();
});
},
};
});
});

8
src/global.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import type { Alpine } from 'alpinejs';
declare global {
interface Window {
Alpine: Alpine;
result?: PublicKeyCredential;
}
}

33
src/index.css Normal file
View File

@ -0,0 +1,33 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* Alpine.js
======================================================================== */
[x-cloak] {
@apply hidden !important;
}
/* Separate
======================================================================== */
.separate {
@apply flex items-center text-center;
}
.separate::after,
.separate::before {
content: '';
@apply border-b border-secondary-200 flex-1;
}
.separate:not(:empty)::after {
@apply ml-2;
}
.separate:not(:empty)::before {
@apply mr-2;
}
}

7
src/index.ts Normal file
View File

@ -0,0 +1,7 @@
import './index.css';
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();

View File

@ -0,0 +1,13 @@
package org.keywind.theme;
import java.util.List;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
public class AuthenticationUtil implements TemplateMethodModelEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
return true;
}
}

View File

@ -0,0 +1,265 @@
package org.keywind.theme;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class LoginDataModel {
public static Map<String, Object> createDataModel() {
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("auth", createAuthModel());
dataModel.put("client", createClientModel());
dataModel.put("locale", createLocaleModel());
dataModel.put("login", createLoginModel());
dataModel.put("logoutConfirm", createLogoutConfirmModel());
dataModel.put("message", createMessageModel());
dataModel.put("oauth", createOAuthModel());
dataModel.put("otpLogin", createOtpLoginModel());
dataModel.put("properties", createPropertiesModel());
dataModel.put("realm", createRealmModel());
dataModel.put("recoveryAuthnCodesConfigBean", createRecoveryAuthnCodesConfigBeanModel());
dataModel.put("recoveryAuthnCodesInputBean", createRecoveryAuthnCodesInputBeanModel());
dataModel.put("social", createSocialModel());
dataModel.put("totp", createTotpModel());
dataModel.put("url", createUrlModel());
dataModel.put("user", createUserModel());
dataModel.put("username", "Username");
dataModel.put("x509", createX509Model());
dataModel.putAll(createWebAuthnModel());
return dataModel;
}
private static Map<String, Object> createAuthModel() {
Map<String, Object> securityKey = new HashMap<>();
securityKey.put("authExecId", "authExecId");
securityKey.put("displayName", "Security Key");
securityKey.put("helpText", "Use your security key to sign in.");
List<Map<String, Object>> authenticationSelections = new ArrayList<>();
authenticationSelections.add(securityKey);
Map<String, Object> auth = new HashMap<>();
auth.put("attemptedUsername", "Attempted Username");
auth.put("authenticationSelections", authenticationSelections);
auth.put("showResetCredentials", new AuthenticationUtil());
auth.put("showTryAnotherWayLink", new AuthenticationUtil());
auth.put("showUsername", new AuthenticationUtil());
return auth;
}
private static Map<String, Object> createClientModel() {
Map<String, Object> attributes = new HashMap<>();
Map<String, Object> client = new HashMap<>();
client.put("attributes", attributes);
return client;
}
private static Map<String, Object> createLocaleModel() {
Map<String, Object> de = new HashMap<>();
de.put("label", "Deutsch");
de.put("url", "url");
Map<String, Object> en = new HashMap<>();
en.put("label", "English");
en.put("url", "url");
Map<String, Object> fr = new HashMap<>();
fr.put("label", "Français");
fr.put("url", "url");
List<Map<String, Object>> supported = new ArrayList<>();
supported.add(de);
supported.add(en);
supported.add(fr);
Map<String, Object> locale = new HashMap<>();
locale.put("current", "English");
locale.put("currentLanguageTag", "en");
locale.put("supported", supported);
return locale;
}
private static Map<String, Object> createLoginModel() {
Map<String, Object> login = new HashMap<>();
login.put("rememberMe", true);
return login;
}
private static Map<String, Object> createLogoutConfirmModel() {
Map<String, Object> logoutConfirm = new HashMap<>();
logoutConfirm.put("code", "code");
logoutConfirm.put("skipLink", false);
return logoutConfirm;
}
private static Map<String, Object> createMessageModel() {
Map<String, Object> message = new HashMap<>();
message.put("summary", "Example of an error message");
message.put("type", "error");
return message;
}
private static Map<String, Object> createOAuthModel() {
List<Map<String, Object>> clientScopesRequested = new ArrayList<>();
Map<String, Object> oauth = new HashMap<>();
oauth.put("clientScopesRequested", clientScopesRequested);
oauth.put("code", "code");
return oauth;
}
private static Map<String, Object> createOtpLoginModel() {
List<Map<String, Object>> userOtpCredentials = new ArrayList<>();
Map<String, Object> otpLogin = new HashMap<>();
otpLogin.put("selectedCredentialId", 1);
otpLogin.put("userOtpCredentials", userOtpCredentials);
return otpLogin;
}
private static Map<String, Object> createPropertiesModel() {
Map<String, Object> properties = new HashMap<>();
properties.put("scripts", "src/index.ts");
properties.put("styles", "src/index.css");
return properties;
}
private static Map<String, Object> createRealmModel() {
Map<String, Object> realm = new HashMap<>();
realm.put("displayNameHtml", "Keywind");
realm.put("internationalizationEnabled", true);
realm.put("loginWithEmailAllowed", true);
realm.put("password", true);
realm.put("registrationAllowed", true);
realm.put("registrationEmailAsUsername", true);
realm.put("rememberMe", true);
realm.put("resetPasswordAllowed", true);
return realm;
}
public static Map<String, Object> createRecoveryAuthnCodesConfigBeanModel() {
List<String> generatedRecoveryAuthnCodesList = new ArrayList<>();
generatedRecoveryAuthnCodesList.add("000000000000");
generatedRecoveryAuthnCodesList.add("111111111111");
Map<String, Object> recoveryAuthnCodesConfigBean = new HashMap<>();
recoveryAuthnCodesConfigBean.put("generatedAt", "generatedAt");
recoveryAuthnCodesConfigBean.put("generatedRecoveryAuthnCodesAsString", "generatedRecoveryAuthnCodesAsString");
recoveryAuthnCodesConfigBean.put("generatedRecoveryAuthnCodesList", generatedRecoveryAuthnCodesList);
return recoveryAuthnCodesConfigBean;
}
public static Map<String, Object> createRecoveryAuthnCodesInputBeanModel() {
Map<String, Object> recoveryAuthnCodesInputBean = new HashMap<>();
recoveryAuthnCodesInputBean.put("codeNumber", "codeNumber");
return recoveryAuthnCodesInputBean;
}
private static Map<String, Object> createSocialModel() {
Map<String, Object> facebook = new HashMap<>();
facebook.put("alias", "facebook");
facebook.put("displayName", "Facebook");
facebook.put("loginUrl", "loginUrl");
Map<String, Object> github = new HashMap<>();
github.put("alias", "github");
github.put("displayName", "GitHub");
github.put("loginUrl", "loginUrl");
Map<String, Object> google = new HashMap<>();
google.put("alias", "google");
google.put("displayName", "Google");
google.put("loginUrl", "loginUrl");
List<Map<String, Object>> providers = new ArrayList<>();
providers.add(facebook);
providers.add(github);
providers.add(google);
Map<String, Object> social = new HashMap<>();
social.put("providers", providers);
return social;
}
private static Map<String, Object> createTotpModel() {
Map<String, Object> otpCredential = new HashMap<>();
List<Map<String, Object>> otpCredentials = new ArrayList<>();
otpCredentials.add(otpCredential);
List<String> supportedApplications = new ArrayList<>();
supportedApplications.add("totpAppFreeOTPName");
Map<String, Object> totp = new HashMap<>();
totp.put("manualUrl", "manualUrl");
totp.put("otpCredentials", otpCredentials);
totp.put("supportedApplications", supportedApplications);
totp.put("totpSecret", "totpSecret");
totp.put("totpSecretQrCode",
"iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAABgAAAAYADwa0LPAAAF70lEQVR42u3d0W3DOBQAwfhwPcj9V2e4CV8D96EgBPVWnilAphRlwY8H6vH5fD4/AAH/XL0AgLMEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgIx/r17A/zmO4+f9fl+9jEt8Pp8l13k8HrnfOnOdM1bdl/dwHjssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIGDk4esbr9fo5juPqZfzKqkHEaUOhO6+zagB11fP55vfwCnZYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQkR0cPWPVsOIZ005o3Hnvq+w8lXSnb34PV7PDAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CAjFsPjt7VtAHLaaeb3n148pvZYQEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIbB0aCdn2Ivrrn4fDjHDgvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjJuPTj6zcOBq4YnV13nm4dCp62nzA4LyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIysoOjz+fz6iWMtnMo9K7XOcN7uJcdFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZDw+jkPMOTMYeca0U0CLp4mylx0WkCFYQIZgARmCBWQIFpAhWECGYAEZggVkjDxxdOdg5M5hxbsOfBaf86o1r7JzPeXhWzssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIGDk4esaq4bedQ4/ThhXPcAro3+992iB0mR0WkCFYQIZgARmCBWQIFpAhWECGYAEZggVkZD9Vbxhvj2mvh9NE//5bZXZYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQkR0cPXVzwWG84pqn3dddh0tXKZ8ia4cFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ2U/VTxtE/GbThl13XmfnEKb30A4LCBEsIEOwgAzBAjIEC8gQLCBDsIAMwQIysoOjd/3896o1TxsynDagu3M9O4dLpz3n1eywgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgY+Tg6KpBu50nPU4b2CsOu057hjvXXHzHrmCHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGY/PzgnDlQsfNmg3bUh1528Vn+FO0X+xkeywgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgI3vi6CrTPlm+8zqr7n3aqZvTTLuvaev5DTssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIyJ44+s12DnMWBxrvel/YYQEhggVkCBaQIVhAhmABGYIFZAgWkCFYQMbIE0eP4/h5v99XL+MSd/1c+86h0Glr3vlbO0+jvYIdFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZIwcHD3j9Xr9HMdx9TJ+ZdVA7DeflvnNw5PThoGvYIcFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ2cHRM3YO2hWHDIunZd71RNZvHoj9DTssIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIuPXg6F1N+6z5zjWvUhxALQ98rmKHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGQZHg+46QLjzRM1pA5/T7n3qO2aHBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGbceHJ06/PZX04YMV5n29yquZ+ff6wp2WECGYAEZggVkCBaQIVhAhmABGYIFZAgWkJEdHH0+n1cvYbRpp1yeUT4J86/3xTl2WECGYAEZggVkCBaQIVhAhmABGYIFZAgWkPH4FCfxgK9khwVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVk/AeoXLE8BnySdAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wOS0yNVQyMDoxMjo1OSswMDowMLyvm1kAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDktMjVUMjA6MTI6NTkrMDA6MDDN8iPlAAAAAElFTkSuQmCC");
return totp;
}
private static Map<String, Object> createUrlModel() {
Map<String, Object> url = new HashMap<>();
url.put("loginAction", "loginAction");
url.put("loginResetCredentialsUrl", "loginResetCredentialsUrl");
url.put("loginRestartFlowUrl", "loginRestartFlowUrl");
url.put("loginUrl", "loginUrl");
url.put("logoutConfirmAction", "logoutConfirmAction");
url.put("oauthAction", "oauthAction");
url.put("registrationAction", "registrationAction");
url.put("registrationUrl", "registrationUrl");
url.put("resourcesPath", "http://localhost:5173");
return url;
}
private static Map<String, Object> createUserModel() {
Map<String, Object> user = new HashMap<>();
user.put("editUsernameAllowed", true);
return user;
}
private static Map<String, Object> createWebAuthnModel() {
Map<String, Object> webAuthn = new HashMap<>();
webAuthn.put("challenge", "challenge");
webAuthn.put("execution", "execution");
webAuthn.put("createTimeout", "60000");
webAuthn.put("isUserIdentified", "true");
webAuthn.put("rpId", "https://webauthn.me");
webAuthn.put("userVerification", "preferred");
return webAuthn;
}
private static Map<String, Object> createX509Model() {
Map<String, Object> formData = new HashMap<>();
formData.put("isUserEnabled", "true");
formData.put("subjectDN", "CN=User, C=US, O=Keywind");
formData.put("username", "Username");
Map<String, Object> x509 = new HashMap<>();
x509.put("formData", formData);
return x509;
}
}

View File

@ -0,0 +1,113 @@
package org.keywind.theme;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import freemarker.core.HTMLOutputFormat;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNotFoundException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;
import org.keycloak.forms.login.LoginFormsPages;
import org.keycloak.forms.login.freemarker.Templates;
import org.keycloak.theme.KeycloakSanitizerMethod;
import org.keycloak.theme.beans.MessageFormatterMethod;
import org.keycloak.theme.beans.MessagesPerFieldBean;
public class LoginThemeTest {
private static final String LANGUAGE = "English";
private static final String MESSAGE_PATH = "theme/base/login/messages/messages";
private static final String OUTPUT_PATH = "html/login";
private static final String THEME_PATH = "theme/keywind/login";
@Test
public void shouldTestTemplates() throws IOException, TemplateException {
Configuration configuration = createFreeMarkerConfiguration();
for (String templateName : getTemplateNames()) {
try {
Template template = configuration.getTemplate(templateName);
String renderedTemplate = renderTemplate(template);
Document document = formatHtml(renderedTemplate);
saveHtmlToFile(templateName, document);
} catch (TemplateNotFoundException e) {
System.out.println("Template not found: " + templateName);
}
}
}
private Configuration createFreeMarkerConfiguration() throws IOException, TemplateModelException {
Configuration configuration = new Configuration(Configuration.VERSION_2_3_32);
configuration.setDirectoryForTemplateLoading(new File(THEME_PATH));
configuration.setOutputFormat(HTMLOutputFormat.INSTANCE);
Locale locale = Locale.of(LANGUAGE);
Properties messages = loadMessages(locale);
configuration.setSharedVariable("kcSanitize", new KeycloakSanitizerMethod());
configuration.setSharedVariable("messagesPerField", new MessagesPerFieldBean());
configuration.setSharedVariable("msg", new MessageFormatterMethod(locale, messages));
return configuration;
}
private Properties loadMessages(Locale locale) {
ResourceBundle resourceBundle = ResourceBundle.getBundle(MESSAGE_PATH, locale);
Properties properties = new Properties();
Enumeration<String> keys = resourceBundle.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
String value = resourceBundle.getString(key);
properties.setProperty(key, value);
}
return properties;
}
private String[] getTemplateNames() {
return Arrays.stream(LoginFormsPages.values())
.map(Templates::getTemplate)
.toArray(String[]::new);
}
private String renderTemplate(Template template) throws IOException, TemplateException {
Map<String, Object> dataModel = LoginDataModel.createDataModel();
try (StringWriter writer = new StringWriter()) {
template.process(dataModel, writer);
return writer.toString();
}
}
private Document formatHtml(String html) {
Document document = Jsoup.parse(html);
document.outputSettings().indentAmount(2);
return document;
}
private void saveHtmlToFile(String templateName, Document document) throws IOException {
File outputFile = new File(OUTPUT_PATH, templateName.replace(".ftl", ".html"));
try (FileWriter fileWriter = new FileWriter(outputFile)) {
fileWriter.write(document.outerHtml());
}
}
}

37
tailwind.config.ts Normal file
View File

@ -0,0 +1,37 @@
import type { Config } from 'tailwindcss';
import colors from 'tailwindcss/colors';
export default {
content: ['./theme/**/*.ftl'],
experimental: {
optimizeUniversalDefaults: true,
},
plugins: [require('@tailwindcss/forms')],
theme: {
extend: {
colors: {
primary: colors.blue,
secondary: colors.gray,
provider: {
apple: '#000000',
bitbucket: '#0052CC',
discord: '#5865F2',
facebook: '#1877F2',
github: '#181717',
gitlab: '#FC6D26',
google: '#4285F4',
instagram: '#E4405F',
linkedin: '#0A66C2',
microsoft: '#5E5E5E',
oidc: '#F78C40',
openshift: '#EE0000',
paypal: '#00457C',
slack: '#4A154B',
stackoverflow: '#F58025',
twitter: '#1DA1F2',
},
},
},
},
} satisfies Config;

View File

@ -0,0 +1,7 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/arrow-top-right-on-square.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M4.25 5.5C3.83579 5.5 3.5 5.83579 3.5 6.25V14.75C3.5 15.1642 3.83579 15.5 4.25 15.5H12.75C13.1642 15.5 13.5 15.1642 13.5 14.75V10.75C13.5 10.3358 13.8358 10 14.25 10C14.6642 10 15 10.3358 15 10.75V14.75C15 15.9926 13.9926 17 12.75 17H4.25C3.00736 17 2 15.9926 2 14.75V6.25C2 5.00736 3.00736 4 4.25 4H9.25C9.66421 4 10 4.33579 10 4.75C10 5.16421 9.66421 5.5 9.25 5.5H4.25Z" fill-rule="evenodd" />
<path clip-rule="evenodd" d="M6.19385 12.7532C6.47175 13.0603 6.94603 13.0841 7.25319 12.8062L16.5 4.43999V7.25C16.5 7.66421 16.8358 8 17.25 8C17.6642 8 18 7.66421 18 7.25V2.75C18 2.33579 17.6642 2 17.25 2H12.75C12.3358 2 12 2.33579 12 2.75C12 3.16421 12.3358 3.5 12.75 3.5H15.3032L6.24682 11.6938C5.93966 11.9717 5.91595 12.446 6.19385 12.7532Z" fill-rule="evenodd" />
</svg>
</#macro>

View File

@ -0,0 +1,6 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/chevron-down.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M5.23017 7.20938C5.52875 6.92228 6.00353 6.93159 6.29063 7.23017L10 11.1679L13.7094 7.23017C13.9965 6.93159 14.4713 6.92228 14.7698 7.20938C15.0684 7.49647 15.0777 7.97125 14.7906 8.26983L10.5406 12.7698C10.3992 12.9169 10.204 13 10 13C9.79599 13 9.60078 12.9169 9.45938 12.7698L5.20938 8.26983C4.92228 7.97125 4.93159 7.49647 5.23017 7.20938Z" fill-rule="evenodd" />
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/eye.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M3.28033 2.21967C2.98744 1.92678 2.51256 1.92678 2.21967 2.21967C1.92678 2.51256 1.92678 2.98744 2.21967 3.28033L16.7197 17.7803C17.0126 18.0732 17.4874 18.0732 17.7803 17.7803C18.0732 17.4874 18.0732 17.0126 17.7803 16.7197L16.0352 14.9745C17.5064 13.8594 18.6595 12.3465 19.3344 10.5959C19.4814 10.2144 19.4816 9.79127 19.3347 9.40962C17.892 5.66051 14.256 3 9.99859 3C8.28207 3 6.66657 3.43249 5.2551 4.19444L3.28033 2.21967ZM7.75194 6.69128L8.84367 7.78301C9.18951 7.60223 9.58291 7.5 10.0002 7.5C11.3809 7.5 12.5002 8.61929 12.5002 10C12.5002 10.4173 12.398 10.8107 12.2172 11.1565L13.3091 12.2484C13.7454 11.6077 14.0004 10.8336 14.0004 10C14.0004 7.79086 12.2095 6 10.0004 6C9.16675 6 8.39268 6.25501 7.75194 6.69128Z" fill-rule="evenodd" />
<path d="M10.7484 13.9302L13.2711 16.4529C12.2462 16.8074 11.1458 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C1.15603 8.12932 1.90108 6.98057 2.83791 6.01969L6.0702 9.25198C6.02436 9.4943 6.00037 9.74435 6.00037 10C6.00037 12.2091 7.79123 14 10.0004 14C10.256 14 10.5061 13.976 10.7484 13.9302Z" />
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://github.com/tailwindlabs/heroicons/blob/master/src/20/solid/eye.svg -->
<#macro kw>
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C11.3807 12.5 12.5 11.3807 12.5 10C12.5 8.61929 11.3807 7.5 10 7.5C8.61929 7.5 7.5 8.61929 7.5 10C7.5 11.3807 8.61929 12.5 10 12.5Z" />
<path clip-rule="evenodd" d="M0.664255 10.5904C0.517392 10.2087 0.517518 9.78563 0.66461 9.40408C2.10878 5.65788 5.7433 3 9.99859 3C14.256 3 17.892 5.66051 19.3347 9.40962C19.4816 9.79127 19.4814 10.2144 19.3344 10.5959C17.8902 14.3421 14.2557 17 10.0004 17C5.74298 17 2.10698 14.3395 0.664255 10.5904ZM14.0004 10C14.0004 12.2091 12.2095 14 10.0004 14C7.79123 14 6.00037 12.2091 6.00037 10C6.00037 7.79086 7.79123 6 10.0004 6C12.2095 6 14.0004 7.79086 14.0004 10Z" fill-rule="evenodd" />
</svg>
</#macro>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,7 @@
<#-- https://apple.com -->
<#macro kw name="Apple">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M12.7549 5.53846C13.8059 5.53846 15.1234 4.8061 15.9079 3.82962C16.6184 2.94469 17.1365 1.70884 17.1365 0.472981C17.1365 0.305149 17.1217 0.137317 17.0921 0C15.9227 0.0457724 14.5164 0.808645 13.6727 1.8309C13.0066 2.60903 12.3997 3.82963 12.3997 5.08074C12.3997 5.26383 12.4293 5.44692 12.4441 5.50795C12.5181 5.5232 12.6365 5.53846 12.7549 5.53846ZM9.05428 24C10.4901 24 11.1266 23.0083 12.9178 23.0083C14.7385 23.0083 15.1382 23.9695 16.7368 23.9695C18.3059 23.9695 19.3569 22.4743 20.3487 21.0095C21.4589 19.3312 21.9178 17.6834 21.9474 17.6071C21.8438 17.5766 18.8388 16.3102 18.8388 12.7552C18.8388 9.67324 21.2072 8.28481 21.3405 8.178C19.7714 5.85887 17.3882 5.79784 16.7368 5.79784C14.9753 5.79784 13.5395 6.89638 12.6365 6.89638C11.6595 6.89638 10.3717 5.85887 8.84704 5.85887C5.94572 5.85887 3 8.33058 3 12.9994C3 15.8983 4.09539 18.965 5.44243 20.9485C6.59704 22.6268 7.60362 24 9.05428 24Z" fill="#000000" />
</svg>
</#macro>

View File

@ -0,0 +1,14 @@
<#-- https://atlassian.design/resources/logo-library -->
<#macro kw name="Bitbucket">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M0.770456 1.21005C0.290906 1.21005 -0.0687541 1.64964 0.0111659 2.08923L3.24816 21.9108C3.32808 22.4304 3.76768 22.79 4.28719 22.79H19.9526C20.3123 22.79 20.632 22.5102 20.7119 22.1505L23.9888 2.1292C24.0688 1.64964 23.7091 1.25002 23.2296 1.25002L0.770456 1.21005ZM14.5177 15.5167H9.52232L8.20352 8.44335H15.7565L14.5177 15.5167Z" fill="#2684FF" />
<path d="M22.9098 8.44335H15.7165L14.5177 15.5167H9.52232L3.64773 22.5102C3.64773 22.5102 3.92746 22.7501 4.32709 22.7501H19.9925C20.3522 22.7501 20.6719 22.4702 20.7518 22.1105L22.9098 8.44335Z" fill="url(#bitbucket)" />
<defs>
<linearGradient id="bitbucket" x1="24.5925" y1="10.4366" x2="12.672" y2="19.7417" gradientUnits="userSpaceOnUse">
<stop offset="0.176" stop-color="#0052CC" />
<stop offset="1" stop-color="#2684FF" />
</linearGradient>
</defs>
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://discord.com/branding -->
<#macro kw name="Discord">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M20.3303 4.42852C18.7535 3.70661 17.0888 3.19446 15.3789 2.90516C15.1449 3.32345 14.9332 3.75381 14.7446 4.19445C12.9232 3.91998 11.071 3.91998 9.24961 4.19445C9.06095 3.75386 8.84924 3.3235 8.61535 2.90516C6.90433 3.19691 5.2386 3.71027 3.66019 4.43229C0.526644 9.06843 -0.322811 13.5894 0.101917 18.0462C1.937 19.4021 3.99098 20.4332 6.17458 21.0948C6.66626 20.4335 7.10134 19.732 7.47519 18.9976C6.76511 18.7324 6.07975 18.4052 5.42706 18.0198C5.59884 17.8952 5.76684 17.7669 5.92918 17.6423C7.82837 18.5354 9.90124 18.9985 12 18.9985C14.0987 18.9985 16.1715 18.5354 18.0707 17.6423C18.235 17.7763 18.403 17.9047 18.5729 18.0198C17.9189 18.4058 17.2323 18.7337 16.5209 18.9995C16.8943 19.7335 17.3294 20.4345 17.8216 21.0948C20.007 20.4359 22.0626 19.4052 23.898 18.0481C24.3963 12.8797 23.0467 8.4002 20.3303 4.42852ZM8.01318 15.3053C6.82961 15.3053 5.85179 14.2312 5.85179 12.9099C5.85179 11.5885 6.79563 10.505 8.0094 10.505C9.22318 10.505 10.1934 11.5885 10.1727 12.9099C10.1519 14.2312 9.21941 15.3053 8.01318 15.3053ZM15.9867 15.3053C14.8013 15.3053 13.8272 14.2312 13.8272 12.9099C13.8272 11.5885 14.7711 10.505 15.9867 10.505C17.2024 10.505 18.1651 11.5885 18.1444 12.9099C18.1236 14.2312 17.193 15.3053 15.9867 15.3053Z" fill="#5865F2" />
</svg>
</#macro>

View File

@ -0,0 +1,8 @@
<#-- https://www.facebook.com/brand/resources/facebookapp/logo -->
<#macro kw name="Facebook">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 17.9895 4.38822 22.954 10.125 23.8542V15.4688H7.07813V12H10.125V9.35626C10.125 6.34875 11.9165 4.68751 14.6576 4.68751C15.9705 4.68751 17.3438 4.92188 17.3438 4.92188V7.875H15.8306C14.3399 7.875 13.875 8.80002 13.875 9.74901V12H17.2031L16.6711 15.4688H13.875V23.8542C19.6118 22.954 24 17.9895 24 12Z" fill="#1877F2" />
<path d="M16.6711 15.4688L17.2031 12H13.875V9.74901C13.875 8.80002 14.3399 7.875 15.8306 7.875H17.3438V4.92188C17.3438 4.92188 15.9705 4.68751 14.6576 4.68751C11.9165 4.68751 10.125 6.34875 10.125 9.35626V12H7.07813V15.4688H10.125V23.8542C10.7453 23.9514 11.3722 24.0002 12 24C12.6379 24 13.2641 23.9501 13.875 23.8542V15.4688H16.6711Z" fill="white" />
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://github.com/logos -->
<#macro kw name="GitHub">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M11.8452 0C5.13387 0 0 5.09516 0 11.8065C0 17.1726 3.37742 21.7645 8.20161 23.3806C8.82097 23.4919 9.03871 23.1097 9.03871 22.7952C9.03871 22.4952 9.02419 20.8403 9.02419 19.8242C9.02419 19.8242 5.6371 20.55 4.92581 18.3823C4.92581 18.3823 4.37419 16.9742 3.58065 16.6113C3.58065 16.6113 2.47258 15.8516 3.65806 15.8661C3.65806 15.8661 4.8629 15.9629 5.52581 17.1145C6.58548 18.9823 8.36129 18.4452 9.05323 18.1258C9.16452 17.3516 9.47903 16.8145 9.82742 16.4952C7.12258 16.1952 4.39355 15.8032 4.39355 11.1484C4.39355 9.81774 4.76129 9.15 5.53548 8.29839C5.40968 7.98387 4.99839 6.6871 5.66129 5.0129C6.67258 4.69839 9 6.31936 9 6.31936C9.96774 6.04839 11.0081 5.90806 12.0387 5.90806C13.0694 5.90806 14.1097 6.04839 15.0774 6.31936C15.0774 6.31936 17.4048 4.69355 18.4161 5.0129C19.079 6.69194 18.6677 7.98387 18.5419 8.29839C19.3161 9.15484 19.7903 9.82258 19.7903 11.1484C19.7903 15.8177 16.9403 16.1903 14.2355 16.4952C14.6806 16.8774 15.0581 17.6032 15.0581 18.7403C15.0581 20.371 15.0435 22.3887 15.0435 22.7855C15.0435 23.1 15.2661 23.4823 15.8806 23.371C20.7194 21.7645 24 17.1726 24 11.8065C24 5.09516 18.5565 0 11.8452 0Z" fill="#181717" />
</svg>
</#macro>

View File

@ -0,0 +1,10 @@
<#-- https://about.gitlab.com/press/press-kit -->
<#macro kw name="GitLab">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M23.6005 9.59068L23.5668 9.50445L20.3002 0.979328C20.2337 0.81224 20.116 0.670499 19.964 0.574441C19.8119 0.480015 19.6345 0.434537 19.4557 0.444146C19.2769 0.453756 19.1054 0.51799 18.9643 0.628176C18.8248 0.741526 18.7235 0.895119 18.6744 1.06805L16.4688 7.81617H7.53749L5.33186 1.06805C5.28402 0.894177 5.18257 0.739814 5.04194 0.626926C4.90083 0.51674 4.7293 0.452506 4.55053 0.442896C4.37175 0.433287 4.19433 0.478766 4.04222 0.573191C3.89054 0.669636 3.77297 0.811254 3.70606 0.978078L0.433225 9.49945L0.400734 9.58568C-0.0695071 10.8143 -0.127551 12.1626 0.235354 13.4271C0.598259 14.6916 1.36244 15.8039 2.41267 16.5962L2.42392 16.605L2.45391 16.6262L7.43002 20.3527L9.89184 22.2159L11.3914 23.3481C11.5668 23.4813 11.781 23.5534 12.0012 23.5534C12.2215 23.5534 12.4357 23.4813 12.6111 23.3481L14.1107 22.2159L16.5725 20.3527L21.5786 16.6037L21.5911 16.5937C22.639 15.8013 23.4014 14.6901 23.7637 13.4273C24.1261 12.1645 24.0688 10.8182 23.6005 9.59068Z" fill="#E24329" />
<path d="M23.6005 9.59068L23.5668 9.50445C21.9751 9.83116 20.4752 10.5054 19.1742 11.4789L12 16.9036C14.4431 18.7519 16.57 20.3577 16.57 20.3577L21.5761 16.6087L21.5886 16.5987C22.638 15.8063 23.4015 14.6945 23.7644 13.4305C24.1272 12.1666 24.0697 10.8191 23.6005 9.59068Z" fill="#FC6D26" />
<path d="M7.43002 20.3577L9.89184 22.2209L11.3914 23.3531C11.5668 23.4863 11.781 23.5584 12.0012 23.5584C12.2215 23.5584 12.4357 23.4863 12.6111 23.3531L14.1107 22.2209L16.5725 20.3577C16.5725 20.3577 14.4431 18.7469 12 16.9036C9.55693 18.7469 7.43002 20.3577 7.43002 20.3577Z" fill="#FCA326" />
<path d="M4.8245 11.4789C3.5246 10.5034 2.02503 9.8274 0.433225 9.49945L0.400734 9.58568C-0.0695071 10.8143 -0.127551 12.1626 0.235354 13.4271C0.598259 14.6916 1.36244 15.8039 2.41267 16.5962L2.42392 16.605L2.45391 16.6262L7.43002 20.3527C7.43002 20.3527 9.55443 18.7469 12 16.8986L4.8245 11.4789Z" fill="#FC6D26" />
</svg>
</#macro>

View File

@ -0,0 +1,10 @@
<#-- https://developers.google.com/identity/branding-guidelines -->
<#macro kw name="Google">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M23.76 12.2727C23.76 11.4218 23.6836 10.6036 23.5418 9.81818H12.24V14.46H18.6982C18.42 15.96 17.5745 17.2309 16.3036 18.0818L18.2427 19.5873L20.1818 21.0927C22.4509 19.0036 23.76 15.9273 23.76 12.2727Z" fill="#4285F4" />
<path d="M12.24 24C15.48 24 18.1964 22.9255 20.1818 21.0927L16.3036 18.0818C15.2291 18.8018 13.8545 19.2273 12.24 19.2273C9.11455 19.2273 6.46909 17.1164 5.52545 14.28L3.52091 15.8345L1.51636 17.3891C3.49091 21.3109 7.54909 24 12.24 24Z" fill="#34A853" />
<path d="M5.52545 14.28C5.28545 13.56 5.14909 12.7909 5.14909 12C5.14909 11.2091 5.28545 10.44 5.52545 9.72L3.52091 8.16546L1.51636 6.61091C0.703637 8.23091 0.240001 10.0636 0.240001 12C0.240001 13.9364 0.703637 15.7691 1.51636 17.3891L5.52545 14.28Z" fill="#FBBC05" />
<path d="M12.24 4.77273C14.0018 4.77273 15.5836 5.37818 16.8273 6.56727L20.2691 3.12545C18.1909 1.18909 15.4745 0 12.24 0C7.54909 0 3.49091 2.68909 1.51636 6.61091L5.52545 9.72C6.46909 6.88364 9.11455 4.77273 12.24 4.77273Z" fill="#EA4335" />
</svg>
</#macro>

View File

@ -0,0 +1,35 @@
<#-- https://www.facebook.com/brand/resources/instagram/instagram-brand -->
<#macro kw name="Instagram">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M19.8463 5.59481C19.8463 4.79718 19.1998 4.15313 18.4052 4.15313C17.6105 4.15313 16.9635 4.79718 16.9635 5.59481C16.9635 6.38951 17.6105 7.03356 18.4052 7.03356C19.1998 7.03356 19.8463 6.38951 19.8463 5.59481Z" fill="url(#instagram_0)" />
<path d="M21.7666 16.8484C21.7132 18.0185 21.5175 18.6543 21.355 19.0765C21.1367 19.6364 20.8764 20.0367 20.4542 20.4565C20.0367 20.8764 19.6364 21.1362 19.0765 21.352C18.6543 21.5169 18.0161 21.7132 16.8461 21.769C15.5811 21.8247 15.2063 21.8366 11.9985 21.8366C8.7937 21.8366 8.4159 21.8247 7.15094 21.769C5.98089 21.7132 5.34573 21.5169 4.92345 21.352C4.36067 21.1362 3.96334 20.8764 3.54347 20.4565C3.12061 20.0367 2.86028 19.6364 2.645 19.0765C2.48248 18.6543 2.28384 18.0185 2.2334 16.8484C2.17175 15.5835 2.16045 15.2027 2.16045 12.0015C2.16045 8.7937 2.17175 8.4159 2.2334 7.15094C2.28384 5.98089 2.48248 5.34573 2.645 4.9199C2.86028 4.36067 3.12061 3.96272 3.54347 3.54284C3.96334 3.12359 4.36067 2.86321 4.92345 2.645C5.34573 2.47954 5.98089 2.28619 7.15094 2.23046C8.4159 2.17469 8.7937 2.16045 11.9985 2.16045C15.2063 2.16045 15.5811 2.17469 16.8461 2.23046C18.0161 2.28619 18.6543 2.47954 19.0765 2.645C19.6364 2.86321 20.0367 3.12359 20.4542 3.54284C20.8764 3.96272 21.1367 4.36067 21.355 4.9199C21.5175 5.34573 21.7132 5.98089 21.7666 7.15094C21.8253 8.4159 21.8395 8.7937 21.8395 12.0015C21.8395 15.2027 21.8253 15.5835 21.7666 16.8484ZM23.9271 7.05251C23.8683 5.77388 23.6667 4.90033 23.3672 4.13948C23.0624 3.35012 22.6538 2.68116 21.9848 2.01221C21.3188 1.34623 20.6499 0.937607 19.8605 0.629238C19.0967 0.3327 18.2261 0.128677 16.9469 0.0729453C15.6677 0.0112537 15.2591 0 11.9985 0C8.74091 0 8.32935 0.0112537 7.05015 0.0729453C5.77388 0.128677 4.90388 0.3327 4.1365 0.629238C3.35012 0.937607 2.68116 1.34623 2.01519 2.01221C1.34623 2.68116 0.937606 3.35012 0.629815 4.13948C0.333277 4.90033 0.131656 5.77388 0.0699646 7.05251C0.0142331 8.33171 0 8.74091 0 12.0015C0 15.2591 0.0142331 15.6677 0.0699646 16.9469C0.131656 18.2231 0.333277 19.0961 0.629815 19.8605C0.937606 20.6469 1.34623 21.3188 2.01519 21.9848C2.68116 22.6508 3.35012 23.0624 4.1365 23.3702C4.90388 23.6667 5.77388 23.8683 7.05015 23.9271C8.32935 23.9858 8.74091 24 11.9985 24C15.2591 24 15.6677 23.9858 16.9469 23.9271C18.2261 23.8683 19.0967 23.6667 19.8605 23.3702C20.6499 23.0624 21.3188 22.6508 21.9848 21.9848C22.6538 21.3188 23.0624 20.6469 23.3672 19.8605C23.6667 19.0961 23.8683 18.2231 23.9271 16.9469C23.9858 15.6677 24 15.2591 24 12.0015C24 8.74091 23.9858 8.33171 23.9271 7.05251Z" fill="url(#instagram_1)" />
<path d="M11.9985 15.998C9.7906 15.998 7.99901 14.2094 7.99901 12.0014C7.99901 9.78998 9.7906 7.99901 11.9985 7.99901C14.207 7.99901 16.001 9.78998 16.001 12.0014C16.001 14.2094 14.207 15.998 11.9985 15.998ZM11.9985 5.83558C8.59502 5.83558 5.83855 8.59796 5.83855 12.0014C5.83855 15.402 8.59502 18.1614 11.9985 18.1614C15.402 18.1614 18.1614 15.402 18.1614 12.0014C18.1614 8.59796 15.402 5.83558 11.9985 5.83558Z" fill="url(#instagram_2)" />
<defs>
<linearGradient id="instagram_0" x1="-139.421" y1="-139.299" x2="42.0899" y2="42.0606" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD521" />
<stop offset="0.05" stop-color="#FFD521" />
<stop offset="0.501119" stop-color="#F50000" />
<stop offset="0.95" stop-color="#B900B4" />
<stop offset="0.950079" stop-color="#B900B4" />
<stop offset="1" stop-color="#B900B4" />
</linearGradient>
<linearGradient id="instagram_1" x1="0.216477" y1="0.218256" x2="22.0189" y2="22.0207" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD521" />
<stop offset="0.05" stop-color="#FFD521" />
<stop offset="0.501119" stop-color="#F50000" />
<stop offset="0.95" stop-color="#B900B4" />
<stop offset="0.950079" stop-color="#B900B4" />
<stop offset="1" stop-color="#B900B4" />
</linearGradient>
<linearGradient id="instagram_2" x1="0.222127" y1="23.7823" x2="22.0193" y2="1.98511" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFD521" />
<stop offset="0.05" stop-color="#FFD521" />
<stop offset="0.501119" stop-color="#F50000" />
<stop offset="0.95" stop-color="#B900B4" />
<stop offset="0.950079" stop-color="#B900B4" />
<stop offset="1" stop-color="#B900B4" />
</linearGradient>
</defs>
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://brand.linkedin.com/downloads -->
<#macro kw name="LinkedIn">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M20.4491 20.4489H16.8931V14.88C16.8931 13.552 16.8694 11.8425 15.0436 11.8425C13.1915 11.8425 12.9081 13.2894 12.9081 14.7833V20.4485H9.35204V8.99659H12.7658V10.5616H12.8136C13.1553 9.97748 13.649 9.49694 14.2421 9.17118C14.8352 8.84542 15.5056 8.68663 16.1819 8.71173C19.7861 8.71173 20.4506 11.0824 20.4506 14.1666L20.4491 20.4489ZM5.33963 7.43118C4.93148 7.43126 4.53248 7.3103 4.19308 7.08361C3.85368 6.85692 3.58912 6.53467 3.43287 6.15762C3.27661 5.78057 3.23567 5.36565 3.31522 4.96534C3.39477 4.56502 3.59125 4.19728 3.8798 3.90863C4.16835 3.61997 4.53602 3.42337 4.9363 3.34367C5.33659 3.26397 5.75153 3.30476 6.12863 3.46089C6.50574 3.61701 6.82808 3.88145 7.05489 4.22077C7.28171 4.56009 7.40281 4.95905 7.40288 5.36719C7.40293 5.63819 7.3496 5.90655 7.24594 6.15694C7.14228 6.40732 6.99031 6.63484 6.79872 6.8265C6.60713 7.01816 6.37967 7.17021 6.12931 7.27396C5.87896 7.37771 5.61063 7.43114 5.33963 7.43118ZM7.11765 20.4489H3.5579V8.99659H7.11765V20.4489ZM22.222 0.0016345H1.77099C1.30681 -0.00360376 0.859536 0.175657 0.527458 0.500024C0.195381 0.824392 0.00566506 1.26733 0 1.7315V22.2673C0.00547116 22.7317 0.195065 23.175 0.527132 23.4997C0.859198 23.8244 1.30658 24.004 1.77099 23.999H22.222C22.6873 24.0049 23.1359 23.8258 23.4693 23.5011C23.8027 23.1764 23.9936 22.7326 24 22.2673V1.73002C23.9934 1.26493 23.8024 0.821487 23.469 0.497127C23.1356 0.172767 22.6871 -0.00598123 22.222 0.000152816" fill="#0A66C2" />
</svg>
</#macro>

View File

@ -0,0 +1,10 @@
<#-- https://learn.microsoft.com/azure/active-directory/develop/howto-add-branding-in-azure-ad-apps -->
<#macro kw name="Microsoft">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M11.3684 0H0V11.3684H11.3684V0Z" fill="#F25022" />
<path d="M11.3684 12.6316H0V24H11.3684V12.6316Z" fill="#00A4EF" />
<path d="M24 0H12.6316V11.3684H24V0Z" fill="#7FBA00" />
<path d="M24 12.6316H12.6316V24H24V12.6316Z" fill="#FFB900" />
</svg>
</#macro>

View File

@ -0,0 +1,9 @@
<#-- https://openid.net/add-openid/logos -->
<#macro kw name="OpenID">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M10.9071 2.6627V20.8342V23.1134L14.5427 21.4023V0.886559L10.9071 2.6627Z" fill="#F8941C" />
<path d="M23.4982 8.66156L24 13.8724L16.9691 12.3427" fill="#BCBEC0" />
<path d="M3.63569 15.3299C3.63569 12.7538 6.44278 10.5843 10.2689 9.92206V7.61282C4.41656 8.32019 0 11.5061 0 15.3299C0 19.2916 4.74059 22.5682 10.9071 23.1134V20.8342C6.75829 20.3141 3.63569 18.0475 3.63569 15.3299ZM15.1809 7.61373V9.92206C16.7033 10.1855 18.065 10.6863 19.1519 11.3582L21.7227 9.76936C19.9707 8.68649 17.7095 7.91936 15.1809 7.61373Z" fill="#BCBEC0" />
</svg>
</#macro>

View File

@ -0,0 +1,11 @@
<#-- https://www.redhat.com/technologies/cloud-computing/openshift -->
<#macro kw name="Red Hat OpenShift">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M24 6.60227C23.7314 6.04752 23.4206 5.51216 23.06 5.00724L19.2073 6.40936C19.6553 6.86793 20.0318 7.38336 20.3396 7.93459L24 6.60227ZM6.96939 10.8635L3.11552 12.2656C3.16478 12.8837 3.2715 13.4942 3.42101 14.0923L7.08198 12.7594C6.96292 12.1401 6.9207 11.5021 6.96939 10.8635Z" fill="#C22133" />
<path d="M15.5221 5.51084C16.3237 5.88497 17.0181 6.39515 17.6039 6.99328L21.4566 5.5912C20.3893 4.09352 18.9356 2.84212 17.1547 2.01116C11.6465 -0.557309 5.07525 1.83406 2.50739 7.34161C1.67586 9.12373 1.3668 11.0167 1.51223 12.8498L5.36554 11.4476C5.42944 10.6126 5.63294 9.77582 6.00646 8.97361C7.67543 5.39535 11.9439 3.84258 15.5221 5.51084ZM19.6315 12.5515C19.5699 13.386 19.3594 14.2228 18.9847 15.0256C17.3164 18.6044 13.0473 20.1573 9.46965 18.489C8.66683 18.1142 7.96727 17.6082 7.38377 17.0089L3.53872 18.408C4.60363 19.9057 6.05558 21.1577 7.83765 21.9892C13.3458 24.5571 19.9159 22.1657 22.4844 16.6576C23.3165 14.8767 23.6232 12.9837 23.4766 11.1524L19.6315 12.5515Z" fill="#DB212E" />
<path d="M20.579 7.84756L16.918 9.17988C17.5983 10.3984 17.9196 11.8011 17.814 13.212L21.6591 11.8135C21.549 10.4358 21.1828 9.09077 20.579 7.84756ZM3.66097 14.0044L0 15.3379C0.337597 16.6778 0.921419 17.9433 1.72169 19.0698L5.56619 17.67C4.57929 16.6567 3.92307 15.3742 3.66097 14.0044Z" fill="#EB2126" />
<path d="M23.442 5.58726C23.3206 5.39023 23.1945 5.19611 23.0602 5.00728L19.2075 6.40941C19.377 6.58299 19.533 6.76712 19.6819 6.95594L23.442 5.58726ZM6.95466 11.6373C6.9449 11.3794 6.94979 11.1212 6.9693 10.8638L3.11542 12.2659C3.13536 12.5128 3.16642 12.7579 3.20338 13.0025L6.95466 11.6373Z" fill="#AD213B" />
<path d="M23.4764 11.1523L19.6313 12.5515C19.5908 13.1051 19.4829 13.6598 19.3064 14.2052L23.4916 12.6793C23.5217 12.1706 23.5166 11.6604 23.4764 11.1523ZM3.53905 18.4085C3.83724 18.8278 4.16469 19.2254 4.51896 19.5984L8.70474 18.0719C8.21567 17.7658 7.77352 17.4087 7.38354 17.0088L3.53905 18.4085Z" fill="#BA2133" />
</svg>
</#macro>

View File

@ -0,0 +1,9 @@
<#-- https://www.paypal.com -->
<#macro kw name="PayPal">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M20.1597 6.09762C20.1374 6.23998 20.112 6.38552 20.0833 6.53504C19.0995 11.586 15.7338 13.3309 11.4352 13.3309H9.24646C8.72077 13.3309 8.27778 13.7127 8.19586 14.2312L7.07527 21.3381L6.75795 23.3526C6.74531 23.4325 6.75013 23.5142 6.77209 23.5921C6.79405 23.67 6.83263 23.7421 6.88516 23.8037C6.93769 23.8652 7.00293 23.9146 7.07639 23.9485C7.14985 23.9824 7.22978 24 7.31069 24H11.1926C11.6523 24 12.0428 23.666 12.1151 23.2126L12.1533 23.0154L12.8842 18.3772L12.9311 18.1227C13.0027 17.6678 13.394 17.3337 13.8537 17.3337H14.4343C18.1953 17.3337 21.1395 15.8067 22 11.388C22.3595 9.5421 22.1734 8.00079 21.2222 6.91679C20.9207 6.58146 20.5607 6.30387 20.1597 6.09762Z" fill="#179BD7" />
<path d="M19.1305 5.68724C18.8164 5.59637 18.4969 5.52545 18.1738 5.4749C17.5354 5.37679 16.8902 5.32972 16.2444 5.33413H10.3973C10.1748 5.33396 9.95951 5.4134 9.79045 5.55809C9.6214 5.70279 9.50969 5.90319 9.47551 6.12307L8.23165 14.0014L8.19586 14.2312C8.23461 13.9804 8.36185 13.7518 8.55455 13.5867C8.74725 13.4215 8.99269 13.3308 9.24646 13.3309H11.4352C15.7338 13.3309 19.0995 11.5852 20.0833 6.53504C20.1128 6.38552 20.1374 6.23998 20.1597 6.09762C19.9001 5.96149 19.6295 5.84736 19.3508 5.75644C19.2778 5.73219 19.2043 5.70913 19.1305 5.68724Z" fill="#222D65" />
<path d="M9.47551 6.12307C9.5094 5.90313 9.62105 5.70263 9.79019 5.55801C9.95933 5.41339 10.1747 5.33423 10.3973 5.33492H16.2444C16.9371 5.33492 17.5837 5.38026 18.1738 5.47569C18.5731 5.53845 18.9669 5.63232 19.3516 5.75644C19.6419 5.85267 19.9115 5.9664 20.1605 6.09762C20.4531 4.23104 20.1581 2.96014 19.1488 1.80933C18.0362 0.5424 16.0281 0 13.4584 0H5.99843C5.47352 0 5.02577 0.381748 4.94464 0.901084L1.83738 20.5969C1.8229 20.6883 1.82841 20.7818 1.85352 20.8709C1.87863 20.96 1.92274 21.0427 1.98282 21.1131C2.0429 21.1835 2.11753 21.2401 2.20157 21.279C2.28561 21.3178 2.37707 21.338 2.46965 21.3381L7.07527 21.3381L8.23165 14.0014L9.47551 6.12307Z" fill="#253B80" />
</svg>
</#macro>

View File

@ -0,0 +1,84 @@
<#import "./apple.ftl" as appleIcon>
<#import "./bitbucket.ftl" as bitbucketIcon>
<#import "./discord.ftl" as discordIcon>
<#import "./facebook.ftl" as facebookIcon>
<#import "./github.ftl" as githubIcon>
<#import "./gitlab.ftl" as gitlabIcon>
<#import "./google.ftl" as googleIcon>
<#import "./instagram.ftl" as instagramIcon>
<#import "./linkedin.ftl" as linkedinIcon>
<#import "./microsoft.ftl" as microsoftIcon>
<#import "./oidc.ftl" as oidcIcon>
<#import "./openshift.ftl" as openshiftIcon>
<#import "./paypal.ftl" as paypalIcon>
<#import "./slack.ftl" as slackIcon>
<#import "./stackoverflow.ftl" as stackoverflowIcon>
<#import "./twitter.ftl" as twitterIcon>
<#macro apple>
<@appleIcon.kw />
</#macro>
<#macro bitbucket>
<@bitbucketIcon.kw />
</#macro>
<#macro discord>
<@discordIcon.kw />
</#macro>
<#macro facebook>
<@facebookIcon.kw />
</#macro>
<#macro github>
<@githubIcon.kw />
</#macro>
<#macro gitlab>
<@gitlabIcon.kw />
</#macro>
<#macro google>
<@googleIcon.kw />
</#macro>
<#macro instagram>
<@instagramIcon.kw />
</#macro>
<#macro "linkedin-openid-connect">
<@linkedinIcon.kw />
</#macro>
<#macro microsoft>
<@microsoftIcon.kw />
</#macro>
<#macro oidc>
<@oidcIcon.kw />
</#macro>
<#macro "openshift-v3">
<@openshiftIcon.kw />
</#macro>
<#macro "openshift-v4">
<@openshiftIcon.kw />
</#macro>
<#macro paypal>
<@paypalIcon.kw />
</#macro>
<#macro slack>
<@slackIcon.kw />
</#macro>
<#macro stackoverflow>
<@stackoverflowIcon.kw />
</#macro>
<#macro twitter>
<@twitterIcon.kw />
</#macro>

View File

@ -0,0 +1,14 @@
<#-- https://slack.com/media-kit -->
<#macro kw name="Slack">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M5.04235 15.1661C5.04235 16.5537 3.9088 17.6873 2.52117 17.6873C1.13355 17.6873 0 16.5537 0 15.1661C0 13.7785 1.13355 12.645 2.52117 12.645H5.04235V15.1661Z" fill="#E01E5A" />
<path d="M6.3127 15.1661C6.3127 13.7785 7.44626 12.645 8.83388 12.645C10.2215 12.645 11.355 13.7785 11.355 15.1661V21.4788C11.355 22.8664 10.2215 24 8.83388 24C7.44626 24 6.3127 22.8664 6.3127 21.4788V15.1661Z" fill="#E01E5A" />
<path d="M8.83388 5.04235C7.44626 5.04235 6.3127 3.9088 6.3127 2.52117C6.3127 1.13355 7.44626 0 8.83388 0C10.2215 0 11.355 1.13355 11.355 2.52117V5.04235H8.83388Z" fill="#36C5F0" />
<path d="M8.83388 6.3127C10.2215 6.3127 11.355 7.44626 11.355 8.83388C11.355 10.2215 10.2215 11.355 8.83388 11.355H2.52117C1.13355 11.355 0 10.2215 0 8.83388C0 7.44626 1.13355 6.3127 2.52117 6.3127H8.83388Z" fill="#36C5F0" />
<path d="M18.9577 8.83388C18.9577 7.44626 20.0912 6.3127 21.4788 6.3127C22.8664 6.3127 24 7.44626 24 8.83388C24 10.2215 22.8664 11.355 21.4788 11.355H18.9577V8.83388Z" fill="#2EB67D" />
<path d="M17.6873 8.83388C17.6873 10.2215 16.5537 11.355 15.1661 11.355C13.7785 11.355 12.645 10.2215 12.645 8.83388V2.52117C12.645 1.13355 13.7785 0 15.1661 0C16.5537 0 17.6873 1.13355 17.6873 2.52117V8.83388Z" fill="#2EB67D" />
<path d="M15.1661 18.9577C16.5537 18.9577 17.6873 20.0912 17.6873 21.4788C17.6873 22.8664 16.5537 24 15.1661 24C13.7785 24 12.645 22.8664 12.645 21.4788V18.9577H15.1661Z" fill="#ECB22E" />
<path d="M15.1661 17.6873C13.7785 17.6873 12.645 16.5537 12.645 15.1661C12.645 13.7785 13.7785 12.645 15.1661 12.645H21.4788C22.8664 12.645 24 13.7785 24 15.1661C24 16.5537 22.8664 17.6873 21.4788 17.6873H15.1661Z" fill="#ECB22E" />
</svg>
</#macro>

View File

@ -0,0 +1,8 @@
<#-- https://stackoverflow.design/brand/logo -->
<#macro kw name="Stack Overflow">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M18.9475 15.4707H21.0799V24H1.88887V15.4707H4.0212V21.8677H18.9475V15.4707Z" fill="#BBBBBB" />
<path d="M6.34504 14.8368L16.8177 17.0379L17.2579 14.9444L6.78527 12.7422L6.34504 14.8368ZM7.73086 9.82211L17.4322 14.3403L18.3359 12.4001L8.63439 7.88188L7.73086 9.82211ZM10.4153 5.06255L18.6399 11.9114L20.0096 10.2666L11.785 3.41794L10.4153 5.06255ZM15.7242 0L14.0067 1.27746L20.3936 9.86495L22.1111 8.58768L15.7242 0ZM6.15354 19.7353H16.8152V17.603H6.15354V19.7353Z" fill="#F58025" />
</svg>
</#macro>

View File

@ -0,0 +1,7 @@
<#-- https://about.twitter.com/en/who-we-are/brand-toolkit -->
<#macro kw name="Twitter">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>${name}</title>
<path d="M21.543 7.10409C21.5576 7.31567 21.5576 7.52725 21.5576 7.74077C21.5576 14.2471 16.6045 21.7508 7.54759 21.7508V21.7469C4.87215 21.7508 2.25229 20.9844 0 19.5394C0.389031 19.5862 0.780012 19.6096 1.17197 19.6106C3.38915 19.6126 5.54296 18.8686 7.28726 17.4987C5.18026 17.4588 3.3326 16.085 2.68714 14.0793C3.42523 14.2217 4.18574 14.1924 4.91018 13.9945C2.61304 13.5304 0.96039 11.5121 0.96039 9.1682C0.96039 9.14675 0.96039 9.12627 0.96039 9.1058C1.64485 9.48703 2.41121 9.6986 3.19512 9.722C1.03157 8.27606 0.364656 5.39781 1.67118 3.14748C4.17112 6.22365 7.8596 8.09373 11.8191 8.29166C11.4223 6.58148 11.9644 4.7894 13.2436 3.58721C15.2268 1.72298 18.3459 1.81853 20.2101 3.80074C21.3129 3.58331 22.3698 3.17868 23.337 2.60537C22.9694 3.74516 22.2001 4.71335 21.1725 5.32859C22.1484 5.21353 23.102 4.95223 24 4.55345C23.3389 5.54406 22.5063 6.40695 21.543 7.10409Z" fill="#1D9BF0" />
</svg>
</#macro>

View File

@ -0,0 +1,22 @@
<#macro kw color="">
<#switch color>
<#case "error">
<#assign colorClass="bg-red-100 text-red-600">
<#break>
<#case "info">
<#assign colorClass="bg-blue-100 text-blue-600">
<#break>
<#case "success">
<#assign colorClass="bg-green-100 text-green-600">
<#break>
<#case "warning">
<#assign colorClass="bg-orange-100 text-orange-600">
<#break>
<#default>
<#assign colorClass="bg-blue-100 text-blue-600">
</#switch>
<div class="${colorClass} p-4 rounded-lg text-sm" role="alert">
<#nested>
</div>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<body class="bg-secondary-100 flex flex-col items-center justify-center min-h-screen sm:py-16">
<#nested>
</body>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<div class="flex flex-col pt-4 space-y-2">
<#nested>
</div>
</#macro>

View File

@ -0,0 +1,33 @@
<#macro kw color="" component="button" size="" rest...>
<#switch color>
<#case "primary">
<#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700">
<#break>
<#case "secondary">
<#assign colorClass="bg-secondary-100 text-secondary-600 focus:ring-secondary-600 hover:bg-secondary-200 hover:text-secondary-900">
<#break>
<#default>
<#assign colorClass="bg-primary-600 text-white focus:ring-primary-600 hover:bg-primary-700">
</#switch>
<#switch size>
<#case "medium">
<#assign sizeClass="px-4 py-2 text-sm">
<#break>
<#case "small">
<#assign sizeClass="px-2 py-1 text-xs">
<#break>
<#default>
<#assign sizeClass="px-4 py-2 text-sm">
</#switch>
<${component}
class="${colorClass} ${sizeClass} flex justify-center relative rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-offset-2"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View File

@ -0,0 +1,19 @@
<#macro kw content="" footer="" header="">
<div class="bg-white p-8 rounded-lg space-y-6">
<#if header?has_content>
<div class="space-y-4">
${header}
</div>
</#if>
<#if content?has_content>
<div class="space-y-4">
${content}
</div>
</#if>
<#if footer?has_content>
<div class="space-y-4">
${footer}
</div>
</#if>
</div>
</#macro>

View File

@ -0,0 +1,19 @@
<#macro kw checked=false label="" name="" rest...>
<div class="flex items-center">
<input
<#if checked>checked</#if>
class="border-secondary-200 h-4 rounded text-primary-600 w-4 focus:ring-primary-200 focus:ring-opacity-50"
id="${name}"
name="${name}"
type="checkbox"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<label class="ml-2 text-secondary-600 text-sm" for="${name}">
${label}
</label>
</div>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<div class="max-w-md space-y-6 w-full">
<#nested>
</div>
</#macro>

View File

@ -0,0 +1,11 @@
<#macro kw rest...>
<form
class="m-0 space-y-4"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</form>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<h1 class="text-center text-xl">
<#nested>
</h1>
</#macro>

View File

@ -0,0 +1,78 @@
<#import "/assets/icons/eye.ftl" as iconEye>
<#import "/assets/icons/eye-slash.ftl" as iconEyeSlash>
<#macro
kw
autofocus=false
class="block border-secondary-200 mt-1 rounded-md w-full focus:border-primary-300 focus:ring focus:ring-primary-200 focus:ring-opacity-50 sm:text-sm"
disabled=false
invalid=false
label=""
message=""
name=""
required=true
type="text"
rest...
>
<div>
<label class="sr-only" for="${name}">
${label}
</label>
<#if type == "password">
<div class="relative" x-data="{ show: false }">
<input
<#if autofocus>autofocus</#if>
<#if disabled>disabled</#if>
<#if required>required</#if>
aria-invalid="${invalid?c}"
class="${class}"
id="${name}"
name="${name}"
placeholder="${label}"
:type="show ? 'text' : 'password'"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<button
@click="show = !show"
aria-controls="${name}"
:aria-expanded="show"
class="absolute text-secondary-400 right-3 top-3 sm:top-2"
type="button"
>
<div x-show="!show">
<@iconEye.kw />
</div>
<div x-cloak x-show="show">
<@iconEyeSlash.kw />
</div>
</button>
</div>
<#else>
<input
<#if autofocus>autofocus</#if>
<#if disabled>disabled</#if>
<#if required>required</#if>
aria-invalid="${invalid?c}"
class="${class}"
id="${name}"
name="${name}"
placeholder="${label}"
type="${type}"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
</#if>
<#if invalid?? && message??>
<div class="mt-2 text-red-600 text-sm">
${message?no_esc}
</div>
</#if>
</div>
</#macro>

View File

@ -0,0 +1,30 @@
<#macro kw color="" component="a" size="" rest...>
<#switch color>
<#case "primary">
<#assign colorClass="text-primary-600 hover:text-primary-500">
<#break>
<#case "secondary">
<#assign colorClass="text-secondary-600 hover:text-secondary-900">
<#break>
<#default>
<#assign colorClass="text-primary-600 hover:text-primary-500">
</#switch>
<#switch size>
<#case "small">
<#assign sizeClass="text-sm">
<#break>
<#default>
<#assign sizeClass="">
</#switch>
<${component}
class="<#compress>${colorClass} ${sizeClass} inline-flex</#compress>"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<#nested>
</${component}>
</#macro>

View File

@ -0,0 +1,8 @@
<#macro kw>
<div class="font-bold text-center text-2xl">
<#--<#nested>-->
<div class="logo-container">
<a href="${client.baseUrl}"><img src="${url.resourcesPath}/header-logo.png" alt="Company Logo"></a>
</div>
</div>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<div class="flex justify-around">
<#nested>
</div>
</#macro>

View File

@ -0,0 +1,18 @@
<#macro kw checked=false id="" label="" rest...>
<div>
<input
<#if checked>checked</#if>
class="border-secondary-200 focus:ring-primary-600"
id="${id}"
type="radio"
<#list rest as attrName, attrValue>
${attrName}="${attrValue}"
</#list>
>
<label class="ml-2 text-secondary-600 text-sm" for="${id}">
${label}
</label>
</div>
</#macro>

View File

@ -0,0 +1,81 @@
<#import "/assets/providers/providers.ftl" as providerIcons>
<#macro kw providers=[]>
<div class="pt-4 separate text-secondary-600 text-sm">
${msg("identity-provider-login-label")}
</div>
<div class="gap-4 grid grid-cols-3">
<#list providers as provider>
<#switch provider.alias>
<#case "apple">
<#assign colorClass="hover:bg-provider-apple/10">
<#break>
<#case "bitbucket">
<#assign colorClass="hover:bg-provider-bitbucket/10">
<#break>
<#case "discord">
<#assign colorClass="hover:bg-provider-discord/10">
<#break>
<#case "facebook">
<#assign colorClass="hover:bg-provider-facebook/10">
<#break>
<#case "github">
<#assign colorClass="hover:bg-provider-github/10">
<#break>
<#case "gitlab">
<#assign colorClass="hover:bg-provider-gitlab/10">
<#break>
<#case "google">
<#assign colorClass="hover:bg-provider-google/10">
<#break>
<#case "instagram">
<#assign colorClass="hover:bg-provider-instagram/10">
<#break>
<#case "linkedin-openid-connect">
<#assign colorClass="hover:bg-provider-linkedin/10">
<#break>
<#case "microsoft">
<#assign colorClass="hover:bg-provider-microsoft/10">
<#break>
<#case "oidc">
<#assign colorClass="hover:bg-provider-oidc/10">
<#break>
<#case "openshift-v3">
<#assign colorClass="hover:bg-provider-openshift/10">
<#break>
<#case "openshift-v4">
<#assign colorClass="hover:bg-provider-openshift/10">
<#break>
<#case "paypal">
<#assign colorClass="hover:bg-provider-paypal/10">
<#break>
<#case "slack">
<#assign colorClass="hover:bg-provider-slack/10">
<#break>
<#case "stackoverflow">
<#assign colorClass="hover:bg-provider-stackoverflow/10">
<#break>
<#case "twitter">
<#assign colorClass="hover:bg-provider-twitter/10">
<#break>
<#default>
<#assign colorClass="hover:bg-secondary-100">
</#switch>
<a
class="${colorClass} border border-secondary-200 flex justify-center py-2 rounded-lg hover:border-transparent"
data-provider="${provider.alias}"
href="${provider.loginUrl}"
type="button"
>
<#if providerIcons[provider.alias]??>
<div class="h-6 w-6">
<@providerIcons[provider.alias] />
</div>
<#else>
${provider.displayName!}
</#if>
</a>
</#list>
</div>
</#macro>

View File

@ -0,0 +1,29 @@
<#import "/assets/icons/chevron-down.ftl" as icon>
<#import "/components/atoms/link.ftl" as link>
<#macro kw currentLocale="" locales=[]>
<div class="relative" x-data="{ open: false }">
<@link.kw @click="open = true" color="secondary" component="button" type="button">
<div class="flex items-center">
<span class="mr-1 text-sm">${currentLocale}</span>
<@icon.kw />
</div>
</@link.kw>
<div
@click.away="open = false"
class="absolute bg-white bottom-0 -left-4 max-h-80 mb-6 overflow-y-scroll rounded-lg shadow-lg"
x-cloak
x-show="open"
>
<#list locales as locale>
<#if currentLocale != locale.label>
<div class="px-4 py-2">
<@link.kw color="secondary" href=locale.url size="small">
${locale.label}
</@link.kw>
</div>
</#if>
</#list>
</div>
</div>
</#macro>

View File

@ -0,0 +1,15 @@
<#import "/assets/icons/arrow-top-right-on-square.ftl" as icon>
<#import "/components/atoms/link.ftl" as link>
<#macro kw linkHref="" linkTitle="" name="">
<div class="flex items-center justify-center mb-4 space-x-2">
<span class="font-medium">${name}</span>
<@link.kw
color="primary"
href=linkHref
title=linkTitle
>
<@icon.kw />
</@link.kw>
</div>
</#macro>

View File

@ -0,0 +1,35 @@
<#macro kw script="">
<title>${msg("loginTitle", (realm.displayName!""))}</title>
<meta charset="utf-8">
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1">
<#if properties.meta?has_content>
<#list properties.meta?split(" ") as meta>
<meta name="${meta?split('==')[0]}" content="${meta?split('==')[1]}">
</#list>
</#if>
<#if properties.favicons?has_content>
<#list properties.favicons?split(" ") as favicon>
<link href="${url.resourcesPath}/${favicon?split('==')[0]}" rel="${favicon?split('==')[1]}">
</#list>
</#if>
<#if properties.styles?has_content>
<#list properties.styles?split(" ") as style>
<link href="${url.resourcesPath}/${style}" rel="stylesheet">
</#list>
</#if>
<#if script?has_content>
<script defer src="${url.resourcesPath}/${script}" type="module"></script>
</#if>
<#if properties.scripts?has_content>
<#list properties.scripts?split(" ") as script>
<script defer src="${url.resourcesPath}/${script}" type="module"></script>
</#list>
</#if>
</#macro>

View File

@ -0,0 +1,18 @@
<#import "template.ftl" as layout>
<#import "components/atoms/alert.ftl" as alert>
<#import "components/atoms/link.ftl" as link>
<@layout.registrationLayout displayMessage=false; section>
<#if section="header">
${kcSanitize(msg("errorTitle"))?no_esc}
<#elseif section="form">
<@alert.kw color="error">${kcSanitize(message.summary)?no_esc}</@alert.kw>
<#if !skipLink??>
<#if client?? && client.baseUrl?has_content>
<@link.kw color="secondary" href=client.baseUrl size="small">
${kcSanitize(msg("backToApplication"))?no_esc}
</@link.kw>
</#if>
</#if>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,5 @@
<#macro kw>
<#compress>
${msg("loginTotpDeviceName")} <#if totp.otpCredentials?size gte 1>*</#if>
</#compress>
</#macro>

View File

@ -0,0 +1,5 @@
<#macro kw>
<#compress>
${msg("authenticatorCode")} *
</#compress>
</#macro>

View File

@ -0,0 +1,11 @@
<#macro kw>
<#compress>
<#if !realm.loginWithEmailAllowed>
${msg("username")}
<#elseif !realm.registrationEmailAsUsername>
${msg("usernameOrEmail")}
<#else>
${msg("email")}
</#if>
</#compress>
</#macro>

View File

@ -0,0 +1,110 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<#import "components/atoms/link.ftl" as link>
<#import "features/labels/totp.ftl" as totpLabel>
<#import "features/labels/totp-device.ftl" as totpDeviceLabel>
<#assign totpLabel><@totpLabel.kw /></#assign>
<#assign totpDeviceLabel><@totpDeviceLabel.kw /></#assign>
<@layout.registrationLayout
displayMessage=!messagesPerField.existsError("totp", "userLabel")
displayRequiredFields=false
;
section
>
<#if section="header">
${msg("loginTotpTitle")}
<#elseif section="form">
<ol class="list-decimal pl-4 space-y-2">
<li class="space-y-2">
<p>${msg("loginTotpStep1")}</p>
<ul class="list-disc pl-4">
<#list totp.supportedApplications as app>
<li>${msg(app)}</li>
</#list>
</ul>
</li>
<#if mode?? && mode="manual">
<li>
<p>${msg("loginTotpManualStep2")}</p>
<p class="font-medium text-xl">${totp.totpSecretEncoded}</p>
</li>
<li>
<@link.kw color="primary" href=totp.qrUrl>
${msg("loginTotpScanBarcode")}
</@link.kw>
</li>
<li class="space-y-2">
<p>${msg("loginTotpManualStep3")}</p>
<ul class="list-disc pl-4">
<li>${msg("loginTotpType")}: ${msg("loginTotp." + totp.policy.type)}</li>
<li>${msg("loginTotpAlgorithm")}: ${totp.policy.getAlgorithmKey()}</li>
<li>${msg("loginTotpDigits")}: ${totp.policy.digits}</li>
<#if totp.policy.type="totp">
<li>${msg("loginTotpInterval")}: ${totp.policy.period}</li>
<#elseif totp.policy.type="hotp">
<li>${msg("loginTotpCounter")}: ${totp.policy.initialCounter}</li>
</#if>
</ul>
</li>
<#else>
<li>
<p>${msg("loginTotpStep2")}</p>
<img
alt="Figure: Barcode"
class="mx-auto"
src="data:image/png;base64, ${totp.totpSecretQrCode}"
>
<@link.kw color="primary" href=totp.manualUrl>
${msg("loginTotpUnableToScan")}
</@link.kw>
</li>
</#if>
<li>${msg("loginTotpStep3")}</li>
<li>${msg("loginTotpStep3DeviceName")}</li>
</ol>
<@form.kw action=url.loginAction method="post">
<input name="totpSecret" type="hidden" value="${totp.totpSecret}">
<#if mode??>
<input name="mode" type="hidden" value="${mode}">
</#if>
<@input.kw
autocomplete="off"
autofocus=true
invalid=messagesPerField.existsError("totp")
label=totpLabel
message=kcSanitize(messagesPerField.get("totp"))
name="totp"
required=false
type="text"
/>
<@input.kw
autocomplete="off"
invalid=messagesPerField.existsError("userLabel")
label=totpDeviceLabel
message=kcSanitize(messagesPerField.get("userLabel"))
name="userLabel"
required=false
type="text"
/>
<@buttonGroup.kw>
<#if isAppInitiatedAction??>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
<@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
${msg("doCancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,18 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/form.ftl" as form>
<@layout.registrationLayout; section>
<#if section="header">
${msg("confirmLinkIdpTitle")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<@button.kw color="primary" name="submitAction" type="submit" value="updateProfile">
${msg("confirmLinkIdpReviewProfile")}
</@button.kw>
<@button.kw color="primary" name="submitAction" type="submit" value="linkAccount">
${msg("confirmLinkIdpContinue", idpDisplayName)}
</@button.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,62 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<@layout.registrationLayout; section>
<#if section="header">
<#if client.attributes.logoUri??>
<img class="mb-4 mx-auto" src="${client.attributes.logoUri}"/>
</#if>
<p>
<#if client.name?has_content>
${msg("oauthGrantTitle", advancedMsg(client.name))}
<#else>
${msg("oauthGrantTitle", client.clientId)}
</#if>
</p>
<#elseif section="form">
<h3>${msg("oauthGrantRequest")}</h3>
<ul class="list-disc pl-4">
<#if oauth.clientScopesRequested??>
<#list oauth.clientScopesRequested as clientScope>
<li>
<#if !clientScope.dynamicScopeParameter??>
${advancedMsg(clientScope.consentScreenText)}
<#else>
${advancedMsg(clientScope.consentScreenText)}: <b>${clientScope.dynamicScopeParameter}</b>
</#if>
</li>
</#list>
</#if>
</ul>
<#if client.attributes.policyUri?? || client.attributes.tosUri??>
<h3>
<#if client.name?has_content>
${msg("oauthGrantInformation",advancedMsg(client.name))}
<#else>
${msg("oauthGrantInformation",client.clientId)}
</#if>
<#if client.attributes.tosUri??>
${msg("oauthGrantReview")}
<a href="${client.attributes.tosUri}" target="_blank">${msg("oauthGrantTos")}</a>
</#if>
<#if client.attributes.policyUri??>
${msg("oauthGrantReview")}
<a href="${client.attributes.policyUri}" target="_blank">${msg("oauthGrantPolicy")}</a>
</#if>
</h3>
</#if>
<@form.kw action=url.oauthAction method="post">
<input name="code" type="hidden" value="${oauth.code}">
<@buttonGroup.kw>
<@button.kw color="primary" name="accept" type="submit">
${msg("doYes")}
</@button.kw>
<@button.kw color="secondary" name="cancel" type="submit">
${msg("doNo")}
</@button.kw>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,50 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<#import "components/atoms/radio.ftl" as radio>
<#import "features/labels/totp.ftl" as totpLabel>
<#assign totpLabel><@totpLabel.kw /></#assign>
<@layout.registrationLayout
displayMessage=!messagesPerField.existsError("totp")
;
section
>
<#if section="header">
${msg("doLogIn")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<#if otpLogin.userOtpCredentials?size gt 1>
<div class="flex items-center space-x-4">
<#list otpLogin.userOtpCredentials as otpCredential>
<@radio.kw
checked=(otpCredential.id == otpLogin.selectedCredentialId)
id="kw-otp-credential-${otpCredential?index}"
label=otpCredential.userLabel
name="selectedCredentialId"
tabindex=otpCredential?index
value=otpCredential.id
/>
</#list>
</div>
</#if>
<@input.kw
autocomplete="off"
autofocus=true
invalid=messagesPerField.existsError("totp")
label=totpLabel
message=kcSanitize(messagesPerField.get("totp"))
name="otp"
type="text"
/>
<@buttonGroup.kw>
<@button.kw color="primary" name="submitAction" type="submit">
${msg("doLogIn")}
</@button.kw>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,18 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<@layout.registrationLayout; section>
<#if section="header">
${msg("pageExpiredTitle")}
<#elseif section="form">
<@buttonGroup.kw>
<@button.kw color="primary" component="a" href=url.loginRestartFlowUrl>
${msg("doTryAgain")}
</@button.kw>
<@button.kw color="secondary" component="a" href=url.loginAction>
${msg("doContinue")}
</@button.kw>
</@buttonGroup.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,39 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<#import "components/atoms/link.ftl" as link>
<@layout.registrationLayout displayMessage=!messagesPerField.existsError("password"); section>
<#if section="header">
${msg("doLogIn")}
<#elseif section="form">
<@form.kw
action=url.loginAction
method="post"
onsubmit="login.disabled = true; return true;"
>
<@input.kw
autofocus=true
invalid=messagesPerField.existsError("password")
label=msg("password")
message=kcSanitize(messagesPerField.get("password"))?no_esc
name="password"
type="password"
/>
<#if realm.resetPasswordAllowed>
<div class="flex items-center justify-between">
<@link.kw color="primary" href=url.loginResetCredentialsUrl size="small">
${msg("doForgotPassword")}
</@link.kw>
</div>
</#if>
<@buttonGroup.kw>
<@button.kw color="primary" name="login" type="submit">
${msg("doLogIn")}
</@button.kw>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,91 @@
<#import "template.ftl" as layout>
<#import "components/atoms/alert.ftl" as alert>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/checkbox.ftl" as checkbox>
<#import "components/atoms/form.ftl" as form>
<@layout.registrationLayout script="dist/recoveryCodes.js"; section>
<#if section="header">
${msg("recovery-code-config-header")}
<#elseif section="form">
<div class="space-y-6" x-data="recoveryCodes">
<@alert.kw color="warning">
<div class="space-y-2">
<h4 class="font-medium">${msg("recovery-code-config-warning-title")}</h4>
<p>${msg("recovery-code-config-warning-message")}</p>
</div>
</@alert.kw>
<ul class="columns-2 font-mono text-center" x-ref="codeList">
<#list recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesList as code>
<li>${code[0..3]}-${code[4..7]}-${code[8..]}</li>
</#list>
</ul>
<div class="flex items-stretch space-x-4 mb-4">
<@button.kw @click="print" color="secondary" size="small" type="button">
${msg("recovery-codes-print")}
</@button.kw>
<@button.kw @click="download" color="secondary" size="small" type="button">
${msg("recovery-codes-download")}
</@button.kw>
<@button.kw @click="copy" color="secondary" size="small" type="button">
${msg("recovery-codes-copy")}
</@button.kw>
</div>
<@form.kw action=url.loginAction method="post">
<input
name="generatedRecoveryAuthnCodes"
type="hidden"
value="${recoveryAuthnCodesConfigBean.generatedRecoveryAuthnCodesAsString}"
/>
<input
name="generatedAt"
type="hidden"
value="${recoveryAuthnCodesConfigBean.generatedAt?c}"
/>
<input
name="userLabel"
type="hidden"
value="${msg('recovery-codes-label-default')}"
/>
<@checkbox.kw
label=msg("recovery-codes-confirmation-message")
name="kcRecoveryCodesConfirmationCheck"
required="required"
x\-ref="confirmationCheck"
/>
<@buttonGroup.kw>
<#if isAppInitiatedAction??>
<@button.kw color="primary" type="submit">
${msg("recovery-codes-action-complete")}
</@button.kw>
<@button.kw
@click="$refs.confirmationCheck.required = false"
color="secondary"
name="cancel-aia"
type="submit"
value="true"
>
${msg("recovery-codes-action-cancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("recovery-codes-action-complete")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</div>
</#if>
</@layout.registrationLayout>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('recoveryCodes', {
downloadFileDate: '${msg("recovery-codes-download-file-date")}',
downloadFileDescription: '${msg("recovery-codes-download-file-description")}',
downloadFileHeader: '${msg("recovery-codes-download-file-header")}',
downloadFileName: 'kc-download-recovery-codes',
})
})
</script>

View File

@ -0,0 +1,26 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<@layout.registrationLayout; section>
<#if section="header">
${msg("auth-recovery-code-header")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<@input.kw
autocomplete="off"
autofocus=true
label=msg("auth-recovery-code-prompt", recoveryAuthnCodesInputBean.codeNumber?c)
name="recoveryCodeInput"
type="text"
/>
<@buttonGroup.kw>
<@button.kw color="primary" name="login" type="submit">
${msg("doLogIn")}
</@button.kw>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,48 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<#import "components/atoms/link.ftl" as link>
<#import "features/labels/username.ftl" as usernameLabel>
<#assign usernameLabel><@usernameLabel.kw /></#assign>
<@layout.registrationLayout
displayInfo=true
displayMessage=!messagesPerField.existsError("username")
;
section
>
<#if section="header">
${msg("emailForgotTitle")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<@input.kw
autocomplete=realm.loginWithEmailAllowed?string("email", "username")
autofocus=true
invalid=messagesPerField.existsError("username")
label=usernameLabel
message=kcSanitize(messagesPerField.get("username"))
name="username"
type="text"
value=(auth?has_content && auth.showUsername())?then(auth.attemptedUsername, '')
/>
<@buttonGroup.kw>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</@buttonGroup.kw>
</@form.kw>
<#elseif section="info">
<#if realm.loginWithEmailAllowed>
${msg("emailInstruction")}
<#else>
${msg("emailInstructionUsername")}
</#if>
<#elseif section="nav">
<@link.kw color="secondary" href=url.loginUrl size="small">
${kcSanitize(msg("backToLogin"))?no_esc}
</@link.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,64 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/checkbox.ftl" as checkbox>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<@layout.registrationLayout
displayMessage=!messagesPerField.existsError("password", "password-confirm")
;
section
>
<#if section="header">
${msg("updatePasswordTitle")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<input
autocomplete="username"
name="username"
type="hidden"
value="${username}"
>
<input autocomplete="current-password" name="password" type="hidden">
<@input.kw
autocomplete="new-password"
autofocus=true
invalid=messagesPerField.existsError("password", "password-confirm")
label=msg("passwordNew")
name="password-new"
type="password"
/>
<@input.kw
autocomplete="new-password"
invalid=messagesPerField.existsError("password-confirm")
label=msg("passwordConfirm")
message=kcSanitize(messagesPerField.get("password-confirm"))
name="password-confirm"
type="password"
/>
<#if isAppInitiatedAction??>
<@checkbox.kw
checked=true
label=msg("logoutOtherSessions")
name="logout-sessions"
value="on"
/>
</#if>
<@buttonGroup.kw>
<#if isAppInitiatedAction??>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
<@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
${msg("doCancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

View File

@ -0,0 +1,71 @@
<#import "template.ftl" as layout>
<#import "components/atoms/button.ftl" as button>
<#import "components/atoms/button-group.ftl" as buttonGroup>
<#import "components/atoms/form.ftl" as form>
<#import "components/atoms/input.ftl" as input>
<@layout.registrationLayout
displayMessage=!messagesPerField.existsError("email", "firstName", "lastName", "username")
;
section
>
<#if section="header">
${msg("loginProfileTitle")}
<#elseif section="form">
<@form.kw action=url.loginAction method="post">
<#if user.editUsernameAllowed>
<@input.kw
autocomplete="username"
autofocus=true
invalid=messagesPerField.existsError("username")
label=msg("username")
message=kcSanitize(messagesPerField.get("username"))
name="username"
type="text"
value=(user.username)!''
/>
</#if>
<@input.kw
autocomplete="email"
invalid=messagesPerField.existsError("email")
label=msg("email")
message=kcSanitize(messagesPerField.get("email"))
name="email"
type="email"
value=(user.email)!''
/>
<@input.kw
autocomplete="given-name"
invalid=messagesPerField.existsError("firstName")
label=msg("firstName")
message=kcSanitize(messagesPerField.get("firstName"))
name="firstName"
type="text"
value=(user.firstName)!''
/>
<@input.kw
autocomplete="family-name"
invalid=messagesPerField.existsError("lastName")
label=msg("lastName")
message=kcSanitize(messagesPerField.get("lastName"))
name="lastName"
type="text"
value=(user.lastName)!''
/>
<@buttonGroup.kw>
<#if isAppInitiatedAction??>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
<@button.kw color="secondary" name="cancel-aia" type="submit" value="true">
${msg("doCancel")}
</@button.kw>
<#else>
<@button.kw color="primary" type="submit">
${msg("doSubmit")}
</@button.kw>
</#if>
</@buttonGroup.kw>
</@form.kw>
</#if>
</@layout.registrationLayout>

Some files were not shown because too many files have changed in this diff Show More