Compare commits
No commits in common. "410a09aec6760f1e8a01680e48424559babfa2ec" and "4cc7f0bbbb492fde059cf777b77511db5df0411c" have entirely different histories.
410a09aec6
...
4cc7f0bbbb
|
@ -67,9 +67,3 @@ 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,6 +45,7 @@
|
||||||
"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 = {}) {
|
||||||
const browserLocale = navigator.language.split("-")[0];
|
let 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);
|
const i18n = createI18n(options)
|
||||||
setI18nLanguage(i18n, defaultLocale);
|
setI18nLanguage(i18n, defaultLocale)
|
||||||
return i18n;
|
return i18n
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setI18nLanguage(i18n, locale) {
|
export function setI18nLanguage(i18n, locale) {
|
||||||
loadLocaleMessages(i18n, locale);
|
loadLocaleMessages(i18n, locale);
|
||||||
if (i18n.mode === "legacy") {
|
if (i18n.mode === 'legacy') {
|
||||||
i18n.global.locale = locale;
|
i18n.global.locale = locale
|
||||||
} else {
|
} else {
|
||||||
i18n.global.locale.value = locale;
|
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
|
||||||
const context = (window as any).kcContext as KcContextBase.Common;
|
let 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,26 +1,3 @@
|
||||||
<#--
|
|
||||||
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>
|
||||||
|
@ -95,9 +72,6 @@ SOFTWARE.
|
||||||
) || (
|
) || (
|
||||||
["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,40 +1,37 @@
|
||||||
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:
|
"noSupport": "Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung.",
|
||||||
"Es scheint, als würde ihr Browser WebAuthn nicht unterstützen. Bitte nutzen Sie einen anderen Browser für die Anmeldung.",
|
"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.",
|
||||||
noSupportOtherMethod:
|
"error": "Bei der Anmeldung mit ihrem Gerät ist etwas schief gelaufen. Bitte versuchen Sie es erneut, 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, oder wählen Sie eine andere Anmeldeoption.",
|
|
||||||
error:
|
|
||||||
"Bei der Anmeldung mit ihrem Gerät ist etwas schief gelaufen. Bitte versuchen Sie es erneut, oder wählen Sie eine andere Anmeldeoption.",
|
|
||||||
},
|
},
|
||||||
redirect: {
|
"redirect": {
|
||||||
message: "Sie werden weitergeleitet...",
|
"message": "Sie werden weitergeleitet..."
|
||||||
},
|
}
|
||||||
};
|
}
|
|
@ -1,40 +1,37 @@
|
||||||
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:
|
"noSupport": "It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser.",
|
||||||
"It seems that your browser doesn't support WebAuthn. Please try logging in with a different browser.",
|
"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.",
|
||||||
noSupportOtherMethod:
|
"error": "Something went wrong during authentication using your device. Please try again, or use a different login method."
|
||||||
"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.",
|
|
||||||
error:
|
|
||||||
"Something went wrong during authentication using your device. Please try again, or use a different login method.",
|
|
||||||
},
|
},
|
||||||
redirect: {
|
"redirect": {
|
||||||
message: "Redirecting...",
|
"message": "Redirecting..."
|
||||||
},
|
}
|
||||||
};
|
}
|
|
@ -1,29 +1,4 @@
|
||||||
/* 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,14 +22,11 @@
|
||||||
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
|
context.realm.loginWithEmailAllowed ? $t("login.usernameOrEmail") : $t("login.username")
|
||||||
? $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'
|
||||||
|
@ -40,9 +37,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")
|
>{{ $t("login.password") }}</label
|
||||||
}}</label>
|
>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
@ -63,9 +60,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")
|
>{{ $t("login.rememberMe") }}</label
|
||||||
}}</label>
|
>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
@ -81,7 +78,7 @@
|
||||||
</layout>
|
</layout>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, onMounted, nextTick } from "vue";
|
import { defineComponent } 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";
|
||||||
|
@ -97,19 +94,6 @@ 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,9 +12,7 @@
|
||||||
stroke-dasharray="164 56" />
|
stroke-dasharray="164 56" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="mt-4 text-xl text-center text-gray-700">
|
<h1 class="mt-4 text-xl text-center text-gray-700">{{ $t("redirect.message") }}</h1>
|
||||||
{{ $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: authenticator.createdAt }) }}
|
{{ $t("webauthn.registered", {date: moment(authenticator.createdAt)}) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +59,9 @@
|
||||||
</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>{{ $t("webauthn.error") }}</ErrorBox>
|
<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">
|
||||||
|
@ -75,6 +77,7 @@ 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",
|
||||||
|
@ -95,6 +98,9 @@ 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, {
|
||||||
|
@ -103,7 +109,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
prepareAuthenticate() {
|
prepareAuthenticate() {
|
||||||
if (this.context.isUserIdentified) {
|
if (this.context.isUserIdentified) {
|
||||||
const allowedCredentials: PublicKeyCredentialDescriptor[] = [];
|
const allowedCredentials = new Array<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 }),
|
||||||
|
@ -115,7 +121,9 @@ export default defineComponent({
|
||||||
this.authenticate([]);
|
this.authenticate([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async authenticate(allowedAuthenticators: PublicKeyCredentialDescriptor[]) {
|
async authenticate(
|
||||||
|
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