MOB-42 Redid the whole frontend part in Vue

This commit is contained in:
TanelOrumaa 2021-12-06 21:08:15 +02:00
parent 7daea4b6c2
commit da2dbeb0fc
25 changed files with 426 additions and 321 deletions

View File

@ -18,6 +18,8 @@
<kotlin.version>1.5.31</kotlin.version>
<caffeine.version>2.8.5</caffeine.version>
<javaxcache.version>1.1.1</javaxcache.version>
<node.version>v16.13.0</node.version>
<npm.version>8.1.4</npm.version>
</properties>
<dependencies>
<dependency>
@ -115,6 +117,91 @@
</dependency>
</dependencies>
</plugin>
<!-- Plugin to install node and npm and then build the vue project -->
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.12.0</version>
<executions>
<execution>
<id>Install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<npmVersion>${npm.version}</npmVersion>
</configuration>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>install</arguments>
</configuration>
</execution>
<execution>
<id>npm build</id>
<goals>
<goal>npm</goal>
</goals>
<phase>process-resources</phase>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
<configuration>
<nodeVersion>${node.version}</nodeVersion>
<workingDirectory>src/demo-website</workingDirectory>
<!-- <installDirectory>src/demo-website/dist</installDirectory>-->
</configuration>
</plugin>
<!-- Plugin to copy built vue project from src/frontend/dist to target/classes/static -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>Copy web-eid.js file to Vue root folder.</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>src/demo-website/src</outputDirectory>
<resources>
<resource>
<directory>src/demo-website/node_modules/@web-eid/web-eid-library/dist/es</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>Copy Vue frontend into Spring Boot target static folder</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/classes/static</outputDirectory>
<resources>
<resource>
<directory>src/demo-website/dist</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

Binary file not shown.

View File

@ -10,10 +10,15 @@
"dependencies": {
"@web-eid/web-eid-library": "../../../../web-eid.js/",
"core-js": "^3.6.5",
"vue": "^3.0.0"
"js-cookie": "^3.0.1",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
@ -1729,17 +1734,6 @@
"node": ">= 6"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
"integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==",
"dev": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@soda/friendly-errors-webpack-plugin": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz",
@ -2567,6 +2561,11 @@
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"node_modules/@vue/devtools-api": {
"version": "6.0.0-beta.20.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.20.1.tgz",
"integrity": "sha512-R2rfiRY+kZugzWh9ZyITaovx+jpU4vgivAEAiz80kvh3yviiTU3CBuGuyWpSwGz9/C7TkSWVM/FtQRGlZ16n8Q=="
},
"node_modules/@vue/preload-webpack-plugin": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@ -8669,6 +8668,14 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true
},
"node_modules/js-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==",
"engines": {
"node": ">=12"
}
},
"node_modules/js-message": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
@ -12104,6 +12111,11 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"node_modules/shvl": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.3.tgz",
"integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw=="
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@ -14151,6 +14163,17 @@
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"node_modules/vue-router": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.12.tgz",
"integrity": "sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.18"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -14173,6 +14196,37 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"node_modules/vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"dependencies": {
"@vue/devtools-api": "^6.0.0-beta.11"
},
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/vuex-persistedstate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz",
"integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==",
"dependencies": {
"deepmerge": "^4.2.2",
"shvl": "^2.0.3"
},
"peerDependencies": {
"vuex": "^3.0 || ^4.0.0-rc"
}
},
"node_modules/vuex-persistedstate/node_modules/deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@ -16370,13 +16424,6 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
"dev": true
},
"@popperjs/core": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
"integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==",
"dev": true,
"peer": true
},
"@soda/friendly-errors-webpack-plugin": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz",
@ -17081,6 +17128,11 @@
}
}
},
"@vue/devtools-api": {
"version": "6.0.0-beta.20.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.20.1.tgz",
"integrity": "sha512-R2rfiRY+kZugzWh9ZyITaovx+jpU4vgivAEAiz80kvh3yviiTU3CBuGuyWpSwGz9/C7TkSWVM/FtQRGlZ16n8Q=="
},
"@vue/preload-webpack-plugin": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
@ -21944,6 +21996,11 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true
},
"js-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.1.tgz",
"integrity": "sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw=="
},
"js-message": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
@ -24809,6 +24866,11 @@
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
"dev": true
},
"shvl": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/shvl/-/shvl-2.0.3.tgz",
"integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw=="
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@ -26491,6 +26553,14 @@
}
}
},
"vue-router": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.12.tgz",
"integrity": "sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.18"
}
},
"vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@ -26515,6 +26585,30 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true
},
"vuex": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
},
"vuex-persistedstate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz",
"integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==",
"requires": {
"deepmerge": "^4.2.2",
"shvl": "^2.0.3"
},
"dependencies": {
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
}
}
},
"watchpack": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",

View File

@ -10,10 +10,15 @@
"dependencies": {
"@web-eid/web-eid-library": "../../../../web-eid.js/",
"core-js": "^3.6.5",
"vue": "^3.0.0"
"js-cookie": "^3.0.1",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.2",
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",

View File

@ -0,0 +1,26 @@
<template>
<router-view/>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>

View File

@ -8,7 +8,16 @@
<p class="text-center">Read more from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
</div>
<div class="justify-content-center d-flex">
<button type="button" class="btn btn-lg btn-dark" v-on:click="authenticate">Authenticate</button>
<button type="button" class="btn loginButton btn-dark" v-on:click="authenticate">
<div v-if="loading" class="d-flex justify-content-center">
<div class="spinner-border text-light spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<span v-else>Authenticate</span>
</button>
</div>
<div class="btn-group-sm d-flex justify-content-center" role="group" aria-label="Basic radio toggle button group">
<input type="radio" class="btn-check" name="btnradio" id="btnCardReader" autocomplete="off" v-on:click="useCardReader">
@ -18,7 +27,6 @@
<label class="btn btn-outline-secondary" for="btnApp">using Android App</label>
</div>
<div id="canvas"></div>
<p>Token: {{ csrftoken }}</p>
</div>
</template>
@ -26,39 +34,41 @@
<script>
import * as webeid from '../web-eid.js';
import router from "@/router";
export default {
name: 'Login',
name: 'LoginComponent',
props: {
"csrftoken": String,
"csrfHeaderName": String,
},
data() {
return {
useApp: true,
useAndroidApp: true,
loading: false,
}
},
methods: {
useApp: function() {
this.useApp = true;
this.useAndroidApp = true;
},
useCardReader: function() {
this.useApp = false;
this.useAndroidApp = false;
},
authenticate: async function () {
const csrfToken = document.querySelector('#csrftoken').content;
const csrfHeaderName = document.querySelector('#csrfheadername').content;
this.loading = true;
const options = {
getAuthChallengeUrl: window.location.origin + "/auth/challenge",
postAuthTokenUrl: window.location.origin + "/auth/login",
getAuthSuccessUrl: window.location.origin + "/auth/login",
useAuthApp: this.useApp,
useAuthApp: this.useAndroidApp,
headers: {
[csrfHeaderName]: csrfToken
}
[this.csrfHeaderName]: this.csrftoken
},
};
console.log(options);
@ -66,29 +76,42 @@ export default {
try {
const response = await webeid.authenticate(options);
console.log("Authentication successful! Response:", response);
window.location.href = "/welcome";
this.loading = false;
this.$store.commit("setLoggedIn", true);
await router.push("welcome");
} catch (error) {
console.log("Authentication failed! Error:", error);
alert(error.message);
this.loading = false;
throw error;
}
}
},
mounted() {
fetch("/auth/challenge")
.then((response) => response.text()
).then((data) => {
console.log(data)
this.msg = data
}
)
computed: {
isLoggedIn() {
return this.$store.authenticated;
},
loading() {
return this.loading;
}
}
}
</script>
<style scoped>
div {
.container > div {
margin-top: 2vh;
}
.loginButton {
height: 4vh;
width: 20vh;
line-height: 3vh;
}
.loginButton > p {
font-size: 3vh;
text-align: center;
}
</style>

View File

@ -1,18 +1,36 @@
<template>
<!-- As a heading -->
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<nav class="navbar navbar-dark bg-dark container-fluid flex-row">
<div class="">
<span class="navbar-brand mb-0 h1">Mobile authentication demo</span>
</div>
<div v-if="isLoggedIn" class="nav-item">
<button type="button" class="btn btn-light" v-on:click="logOut">Log out</button>
</div>
</nav>
</template>
<script>
import router from "@/router";
export default {
name: "Navbar"
name: "Navbar",
computed: {
isLoggedIn() {
return this.$store.getters.getAuthenticated;
}
},
methods: {
logOut: function () {
this.$store.commit("setLoggedIn", false);
router.push("/");
}
}
}
</script>
<style scoped>
nav {
height: 5vh;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<div class="container container-md d-flex flex-column">
<div>
<h3 class="text-center">Congratulations, you logged into the site. Log out to try again.</h3>
<p class="text-center">Read more from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
</div>
</div>
</template>
<script>
export default {
name: 'WelcomeComponent',
props: {
"csrftoken": String,
},
computed: {
isLoggedIn() {
return this.$store.getters.getAuthenticated;
}
}
}
</script>
<style scoped>
div {
margin-top: 2vh;
}
</style>

View File

@ -0,0 +1,50 @@
import {createApp} from 'vue';
import App from './App.vue';
import {createStore} from 'vuex'
import BootstrapVue3 from 'bootstrap-vue-3'
import createPersistedState from "vuex-persistedstate";
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
import router from "./router/index";
// Create a new store instance.
const store = createStore({
state() {
return {
authenticated: false,
}
},
mutations: {
setLoggedIn(state, isLoggedIn) {
console.log("Setting logged in: " + isLoggedIn);
state.authenticated = isLoggedIn;
}
},
getters: {
getAuthenticated: state => {
return state.authenticated;
}
},
plugins: [createPersistedState()],
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.state.authenticated) {
next({name: 'Login'})
} else {
next() // go to wherever I'm going
}
} else {
next() // does not require auth, make sure to always call next()!
}
})
const app = createApp(App)
app.use(BootstrapVue3)
app.use(router)
app.use(store)
app.mount('#app')

View File

@ -1,10 +0,0 @@
import { createApp } from 'vue';
import App from './App.vue';
import BootstrapVue3 from 'bootstrap-vue-3'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
const app = createApp(App)
app.use(BootstrapVue3)
app.mount('#app')

View File

@ -1,10 +0,0 @@
import { createApp } from 'vue';
import App from './App.vue';
import BootstrapVue3 from 'bootstrap-vue-3'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
const app = createApp(App)
app.use(BootstrapVue3)
app.mount('#app')

View File

@ -0,0 +1,31 @@
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login.vue'
import Welcome from "@/views/Welcome";
const routes = [
{
path: '/',
name: 'Login',
component: Login,
meta: {
requiresAuth: false
}
},
{
path: '/welcome',
name: 'Welcome',
component: Welcome,
meta: {
requiresAuth: true
}
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router

View File

@ -1,16 +1,16 @@
<template>
<Navbar/>
<hello-world v-bind:csrftoken="csrf_token()"/>
<LoginComponent/>
</template>
<script>
import HelloWorld from '../../components/Login.vue'
import LoginComponent from '@/components/Login'
import Navbar from "@/components/Navbar";
export default {
name: 'App',
name: 'Login',
components: {
HelloWorld,
LoginComponent,
Navbar
},
methods: {

View File

@ -1,16 +1,16 @@
<template>
<Navbar/>
<hello-world v-bind:csrftoken="csrf_token()"/>
<WelcomeComponent/>
</template>
<script>
import HelloWorld from '../../components/Login.vue'
import WelcomeComponent from '@/components/Welcome'
import Navbar from "@/components/Navbar";
export default {
name: 'App',
name: 'Welcome',
components: {
HelloWorld,
WelcomeComponent,
Navbar
},
methods: {

View File

@ -8776,7 +8776,9 @@ class WebExtensionService {
onAckTimeout(pending) {
var _a, _b;
console.log("onAckTimeout", pending.message.action);
if (pending.message.authApp && pending.message.authApp == true) {
console.log("Pending message");
console.log(pending.message.authApp);
if (pending.message.useAuthApp && pending.message.useAuthApp == true) {
(_a = pending.reject) === null || _a === void 0 ? void 0 : _a.call(pending, new AuthAppNotInstalledError());
}
else {

View File

@ -17,20 +17,4 @@ module.exports = {
}
}
},
pages: {
index: {
entry: "./src/pages/home/main.js",
template: "public/index.html",
filename: "index.html",
title: "Home",
chunks: [],
},
legal: {
entry: "./src/pages/legal/main.js",
template: "public/index.html",
filename: "legal.html",
title: "Legal",
chunks:,
}
}
}

View File

@ -13,6 +13,7 @@ import org.webeid.security.validator.AuthTokenValidator
import org.webeid.security.validator.AuthTokenValidatorBuilder
import java.io.IOException
import java.net.URI
import java.net.URL
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
@ -39,7 +40,7 @@ class ValidationConfiguration {
private val NONCE_TTL_MINUTES: Long = 5
private val CACHE_NAME = "nonceCache"
private val CERTS_RESOURCE_PATH = "/certs/"
private val CERTS_RESOURCE_PATH = "/certs"
private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
private val TRUSTSTORE_PASSWORD = "changeit"
companion object {
@ -65,6 +66,7 @@ class ValidationConfiguration {
LOG.warn("Creating new cache.")
cache = createNonceCache(cacheManager)
}
return cache
}

View File

@ -1,20 +0,0 @@
package com.tarkvaratehnika.demobackend.web
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
@Controller
class LoginController {
@GetMapping
fun login(model : Model) : String {
model.addAttribute("intentUrl", ApplicationConfiguration.AUTH_APP_LAUNCH_INTENT)
model.addAttribute("challengeUrl", ApplicationConfiguration.CHALLENGE_ENDPOINT_URL)
model.addAttribute("originUrl", ApplicationConfiguration.WEBSITE_ORIGIN_URL)
model.addAttribute("loggedInUrl", "/signature")
model.addAttribute("authenticationRequestUrl", ApplicationConfiguration.AUTHENTICATION_ENDPOINT_URL)
return "index"
}
}

View File

@ -1,20 +0,0 @@
package com.tarkvaratehnika.demobackend.web
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
@Controller
class SignatureController {
@PreAuthorize("hasAuthority('$ROLE_USER')")
@GetMapping("signature")
fun signature(model : Model) : String {
// model.addAttribute("intentUrl", ApplicationConfiguration.AUTH_APP_LAUNCH_INTENT)
// model.addAttribute("challengeUrl", ApplicationConfiguration.CHALLENGE_ENDPOINT_URL)
return "signature"
}
}

View File

@ -30,7 +30,7 @@ class AuthenticationController {
}
@GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE])
fun getAuthenticated(@RequestParam challenge: String) : Authentication? {
fun getAuthenticated(headers: String) : Authentication? {
val auth = WebEidAuthentication.fromChallenge(challenge)
if (auth == null) {
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Not allowed.")

View File

@ -1,29 +0,0 @@
html {
font-size: 2vh;
}
.navbar {
padding-left: 1rem;
padding-right: 1rem;
}
.cont {
display: grid;
width: 80%;
padding-top: 10%;
margin-left: auto;
margin-right: auto;
justify-items: center;
}
h4 {
margin: 10%;
}
#loginButton {
width: 40%;
}
.cont > * {
margin: 1rem;
}

View File

@ -1,14 +0,0 @@
window.onload = () => {
// Add event listener for login button.
let loginButton = document.getElementById("loginButton");
if (loginButton != null) {
loginButton.addEventListener("click", () => {
let action = loginButton.getAttribute("data-action");
loginButton.setAttribute("disabled", "true");
loginButton.textContent = "Logging in";
launchAuthApp(action);
})
}
}

View File

@ -1,71 +0,0 @@
const POLLING_INTERVAL = 1000;
const POLLING_RETRIES = 120;
function launchAuthApp(action) {
if (!isAndroid()) {
alert("Functionality only available for Android devices.")
return null
}
// Fetch challenge.
httpGetAsync(originUrl + challengeUrl, (body) => {
let data = JSON.parse(body);
let challenge = data.nonce;
let intent = createParametrizedIntentUrl(challenge, action, originUrl); // TODO: Error handling.
console.log(intent);
window.location.href = intent;
pollForAuth(POLLING_INTERVAL, challenge);
})
}
function pollForAuth(timeout, challenge) {
console.log("Polling for auth");
let encodedChallenge = encodeURIComponent(challenge);
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + encodedChallenge;
let counter = 0;
let timer = setInterval(() => {
// Fetch authentication object.
httpGetAsync(requestUrl, (body) => {
console.log(body);
// If this is a successful request, stop the polling.
clearInterval(timer);
window.location.href = originUrl + loggedInUrl;
});
counter++;
if (counter > POLLING_RETRIES) {
clearInterval(timer); // Stop polling after some time.
let loginErrorAlert = document.getElementById("loginErrorAlert");
loginErrorAlert.classList.remove("d-none")
}
}, timeout)
}
function createParametrizedIntentUrl(challenge, action) {
if (action == null) {
console.error("There has to be an action for intent.")
}
else if (challenge == null) {
console.error("Challenge missing, can't authenticate without it.")
} else {
return intentUrl + "?" + "action=" + action + "&challenge=" + encodeURIComponent(challenge) + "&authUrl=" + authenticationRequestUrl + "&originUrl=" + originUrl;
}
}
function isAndroid() {
// Check if using Android device.
const ua = navigator.userAgent.toLowerCase();
return ua.indexOf("android") > -1;
}
function httpGetAsync(theUrl, callback) {
console.log("Sending a request.")
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
callback(xmlHttp.responseText);
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}

View File

@ -1,38 +0,0 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link th:href="@{/css/main.css}" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
<script type="text/javascript" th:src="@{/js/index.js}"></script>
<script type="text/javascript" th:src="@{/js/main.js}"></script>
<script th:inline="javascript">const originUrl = [[${originUrl}]];
const intentUrl = [[${intentUrl}]];
const challengeUrl = [[${challengeUrl}]];
const loggedInUrl = [[${loggedInUrl}]];
const authenticationRequestUrl = [[${authenticationRequestUrl}]]</script> <!-- Pass some values to JS -->
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Auth demo webapp</a>
</div>
</nav>
<div class="cont">
<h4>Welcome to Estonian ID card mobile authentication demo website. When using a mobile phone, you can log in to the
website using your ID card by using the button below.</h4>
<h5>Make sure you've installed the authentication app from: <a
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5>
<button type="button" class="btn btn-lg btn-secondary" id="loginButton" data-action="auth">Log in</button>
<div class="alert alert-danger d-none" role="alert" id="loginErrorAlert">
Login failed. Refresh the page to try again.
</div>
</div>
</body>
</html>

View File

@ -1,35 +0,0 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link th:href="@{/css/main.css}" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
<script type="text/javascript" th:src="@{/js/signature.js}"></script>
<script type="text/javascript" th:src="@{/js/main.js}"></script>
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<a class="navbar-brand" href="#">Auth demo web application</a>
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a href="/" class="btn btn-danger">Log out</a>
</li>
</ul>
</nav>
<div class="cont">
<h4>Congratulations! You have just authenticated yourself using your mobile phone and your ID-card. You can try to
give a signature to a file now.</h4>
<h5>This page is still WIP, signing a document feature will be implemented later.</h5>
<div class="custom-file">
<input type="file" class="custom-file-input" id="customFile">
</div>
<button type="button" class="btn btn-secondary" id="signFile" data-action="auth">Sign</button>
</div>
</body>
</html>