mirror of
https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC.git
synced 2024-12-22 12:30:16 +02:00
MOB-42 Redid the whole frontend part in Vue
This commit is contained in:
parent
7daea4b6c2
commit
da2dbeb0fc
@ -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.
132
demoBackend/src/demo-website/package-lock.json
generated
132
demoBackend/src/demo-website/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
26
demoBackend/src/demo-website/src/App.vue
Normal file
26
demoBackend/src/demo-website/src/App.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
30
demoBackend/src/demo-website/src/components/Welcome.vue
Normal file
30
demoBackend/src/demo-website/src/components/Welcome.vue
Normal 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>
|
50
demoBackend/src/demo-website/src/main.js
Normal file
50
demoBackend/src/demo-website/src/main.js
Normal 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')
|
@ -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')
|
@ -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')
|
31
demoBackend/src/demo-website/src/router/index.js
Normal file
31
demoBackend/src/demo-website/src/router/index.js
Normal 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
|
@ -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: {
|
@ -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: {
|
@ -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 {
|
||||
|
@ -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:,
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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.")
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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>
|
@ -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>
|
Loading…
Reference in New Issue
Block a user