Compare commits
5 commits
4cc7f0bbbb
...
410a09aec6
Author | SHA1 | Date | |
---|---|---|---|
Oliver Traber | 410a09aec6 | ||
Oliver Traber | 627cfe6462 | ||
Oliver Traber | f2bc66c3ea | ||
Oliver Traber | 0a562763e6 | ||
Oliver Traber | 10cfff043b |
|
@ -67,3 +67,9 @@ If you want to add new pages, there are some things to be aware of.
|
||||||
If the browser language is supported, it will be loaded automatically. Otherwise, English is loaded by default.
|
If the browser language is supported, it will be loaded automatically. Otherwise, English is loaded by default.
|
||||||
|
|
||||||
To add new languages, the corresponding file must be added under `static/login/resources/locales`. Then the language must be added to `SUPPORT_LOCALES` in `functions/i18n.ts`.
|
To add new languages, the corresponding file must be added under `static/login/resources/locales`. Then the language must be added to `SUPPORT_LOCALES` in `functions/i18n.ts`.
|
||||||
|
|
||||||
|
### Credits
|
||||||
|
|
||||||
|
Special thanks to the following projects, without which the development of keycloak-modern-login would not be possible:
|
||||||
|
|
||||||
|
- [Keycloakify](https://github.com/keycloakify/keycloakify), from which the [`ftl_object_to_js_code_declaring_an_object`](src/static/login/baselayout.ftl) function and [Keycloak context type definitions](src/types/context.ts) are derived.
|
|
@ -45,7 +45,6 @@
|
||||||
"webpack-cli": "^4.9.1"
|
"webpack-cli": "^4.9.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"moment": "^2.29.4",
|
|
||||||
"rfc4648": "^1.5.2",
|
"rfc4648": "^1.5.2",
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
"vue-i18n": "9"
|
"vue-i18n": "9"
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
import { nextTick } from "vue"
|
import { nextTick } from "vue";
|
||||||
import { createI18n } from "vue-i18n"
|
import { createI18n } from "vue-i18n";
|
||||||
import { KcContextBase } from "~/types/context";
|
import { KcContextBase } from "~/types/context";
|
||||||
|
|
||||||
export const SUPPORT_LOCALES = ["en", "de"]
|
export const SUPPORT_LOCALES = ["en", "de"];
|
||||||
|
|
||||||
export function setupI18n(options = {}) {
|
export function setupI18n(options = {}) {
|
||||||
let browserLocale = navigator.language.split("-")[0];
|
const browserLocale = navigator.language.split("-")[0];
|
||||||
let defaultLocale = "en";
|
let defaultLocale = "en";
|
||||||
if (SUPPORT_LOCALES.includes(browserLocale)) {
|
if (SUPPORT_LOCALES.includes(browserLocale)) {
|
||||||
defaultLocale = browserLocale;
|
defaultLocale = browserLocale;
|
||||||
}
|
|
||||||
|
|
||||||
const i18n = createI18n(options)
|
|
||||||
setI18nLanguage(i18n, defaultLocale)
|
|
||||||
return i18n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setI18nLanguage(i18n, locale) {
|
const i18n = createI18n(options);
|
||||||
loadLocaleMessages(i18n, locale);
|
setI18nLanguage(i18n, defaultLocale);
|
||||||
if (i18n.mode === 'legacy') {
|
return i18n;
|
||||||
i18n.global.locale = locale
|
}
|
||||||
} else {
|
|
||||||
i18n.global.locale.value = locale
|
export function setI18nLanguage(i18n, locale) {
|
||||||
}
|
loadLocaleMessages(i18n, locale);
|
||||||
|
if (i18n.mode === "legacy") {
|
||||||
|
i18n.global.locale = locale;
|
||||||
|
} else {
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadLocaleMessages(i18n, locale) {
|
export async function loadLocaleMessages(i18n, locale) {
|
||||||
// load locale messages with dynamic import
|
// load locale messages with dynamic import
|
||||||
let context = (window as any).kcContext as KcContextBase.Common;
|
const context = (window as any).kcContext as KcContextBase.Common;
|
||||||
const messages = await import(
|
const messages = await import(
|
||||||
/* webpackIgnore: true */`${context.url.resourcesPath}/locales/${locale}.js`
|
/* webpackIgnore: true */ `${context.url.resourcesPath}/locales/${locale}.js`
|
||||||
)
|
);
|
||||||
|
|
||||||
// set locale and locale message
|
// set locale and locale message
|
||||||
i18n.global.setLocaleMessage(locale, messages.content)
|
i18n.global.setLocaleMessage(locale, messages.content);
|
||||||
|
|
||||||
return nextTick()
|
return nextTick();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,26 @@
|
||||||
|
<#--
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 GitHub user u/garronej
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
-->
|
||||||
<#function are_same_path path searchedPath>
|
<#function are_same_path path searchedPath>
|
||||||
<#if path?size != searchedPath?size>
|
<#if path?size != searchedPath?size>
|
||||||
<#return false>
|
<#return false>
|
||||||
|
@ -69,9 +92,12 @@
|
||||||
key == "identityProviderBrokerCtx" &&
|
key == "identityProviderBrokerCtx" &&
|
||||||
are_same_path(path, []) &&
|
are_same_path(path, []) &&
|
||||||
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(pageId)
|
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(pageId)
|
||||||
) || (
|
) || (
|
||||||
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
|
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
|
||||||
are_same_path(path, ["realm"])
|
are_same_path(path, ["realm"])
|
||||||
|
) || (
|
||||||
|
["password"]?seq_contains(key) &&
|
||||||
|
are_same_path(path, ["login"])
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
<#continue>
|
<#continue>
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
export const content = {
|
export const content = {
|
||||||
"login": {
|
login: {
|
||||||
"title": "Mit Ihrem Konto anmelden",
|
title: "Mit Ihrem Konto anmelden",
|
||||||
"noAccount": "Sie haben noch kein Konto?",
|
noAccount: "Sie haben noch kein Konto?",
|
||||||
"signupLink": "Jetzt registrieren.",
|
signupLink: "Jetzt registrieren.",
|
||||||
"username": "Benutzername",
|
username: "Benutzername",
|
||||||
"usernameOrEmail": "Benutzername oder E-Mail-Adresse",
|
usernameOrEmail: "Benutzername oder E-Mail-Adresse",
|
||||||
"password": "Passwort",
|
password: "Passwort",
|
||||||
"forgotPassword": "Passwort vergessen?",
|
forgotPassword: "Passwort vergessen?",
|
||||||
"rememberMe": "Angemeldet bleiben",
|
rememberMe: "Angemeldet bleiben",
|
||||||
"login": "Anmelden",
|
login: "Anmelden",
|
||||||
"rety": "Erneut versuchen",
|
rety: "Erneut versuchen",
|
||||||
"welcome": "Willkommen!"
|
welcome: "Willkommen!",
|
||||||
},
|
},
|
||||||
"2fa": {
|
"2fa": {
|
||||||
"selectFactor": "Bitte wählen Sie einen zweiten Faktor aus:",
|
selectFactor: "Bitte wählen Sie einen zweiten Faktor aus:",
|
||||||
"hwSecKey": "Hardware Sicherheits-Schlüssel",
|
hwSecKey: "Hardware Sicherheits-Schlüssel",
|
||||||
"hwSecKeyDesc": "Verwenden Sie ein WebAuthn kompatibles Gerät",
|
hwSecKeyDesc: "Verwenden Sie ein WebAuthn kompatibles Gerät",
|
||||||
"otp": "Authenticator App",
|
otp: "Authenticator App",
|
||||||
"otpDesc": "Verwenden Sie einen Einmalcode aus ihrem Authenticator",
|
otpDesc: "Verwenden Sie einen Einmalcode aus ihrem Authenticator",
|
||||||
"recoveryCode": "Wiederherstellungsschlüssel",
|
recoveryCode: "Wiederherstellungsschlüssel",
|
||||||
"recoveryCodeDesc": "Verwenden Sie einen ihrer Wiederherstellungsschlüssel",
|
recoveryCodeDesc: "Verwenden Sie einen ihrer Wiederherstellungsschlüssel",
|
||||||
"tryAnotherWay": "Eine andere Option nutzen."
|
tryAnotherWay: "Eine andere Option nutzen.",
|
||||||
},
|
},
|
||||||
"webauthn": {
|
webauthn: {
|
||||||
"title": "Bitte verwenden Sie eines ihrer Geräte, um fortzufahren:",
|
title: "Bitte verwenden Sie eines ihrer Geräte, um fortzufahren:",
|
||||||
"registered": "Registriert: {date}",
|
registered: "Registriert: {date}",
|
||||||
"authenticate": "Fortfahren",
|
authenticate: "Fortfahren",
|
||||||
"noDevice": "Kein Gerät zur Hand?",
|
noDevice: "Kein Gerät zur Hand?",
|
||||||
"noSupport": "Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung.",
|
noSupport:
|
||||||
"noSupportOtherMethod": "Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung, oder wählen Sie eine andere Anmeldeoption.",
|
"Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung.",
|
||||||
"error": "Bei der Anmeldung mit ihrem Gerät ist etwas schief gelaufen. Bitte versuchen Sie es erneut, oder wählen Sie eine andere Anmeldeoption."
|
noSupportOtherMethod:
|
||||||
},
|
"Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung, oder wählen Sie eine andere Anmeldeoption.",
|
||||||
"redirect": {
|
error:
|
||||||
"message": "Sie werden weitergeleitet..."
|
"Bei der Anmeldung mit ihrem Gerät ist etwas schief gelaufen. Bitte versuchen Sie es erneut, oder wählen Sie eine andere Anmeldeoption.",
|
||||||
}
|
},
|
||||||
}
|
redirect: {
|
||||||
|
message: "Sie werden weitergeleitet...",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
export const content = {
|
export const content = {
|
||||||
"login": {
|
login: {
|
||||||
"title": "Login to your account",
|
title: "Login to your account",
|
||||||
"noAccount": "Don't have an account yet?",
|
noAccount: "Don't have an account yet?",
|
||||||
"signupLink": "Sign up.",
|
signupLink: "Sign up.",
|
||||||
"username": "Username",
|
username: "Username",
|
||||||
"usernameOrEmail": "Email or Username",
|
usernameOrEmail: "Email or Username",
|
||||||
"password": "Password",
|
password: "Password",
|
||||||
"forgotPassword": "Forgot Password?",
|
forgotPassword: "Forgot Password?",
|
||||||
"rememberMe": "Remember me",
|
rememberMe: "Remember me",
|
||||||
"login": "Login",
|
login: "Login",
|
||||||
"rety": "Retry",
|
rety: "Retry",
|
||||||
"welcome": "Welcome!"
|
welcome: "Welcome!",
|
||||||
},
|
},
|
||||||
"2fa": {
|
"2fa": {
|
||||||
"selectFactor": "Please select a second factor you would like to use:",
|
selectFactor: "Please select a second factor you would like to use:",
|
||||||
"hwSecKey": "Hardware Security Key",
|
hwSecKey: "Hardware Security Key",
|
||||||
"hwSecKeyDesc": "Authenticate using a WebAuthn capable device",
|
hwSecKeyDesc: "Authenticate using a WebAuthn capable device",
|
||||||
"otp": "Authenticator App",
|
otp: "Authenticator App",
|
||||||
"otpDesc": "Authenticate using a one time code from your authenticator app",
|
otpDesc: "Authenticate using a one time code from your authenticator app",
|
||||||
"recoveryCode": "Recovery Code",
|
recoveryCode: "Recovery Code",
|
||||||
"recoveryCodeDesc": "Authenticate using one of your recovery codes",
|
recoveryCodeDesc: "Authenticate using one of your recovery codes",
|
||||||
"tryAnotherWay": "Try another method."
|
tryAnotherWay: "Try another method.",
|
||||||
},
|
},
|
||||||
"webauthn": {
|
webauthn: {
|
||||||
"title": "Please use one of your registered devices to continue:",
|
title: "Please use one of your registered devices to continue:",
|
||||||
"registered": "Registered: {date}",
|
registered: "Registered: {date}",
|
||||||
"authenticate": "Continue",
|
authenticate: "Continue",
|
||||||
"noDevice": "Don't have your device at hand?",
|
noDevice: "Don't have your device at hand?",
|
||||||
"noSupport": "It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser.",
|
noSupport:
|
||||||
"noSupportOtherMethod": "It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser, or try a different login method using the button below.",
|
"It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser.",
|
||||||
"error": "Something went wrong during authentication using your device. Please try again, or use a different login method."
|
noSupportOtherMethod:
|
||||||
},
|
"It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser, or try a different login method using the button below.",
|
||||||
"redirect": {
|
error:
|
||||||
"message": "Redirecting..."
|
"Something went wrong during authentication using your device. Please try again, or use a different login method.",
|
||||||
}
|
},
|
||||||
}
|
redirect: {
|
||||||
|
message: "Redirecting...",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,29 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 GitHub user u/garronej
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
/** Take theses type definition with a grain of salt.
|
/** Take theses type definition with a grain of salt.
|
||||||
* Some values might be undefined on some pages.
|
* Some values might be undefined on some pages.
|
||||||
* (ex: url.loginAction is undefined on error.ftl)
|
* (ex: url.loginAction is undefined on error.ftl)
|
||||||
|
|
|
@ -4,6 +4,6 @@ import { setupI18n } from "~/functions/i18n";
|
||||||
|
|
||||||
if ((window as any).kcContext) {
|
if ((window as any).kcContext) {
|
||||||
const app = createApp(index);
|
const app = createApp(index);
|
||||||
app.use(setupI18n())
|
app.use(setupI18n());
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,14 @@
|
||||||
method="post">
|
method="post">
|
||||||
<div>
|
<div>
|
||||||
<label for="username" class="block text-sm text-gray-800">{{
|
<label for="username" class="block text-sm text-gray-800">{{
|
||||||
context.realm.loginWithEmailAllowed ? $t("login.usernameOrEmail") : $t("login.username")
|
context.realm.loginWithEmailAllowed
|
||||||
|
? $t("login.usernameOrEmail")
|
||||||
|
: $t("login.username")
|
||||||
}}</label>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
name="username"
|
name="username"
|
||||||
type="text"
|
type="text"
|
||||||
|
ref="focus"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
context.realm.loginWithEmailAllowed
|
context.realm.loginWithEmailAllowed
|
||||||
? 'jane.doe@example.com'
|
? 'jane.doe@example.com'
|
||||||
|
@ -37,9 +40,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="password" class="block text-sm text-gray-800"
|
<label for="password" class="block text-sm text-gray-800">{{
|
||||||
>{{ $t("login.password") }}</label
|
$t("login.password")
|
||||||
>
|
}}</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
@ -60,9 +63,9 @@
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="rememberMe"
|
name="rememberMe"
|
||||||
class="w-4 h-4 text-blue-500 bg-gray-100 border-gray-400 rounded dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" />
|
class="w-4 h-4 text-blue-500 bg-gray-100 border-gray-400 rounded dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600" />
|
||||||
<label for="rememberMe" class="block ml-3 text-sm text-gray-800"
|
<label for="rememberMe" class="block ml-3 text-sm text-gray-800">{{
|
||||||
>{{ $t("login.rememberMe") }}</label
|
$t("login.rememberMe")
|
||||||
>
|
}}</label>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@ -78,7 +81,7 @@
|
||||||
</layout>
|
</layout>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent, ref, onMounted, nextTick } from "vue";
|
||||||
import Layout from "~/components/Layout.vue";
|
import Layout from "~/components/Layout.vue";
|
||||||
import ErrorBox from "~/components/ErrorBox.vue";
|
import ErrorBox from "~/components/ErrorBox.vue";
|
||||||
import type { KcContextBase } from "~/types/context";
|
import type { KcContextBase } from "~/types/context";
|
||||||
|
@ -94,6 +97,19 @@ export default defineComponent({
|
||||||
context: (window as any).kcContext as KcContextBase.Login,
|
context: (window as any).kcContext as KcContextBase.Login,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const focus = ref(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
focus.value.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
focus,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -4,6 +4,6 @@ import { setupI18n } from "~/functions/i18n";
|
||||||
|
|
||||||
if ((window as any).kcContext) {
|
if ((window as any).kcContext) {
|
||||||
const app = createApp(index);
|
const app = createApp(index);
|
||||||
app.use(setupI18n())
|
app.use(setupI18n());
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
stroke-dasharray="164 56" />
|
stroke-dasharray="164 56" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-4 text-xl text-center text-gray-700">{{ $t("redirect.message") }}</h1>
|
<h1 class="mt-4 text-xl text-center text-gray-700">
|
||||||
|
{{ $t("redirect.message") }}
|
||||||
|
</h1>
|
||||||
</layout>
|
</layout>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -4,6 +4,6 @@ import { setupI18n } from "~/functions/i18n";
|
||||||
|
|
||||||
if ((window as any).kcContext) {
|
if ((window as any).kcContext) {
|
||||||
const app = createApp(index);
|
const app = createApp(index);
|
||||||
app.use(setupI18n())
|
app.use(setupI18n());
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="w-full ml-5 flex flex-col">
|
<div class="w-full ml-5 flex flex-col">
|
||||||
<p>{{ authenticator.label }}</p>
|
<p>{{ authenticator.label }}</p>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
{{ $t("webauthn.registered", {date: moment(authenticator.createdAt)}) }}
|
{{ $t("webauthn.registered", { date: authenticator.createdAt }) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,9 +59,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error" class="mt-5 flex flex-col">
|
<div v-if="error" class="mt-5 flex flex-col">
|
||||||
<ErrorBox
|
<ErrorBox>{{ $t("webauthn.error") }}</ErrorBox>
|
||||||
>{{ $t("webauthn.error") }}</ErrorBox
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
:onclick="retryAuth"
|
:onclick="retryAuth"
|
||||||
class="w-full mt-5 px-4 py-2 tracking-wide text-white transition-colors duration-200 transform bg-blue-500 rounded-md hover:bg-blue-400 focus:outline-none focus:bg-blue-400">
|
class="w-full mt-5 px-4 py-2 tracking-wide text-white transition-colors duration-200 transform bg-blue-500 rounded-md hover:bg-blue-400 focus:outline-none focus:bg-blue-400">
|
||||||
|
@ -77,7 +75,6 @@ import ErrorBox from "~/components/ErrorBox.vue";
|
||||||
import type { KcContextBase } from "~/types/context";
|
import type { KcContextBase } from "~/types/context";
|
||||||
import { base64url } from "rfc4648";
|
import { base64url } from "rfc4648";
|
||||||
import { formPost } from "~/functions/utils";
|
import { formPost } from "~/functions/utils";
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "WebAuthnAuthenticate",
|
name: "WebAuthnAuthenticate",
|
||||||
|
@ -98,9 +95,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
moment(date: string) : string {
|
|
||||||
return moment(date).toDate().toLocaleString();
|
|
||||||
},
|
|
||||||
tryAnotherWay(e: Event) {
|
tryAnotherWay(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
formPost(this.context.url.loginAction, {
|
formPost(this.context.url.loginAction, {
|
||||||
|
@ -109,7 +103,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
prepareAuthenticate() {
|
prepareAuthenticate() {
|
||||||
if (this.context.isUserIdentified) {
|
if (this.context.isUserIdentified) {
|
||||||
const allowedCredentials = new Array<PublicKeyCredentialDescriptor>();
|
const allowedCredentials: PublicKeyCredentialDescriptor[] = [];
|
||||||
this.context.authenticators.authenticators.forEach(authenticator => {
|
this.context.authenticators.authenticators.forEach(authenticator => {
|
||||||
allowedCredentials.push({
|
allowedCredentials.push({
|
||||||
id: base64url.parse(authenticator.credentialId, { loose: true }),
|
id: base64url.parse(authenticator.credentialId, { loose: true }),
|
||||||
|
@ -121,9 +115,7 @@ export default defineComponent({
|
||||||
this.authenticate([]);
|
this.authenticate([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async authenticate(
|
async authenticate(allowedAuthenticators: PublicKeyCredentialDescriptor[]) {
|
||||||
allowedAuthenticators: Array<PublicKeyCredentialDescriptor>
|
|
||||||
) {
|
|
||||||
const publicKey: PublicKeyCredentialRequestOptions = {
|
const publicKey: PublicKeyCredentialRequestOptions = {
|
||||||
rpId: this.context.rpId,
|
rpId: this.context.rpId,
|
||||||
challenge: base64url.parse(this.context.challenge, { loose: true }),
|
challenge: base64url.parse(this.context.challenge, { loose: true }),
|
||||||
|
|
Loading…
Reference in a new issue