[{"/home/oliver/git-repos/keycloak-theme-vuejs/src/components/ErrorBox.vue":"1","/home/oliver/git-repos/keycloak-theme-vuejs/src/components/Layout.vue":"2","/home/oliver/git-repos/keycloak-theme-vuejs/src/shims-vue.d.ts":"3","/home/oliver/git-repos/keycloak-theme-vuejs/src/types/context.ts":"4","/home/oliver/git-repos/keycloak-theme-vuejs/src/views/login/index.ts":"5","/home/oliver/git-repos/keycloak-theme-vuejs/src/views/login/index.vue":"6","/home/oliver/git-repos/keycloak-theme-vuejs/src/views/webauthn-authenticate/index.ts":"7","/home/oliver/git-repos/keycloak-theme-vuejs/src/views/webauthn-authenticate/index.vue":"8","/home/oliver/git-repos/keycloak-theme-vuejs/tailwind.config.js":"9","/home/oliver/git-repos/keycloak-theme-vuejs/webpack.config.js":"10"},{"size":334,"mtime":1676764735273,"results":"11","hashOfConfig":"12"},{"size":703,"mtime":1676744043737,"results":"13","hashOfConfig":"12"},{"size":146,"mtime":1676768217528,"results":"14","hashOfConfig":"12"},{"size":10105,"mtime":1676767602409,"results":"15","hashOfConfig":"12"},{"size":155,"mtime":1676744043933,"results":"16","hashOfConfig":"12"},{"size":3456,"mtime":1676764735457,"results":"17","hashOfConfig":"12"},{"size":155,"mtime":1676744043997,"results":"18","hashOfConfig":"12"},{"size":6913,"mtime":1676768178300,"results":"19","hashOfConfig":"12"},{"size":144,"mtime":1676744044069,"results":"20","hashOfConfig":"12"},{"size":3277,"mtime":1676767942024,"results":"21","hashOfConfig":"12"},{"filePath":"22","messages":"23","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"yepsnx",{"filePath":"24","messages":"25","errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"26"},{"filePath":"27","messages":"28","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"29","messages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"31","messages":"32","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"33","messages":"34","errorCount":5,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"35"},{"filePath":"36","messages":"37","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":12,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"40"},{"filePath":"41","messages":"42","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"43","messages":"44","errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"45"},"/home/oliver/git-repos/keycloak-theme-vuejs/src/components/ErrorBox.vue",[],"/home/oliver/git-repos/keycloak-theme-vuejs/src/components/Layout.vue",["46","47"],"<template>\n  <div\n    :class=\"\n      'flex flex-col min-h-screen w-full md:w-1/2 lg:w-1/3 max-w-none md:max-w-2xl bg-gray-50 md:rounded-r-3xl shadow-2xl bg-[url(\\'' +\n      context.url.resourcesPath +\n      '/img/background.jpg\\')] bg-no-repeat bg-center bg-cover font-sans overflow-hidden'\n    \">\n    <div class=\"flex flex-grow flex-col justify-center w-full px-8 md:px-20\">\n      <slot></slot>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\">\nimport { defineComponent } from \"vue\";\nimport { KcContextBase } from \"~/types/context\";\n\nexport default defineComponent({\n  name: \"Layout\",\n  data() {\n    return {\n      context: (window as any).kcContext as KcContextBase.Common,\n    };\n  },\n});\n</script>\n","/home/oliver/git-repos/keycloak-theme-vuejs/src/shims-vue.d.ts",[],"/home/oliver/git-repos/keycloak-theme-vuejs/src/types/context.ts",[],"/home/oliver/git-repos/keycloak-theme-vuejs/src/views/login/index.ts",[],"/home/oliver/git-repos/keycloak-theme-vuejs/src/views/login/index.vue",["48","49","50","51","52"],"<template>\n  <layout>\n    <h1 class=\"mb-8 text-3xl font-semibold text-center text-gray-700\">\n      Login to your account\n    </h1>\n    <p\n      v-if=\"context.realm.registrationAllowed\"\n      class=\"mt-4 text-sm font-light text-center text-gray-700\">\n      Don't have an account yet?\n      <a\n        :href=\"context.url.registrationUrl\"\n        class=\"font-medium text-blue-500 hover:underline\"\n        >Sign up</a\n      >\n    </p>\n    <div v-if=\"context.message?.type == 'error'\">\n      <ErrorBox>{{ context.message?.summary }}</ErrorBox>\n    </div>\n    <form\n      class=\"flex flex-col justify-center mt-6\"\n      :action=\"context.url.loginAction\"\n      method=\"post\">\n      <div>\n        <label for=\"username\" class=\"block text-sm text-gray-800\">{{\n          context.realm.loginWithEmailAllowed ? \"Email or Username\" : \"Username\"\n        }}</label>\n        <input\n          name=\"username\"\n          type=\"text\"\n          :placeholder=\"\n            context.realm.loginWithEmailAllowed\n              ? 'jane.doe@example.com'\n              : 'JDoe'\n          \"\n          :value=\"context.login.username ? context.login.username : ''\"\n          class=\"block w-full px-4 py-2 mt-2 text-gray-800 bg-white border rounded-md focus:border-blue-500 focus:ring-transparent focus:outline-none focus:ring focus:ring-opacity-40\" />\n      </div>\n      <div class=\"mt-4\">\n        <div>\n          <label for=\"password\" class=\"block text-sm text-gray-800\"\n            >Password</label\n          >\n          <input\n            type=\"password\"\n            name=\"password\"\n            placeholder=\"&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;\"\n            class=\"block w-full px-4 py-2 mt-2 text-gray-800 bg-white border rounded-md focus:border-blue-500 focus:ring-transparent focus:outline-none focus:ring focus:ring-opacity-40\" />\n        </div>\n        <a\n          v-if=\"context.realm.resetPasswordAllowed\"\n          :href=\"context.url.loginResetCredentialsUrl\"\n          class=\"text-xs text-gray-600 hover:underline\"\n          >Forgot Password?</a\n        >\n      </div>\n      <div\n        v-if=\"context.realm.rememberMe\"\n        class=\"mt-5 flex flex-row items-center\">\n        <input\n          type=\"checkbox\"\n          name=\"rememberMe\"\n          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\" />\n        <label for=\"rememberMe\" class=\"block ml-3 text-sm text-gray-800\"\n          >Remember me</label\n        >\n      </div>\n      <input\n        type=\"hidden\"\n        name=\"credentialId\"\n        :value=\"\n          context.auth.selectedCredential ? context.auth.selectedCredential : ''\n        \" />\n      <button\n        class=\"w-full mt-6 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\">\n        Login\n      </button>\n    </form>\n  </layout>\n</template>\n<script lang=\"ts\">\nimport { defineComponent } from \"vue\";\nimport Layout from \"~/components/Layout.vue\";\nimport ErrorBox from \"~/components/ErrorBox.vue\";\nimport type { KcContextBase } from \"~/types/context\";\n\nexport default defineComponent({\n  name: \"Login\",\n  components: {\n    Layout,\n    ErrorBox,\n  },\n  data() {\n    return {\n      context: (window as any).kcContext as KcContextBase.Login,\n    };\n  },\n});\n</script>\n<style>\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n</style>\n","/home/oliver/git-repos/keycloak-theme-vuejs/src/views/webauthn-authenticate/index.ts",[],"/home/oliver/git-repos/keycloak-theme-vuejs/src/views/webauthn-authenticate/index.vue",["53","54","55","56","57","58","59","60","61","62","63","64"],"<template>\n  <layout>\n    <h1\n      v-if=\"context.isUserIdentified\"\n      class=\"text-3xl font-semibold text-center text-gray-700\">\n      Welcome {{ context.auth?.attemptedUsername }}!\n    </h1>\n    <h1 v-else class=\"text-3xl font-semibold text-center text-gray-700\">\n      Welcome!\n    </h1>\n    <div v-if=\"webauthnSupported && !error\">\n      <p class=\"mt-5 mb-5 text-center\">\n        Please use one of your registered devices to continue:\n      </p>\n      <div\n        v-for=\"authenticator in context.authenticators.authenticators\"\n        :key=\"authenticator.credentialId\"\n        class=\"w-full h-20 mb-5 bg-white shadow-md rounded-md flex flex-row items-center\">\n        <svg\n          class=\"w-12 h-12 ml-4 text-gray-700\"\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"16\"\n          height=\"16\"\n          fill=\"currentColor\"\n          viewBox=\"0 0 16 16\">\n          <path\n            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\" />\n        </svg>\n        <div class=\"w-full ml-5 flex flex-col\">\n          <p>{{ authenticator.label }}</p>\n          <p class=\"text-sm\">\n            Registered: {{ new Date(authenticator.createdAt).toLocaleString() }}\n          </p>\n        </div>\n      </div>\n      <button\n        :onclick=\"prepareAuthenticate\"\n        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\">\n        Authenticate\n      </button>\n      <p\n        class=\"mt-5 text-sm text-center\"\n        v-if=\"context.auth?.showTryAnotherWayLink\">\n        Don't have your device at hand?\n        <a\n          :onclick=\"tryAnotherWay\"\n          class=\"text-blue-500 hover:underline cursor-pointer\"\n          >Try another way.</a\n        >\n      </p>\n    </div>\n    <div v-if=\"!webauthnSupported\">\n      <p class=\"mt-5 text-center\">\n        It seems that your browser doesn't support WebAuthn. Please try logging\n        in with a different browser<span\n          v-if=\"!context.auth?.showTryAnotherWayLink\"\n          >.</span\n        ><span v-else\n          >, or try a different login method using the button below.</span\n        >\n      </p>\n      <button\n        v-if=\"context.auth?.showTryAnotherWayLink\"\n        :onclick=\"tryAnotherWay\"\n        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\">\n        Try another method\n      </button>\n    </div>\n    <div v-if=\"error\" class=\"mt-5 flex flex-col\">\n      <ErrorBox\n        >Something went wrong during authentication using your device.</ErrorBox\n      >\n      <button\n        :onclick=\"retryAuth\"\n        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\">\n        Retry\n      </button>\n    </div>\n  </layout>\n</template>\n<script lang=\"ts\">\nimport { defineComponent } from \"vue\";\nimport Layout from \"~/components/Layout.vue\";\nimport ErrorBox from \"~/components/ErrorBox.vue\";\nimport type { KcContextBase } from \"~/types/context\";\nimport { base64url } from \"rfc4648\";\n\nexport default defineComponent({\n  name: \"Login\",\n  components: {\n    Layout,\n    ErrorBox,\n  },\n  data() {\n    return {\n      context: (window as any).kcContext as KcContextBase.WebauthnAuthenticate,\n      webauthnSupported: true,\n      error: false,\n    };\n  },\n  mounted: function () {\n    if (typeof PublicKeyCredential === \"undefined\") {\n      this.webauthnSupported = false;\n    }\n  },\n  methods: {\n    tryAnotherWay(e: Event) {\n      e.preventDefault();\n      this.formPost(this.context.url.loginAction, {\n        tryAnotherWay: \"on\",\n      });\n    },\n    prepareAuthenticate() {\n      if (this.context.isUserIdentified) {\n        const allowedCredentials = new Array<PublicKeyCredentialDescriptor>();\n        this.context.authenticators.authenticators.forEach(authenticator => {\n          allowedCredentials.push({\n            id: base64url.parse(authenticator.credentialId, { loose: true }),\n            type: \"public-key\",\n          });\n        });\n        this.authenticate(allowedCredentials);\n      } else {\n        this.authenticate([]);\n      }\n    },\n    async authenticate(\n      allowedAuthenticators: Array<PublicKeyCredentialDescriptor>\n    ) {\n      const publicKey: PublicKeyCredentialRequestOptions = {\n        rpId: this.context.rpId,\n        challenge: base64url.parse(this.context.challenge, { loose: true }),\n      };\n      if (this.context.createTimeout !== \"0\")\n        publicKey.timeout = Number(this.context.createTimeout) * 1000;\n      if (allowedAuthenticators.length)\n        publicKey.allowCredentials = allowedAuthenticators;\n      if (this.context.userVerification !== \"not specified\")\n        publicKey.userVerification = this.context.userVerification;\n\n      try {\n        const resultRaw = await navigator.credentials.get({ publicKey });\n        if (!resultRaw || resultRaw.type !== \"public-key\") return;\n        const result = resultRaw as PublicKeyCredential;\n        if (!(\"authenticatorData\" in result.response)) return;\n        const response = result.response as AuthenticatorAssertionResponse;\n        const clientDataJSON = response.clientDataJSON;\n        const authenticatorData = response.authenticatorData;\n        const signature = response.signature;\n\n        const postData = {\n          credentialId: result.id,\n          clientDataJSON: base64url.stringify(new Uint8Array(clientDataJSON), {\n            pad: false,\n          }),\n          authenticatorData: base64url.stringify(\n            new Uint8Array(authenticatorData),\n            { pad: false }\n          ),\n          signature: base64url.stringify(new Uint8Array(signature), {\n            pad: false,\n          }),\n          userHandle: base64url.stringify(\n            new Uint8Array(response.userHandle!),\n            { pad: false }\n          ),\n        };\n\n        this.formPost(this.context.url.loginAction, postData);\n      } catch (err) {\n        this.error = true;\n      }\n    },\n    retryAuth() {\n      this.error = false;\n    },\n    formPost(url: string, data: object) {\n      const form = document.createElement(\"form\");\n      form.method = \"post\";\n      form.action = url;\n\n      for (const key in data) {\n        if (Object.prototype.hasOwnProperty.call(data, key)) {\n          const hiddenField = document.createElement(\"input\");\n          hiddenField.type = \"hidden\";\n          hiddenField.name = key;\n          hiddenField.value = data[key];\n\n          form.appendChild(hiddenField);\n        }\n      }\n      document.body.appendChild(form);\n      form.submit();\n    },\n  },\n});\n</script>\n<style>\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n</style>\n","/home/oliver/git-repos/keycloak-theme-vuejs/tailwind.config.js",[],"/home/oliver/git-repos/keycloak-theme-vuejs/webpack.config.js",["65"],"const path = require(\"path\");\nconst { VueLoaderPlugin } = require(\"vue-loader\");\nconst HtmlWebpackPlugin = require(\"html-webpack-plugin\");\nconst { CleanWebpackPlugin } = require(\"clean-webpack-plugin\");\nconst CopyWebpackPlugin = require(\"copy-webpack-plugin\");\n\nconst THEME_NAME = \"modern-login\";\nconst customPages = [\"login\", \"webauthn-authenticate\"];\n\nmodule.exports = function (env, argv) {\n  const isDevelopment = argv.mode !== \"production\";\n  return {\n    entry: () => {\n      const entryList = {};\n      for (const entry of customPages) {\n        entryList[entry] = path.resolve(\n          __dirname,\n          \"src\",\n          \"views\",\n          entry,\n          \"index.ts\"\n        );\n      }\n      return entryList;\n    },\n    output: {\n      path: path.resolve(__dirname, \"dist\", \"theme\", THEME_NAME, \"login\"),\n      filename: \"resources/js/[name].js\",\n      publicPath: \"/\",\n    },\n    devtool: \"inline-cheap-module-source-map\",\n    resolve: {\n      extensions: [\".ts\", \".tsx\", \".js\", \".vue\", \".json\", \".scss\"],\n      alias: {\n        \"~\": path.resolve(__dirname, \"src\"),\n      },\n    },\n    mode: isDevelopment ? \"development\" : \"production\",\n    watch: isDevelopment,\n    module: {\n      rules: [\n        {\n          test: /\\.vue$/,\n          loader: \"vue-loader\",\n          options: {\n            sourceMap: isDevelopment,\n            extract: false,\n          },\n        },\n        {\n          test: /\\.(ts|tsx)$/,\n          exclude: /node_modules/,\n          use: {\n            loader: \"babel-loader\",\n            options: {\n              presets: [\"@babel/preset-env\", \"@babel/preset-typescript\"],\n              plugins: [\n                \"@babel/plugin-transform-runtime\",\n                \"@babel/plugin-transform-typescript\",\n              ],\n            },\n          },\n        },\n        {\n          test: /\\.css$/,\n          use: [\n            \"style-loader\",\n            \"css-loader\",\n            {\n              loader: \"postcss-loader\",\n              options: {\n                postcssOptions: {\n                  plugins: {\n                    tailwindcss: {},\n                    autoprefixer: {},\n                  },\n                },\n              },\n            },\n          ],\n        },\n      ],\n    },\n    plugins: [\n      new CleanWebpackPlugin(),\n      new VueLoaderPlugin(),\n      ...customPages.map(\n        entry =>\n          new HtmlWebpackPlugin({\n            inject: false,\n            template: path.resolve(\n              __dirname,\n              \"src\",\n              \"views\",\n              entry,\n              \"index.ftl\"\n            ),\n            filename: `${entry}.ftl`,\n            minify: false,\n          })\n      ),\n      new CopyWebpackPlugin({\n        patterns: [\n          {\n            from: path.resolve(__dirname, \"src\", \"static\", \"login\"),\n            to: path.resolve(__dirname, \"dist\", \"theme\", THEME_NAME, \"login\"),\n          },\n          {\n            from: path.resolve(__dirname, \"src\", \"static\", \"jar\"),\n            to: path.resolve(__dirname, \"dist\"),\n          },\n        ],\n      }),\n    ],\n    ...(isDevelopment\n      ? {}\n      : {\n          optimization: {\n            removeAvailableModules: false,\n            removeEmptyChunks: false,\n            splitChunks: false,\n          },\n        }),\n  };\n};\n",{"ruleId":"66","severity":2,"message":"67","line":4,"column":1,"nodeType":"68","messageId":"69","endLine":4,"endColumn":136},{"ruleId":"70","severity":2,"message":"71","line":18,"column":9,"nodeType":"72","messageId":"73","endLine":18,"endColumn":17},{"ruleId":"66","severity":2,"message":"74","line":36,"column":1,"nodeType":"68","messageId":"69","endLine":36,"endColumn":187},{"ruleId":"66","severity":2,"message":"75","line":47,"column":1,"nodeType":"68","messageId":"69","endLine":47,"endColumn":189},{"ruleId":"66","severity":2,"message":"76","line":62,"column":1,"nodeType":"68","messageId":"69","endLine":62,"endColumn":143},{"ruleId":"66","severity":2,"message":"77","line":74,"column":1,"nodeType":"68","messageId":"69","endLine":74,"endColumn":183},{"ruleId":"70","severity":2,"message":"78","line":87,"column":9,"nodeType":"72","messageId":"73","endLine":87,"endColumn":16},{"ruleId":"66","severity":2,"message":"79","line":27,"column":1,"nodeType":"68","messageId":"69","endLine":27,"endColumn":174},{"ruleId":"66","severity":2,"message":"80","line":38,"column":1,"nodeType":"68","messageId":"69","endLine":38,"endColumn":178},{"ruleId":"66","severity":2,"message":"77","line":65,"column":1,"nodeType":"68","messageId":"69","endLine":65,"endColumn":183},{"ruleId":"66","severity":2,"message":"77","line":75,"column":1,"nodeType":"68","messageId":"69","endLine":75,"endColumn":183},{"ruleId":"70","severity":2,"message":"78","line":89,"column":9,"nodeType":"72","messageId":"73","endLine":89,"endColumn":16},{"ruleId":"81","severity":2,"message":"82","line":115,"column":36,"nodeType":"83","messageId":"84","endLine":115,"endColumn":78},{"ruleId":"85","severity":2,"message":"86","line":115,"column":46,"nodeType":"87","messageId":"88","endLine":115,"endColumn":75},{"ruleId":"89","severity":2,"message":"90","line":127,"column":5,"nodeType":"91","messageId":"92","endLine":173,"endColumn":6},{"ruleId":"85","severity":2,"message":"86","line":128,"column":36,"nodeType":"87","messageId":"88","endLine":128,"endColumn":65},{"ruleId":"85","severity":2,"message":"93","line":130,"column":24,"nodeType":"87","messageId":"88","endLine":130,"endColumn":57},{"ruleId":"85","severity":2,"message":"94","line":144,"column":37,"nodeType":"87","messageId":"88","endLine":144,"endColumn":56},{"ruleId":"85","severity":2,"message":"95","line":146,"column":45,"nodeType":"87","messageId":"88","endLine":146,"endColumn":75},{"ruleId":"89","severity":2,"message":"96","line":10,"column":18,"nodeType":"97","messageId":"92","endLine":125,"endColumn":2},"max-len","This line has a length of 135. Maximum allowed is 120.","Program","max","vue/multi-word-component-names","Component name \"Layout\" should always be multi-word.","Literal","unexpected","This line has a length of 186. Maximum allowed is 120.","This line has a length of 188. Maximum allowed is 120.","This line has a length of 142. Maximum allowed is 120.","This line has a length of 182. Maximum allowed is 120.","Component name \"Login\" should always be multi-word.","This line has a length of 173. Maximum allowed is 120.","This line has a length of 177. Maximum allowed is 120.","no-array-constructor","The array literal notation [] is preferable.","NewExpression","preferLiteral","no-undef","'PublicKeyCredentialDescriptor' is not defined.","Identifier","undef","max-lines-per-function","Async method 'authenticate' has too many lines (44). Maximum allowed is 30.","Property","exceed","'PublicKeyCredentialRequestOptions' is not defined.","'PublicKeyCredential' is not defined.","'AuthenticatorAssertionResponse' is not defined.","Function has too many lines (116). Maximum allowed is 30.","FunctionExpression"]