<template>
  <layout>
    <h1 class="text-3xl font-semibold text-center text-gray-700">
      {{ $t("login.welcome") }}
    </h1>
    <div v-if="webauthnSupported && !error">
      <p class="mt-5 mb-5 text-center">
        {{ $t("webauthn.title") }}
      </p>
      <div
        v-for="authenticator in context.authenticators.authenticators"
        :key="authenticator.credentialId"
        class="w-full h-20 mb-5 bg-white shadow-md rounded-md flex flex-row items-center">
        <svg
          class="w-12 h-12 ml-4 text-gray-700"
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          fill="currentColor"
          viewBox="0 0 16 16">
          <path
            d="M6 .5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4H6v-4ZM7 1v1h1V1H7Zm2 0v1h1V1H9ZM5.5 5a.5.5 0 0 0-.5.5V15a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V5.5a.5.5 0 0 0-.5-.5h-6Z" />
        </svg>
        <div class="w-full ml-5 flex flex-col">
          <p>{{ authenticator.label }}</p>
          <p class="text-sm">
            {{ $t("webauthn.registered", { date: authenticator.createdAt }) }}
          </p>
        </div>
      </div>
      <button
        :onclick="prepareAuthenticate"
        class="w-full 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">
        {{ $t("webauthn.authenticate") }}
      </button>
      <p
        class="mt-5 text-sm text-center"
        v-if="context.auth?.showTryAnotherWayLink">
        {{ $t("webauthn.noDevice") }}
        <a
          :onclick="tryAnotherWay"
          class="text-blue-500 hover:underline cursor-pointer"
          >{{ $t("2fa.tryAnotherWay") }}</a
        >
      </p>
    </div>
    <div v-if="!webauthnSupported">
      <p v-if="context.auth?.showTryAnotherWayLink" class="mt-5 text-center">
        {{ $t("webauthn.noSupportOtherMethod") }}
      </p>
      <p v-else class="mt-5 text-center">
        {{ $t("webauthn.noSupport") }}
      </p>
      <button
        v-if="context.auth?.showTryAnotherWayLink"
        :onclick="tryAnotherWay"
        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">
        {{ $t("2fa.tryAnotherWay") }}
      </button>
    </div>
    <div v-if="error" class="mt-5 flex flex-col">
      <ErrorBox>{{ $t("webauthn.error") }}</ErrorBox>
      <button
        :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">
        {{ $t("login.retry") }}
      </button>
    </div>
  </layout>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Layout from "~/components/Layout.vue";
import ErrorBox from "~/components/ErrorBox.vue";
import type { KcContextBase } from "~/types/context";
import { base64url } from "rfc4648";
import { formPost } from "~/functions/utils";

export default defineComponent({
  name: "WebAuthnAuthenticate",
  components: {
    Layout,
    ErrorBox,
  },
  data() {
    return {
      context: (window as any).kcContext as KcContextBase.WebauthnAuthenticate,
      webauthnSupported: true,
      error: false,
    };
  },
  mounted: function () {
    if (typeof PublicKeyCredential === "undefined") {
      this.webauthnSupported = false;
    }
  },
  methods: {
    tryAnotherWay(e: Event) {
      e.preventDefault();
      formPost(this.context.url.loginAction, {
        tryAnotherWay: "on",
      });
    },
    prepareAuthenticate() {
      if (this.context.isUserIdentified) {
        const allowedCredentials: PublicKeyCredentialDescriptor[] = [];
        this.context.authenticators.authenticators.forEach(authenticator => {
          allowedCredentials.push({
            id: base64url.parse(authenticator.credentialId, { loose: true }),
            type: "public-key",
          });
        });
        this.authenticate(allowedCredentials);
      } else {
        this.authenticate([]);
      }
    },
    async authenticate(
      allowedAuthenticators: PublicKeyCredentialDescriptor[]
    ) {
      const publicKey: PublicKeyCredentialRequestOptions = {
        rpId: this.context.rpId,
        challenge: base64url.parse(this.context.challenge, { loose: true }),
      };
      if (this.context.createTimeout !== "0")
        publicKey.timeout = Number(this.context.createTimeout) * 1000;
      if (allowedAuthenticators.length)
        publicKey.allowCredentials = allowedAuthenticators;
      if (this.context.userVerification !== "not specified")
        publicKey.userVerification = this.context.userVerification;

      try {
        const resultRaw = await navigator.credentials.get({ publicKey });
        if (!resultRaw || resultRaw.type !== "public-key") return;
        const result = resultRaw as PublicKeyCredential;
        if (!("authenticatorData" in result.response)) return;
        const response = result.response as AuthenticatorAssertionResponse;
        const clientDataJSON = response.clientDataJSON;
        const authenticatorData = response.authenticatorData;
        const signature = response.signature;

        const postData = {
          credentialId: result.id,
          clientDataJSON: base64url.stringify(new Uint8Array(clientDataJSON), {
            pad: false,
          }),
          authenticatorData: base64url.stringify(
            new Uint8Array(authenticatorData),
            { pad: false }
          ),
          signature: base64url.stringify(new Uint8Array(signature), {
            pad: false,
          }),
          userHandle: base64url.stringify(
            new Uint8Array(response.userHandle!),
            { pad: false }
          ),
        };

        formPost(this.context.url.loginAction, postData);
      } catch (err) {
        this.error = true;
      }
    },
    retryAuth() {
      this.error = false;
    },
  },
});
</script>
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
