<template>
  <layout>
    <h1
      v-if="context.isUserIdentified"
      class="text-3xl font-semibold text-center text-gray-700">
      Welcome {{ context.auth?.attemptedUsername }}!
    </h1>
    <h1 v-else class="text-3xl font-semibold text-center text-gray-700">
      Welcome!
    </h1>
    <div v-if="webauthnSupported && !error">
      <p class="mt-5 mb-5 text-center">
        Please use one of your registered devices to continue:
      </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">
            Registered: {{ new Date(authenticator.createdAt).toLocaleString() }}
          </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">
        Authenticate
      </button>
      <p
        class="mt-5 text-sm text-center"
        v-if="context.auth?.showTryAnotherWayLink">
        Don't have your device at hand?
        <a
          :onclick="tryAnotherWay"
          class="text-blue-500 hover:underline cursor-pointer"
          >Try another way.</a
        >
      </p>
    </div>
    <div v-if="!webauthnSupported">
      <p class="mt-5 text-center">
        It seems that your browser doesn't support WebAuthn. Please try logging
        in with a different browser<span
          v-if="!context.auth?.showTryAnotherWayLink"
          >.</span
        ><span v-else
          >, or try a different login method using the button below.</span
        >
      </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">
        Try another method
      </button>
    </div>
    <div v-if="error" class="mt-5 flex flex-col">
      <ErrorBox
        >Something went wrong during authentication using your device.</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">
        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";

export default defineComponent({
  name: "Login",
  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();
      this.formPost(this.context.url.loginAction, {
        tryAnotherWay: "on",
      });
    },
    prepareAuthenticate() {
      if (this.context.isUserIdentified) {
        const allowedCredentials = new Array<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: Array<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 }
          ),
        };

        this.formPost(this.context.url.loginAction, postData);
      } catch (err) {
        this.error = true;
      }
    },
    retryAuth() {
      this.error = false;
    },
    formPost(url: string, data: object) {
      const form = document.createElement("form");
      form.method = "post";
      form.action = url;

      for (const key in data) {
        if (Object.prototype.hasOwnProperty.call(data, key)) {
          const hiddenField = document.createElement("input");
          hiddenField.type = "hidden";
          hiddenField.name = key;
          hiddenField.value = data[key];

          form.appendChild(hiddenField);
        }
      }
      document.body.appendChild(form);
      form.submit();
    },
  },
});
</script>
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
