From c536707f3acc29604127f9672effa1bd130dbbd8 Mon Sep 17 00:00:00 2001 From: konrad Date: Sat, 21 Nov 2020 16:38:40 +0000 Subject: [PATCH] Authentication with OpenID Connect providers (#305) Fix setting auth config from api in state Verify auth state before authenticating Add showing openid providers on login Parse auth config from /info Add authentication through openid Add openid auth component Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/305 Co-Authored-By: konrad Co-Committed-By: konrad --- src/components/home/contentNoAuth.vue | 3 +- src/router/index.js | 6 +++ src/store/modules/auth.js | 34 ++++++++++++++ src/store/modules/config.js | 18 ++++++++ src/views/user/Login.vue | 28 +++++++++--- src/views/user/OpenIdAuth.vue | 66 +++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 src/views/user/OpenIdAuth.vue diff --git a/src/components/home/contentNoAuth.vue b/src/components/home/contentNoAuth.vue index e9490002..05a8dae2 100644 --- a/src/components/home/contentNoAuth.vue +++ b/src/components/home/contentNoAuth.vue @@ -32,7 +32,8 @@ export default { this.$route.name !== 'user.password-reset.request' && this.$route.name !== 'user.password-reset.reset' && this.$route.name !== 'user.register' && - this.$route.name !== 'link-share.auth' + this.$route.name !== 'link-share.auth' && + this.$route.name !== 'openid.auth' ) { this.$router.push({name: 'user.login'}) } diff --git a/src/router/index.js b/src/router/index.js index 163e2936..981bd97f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -8,6 +8,7 @@ import ErrorComponent from '../components/misc/error' // User Handling import LoginComponent from '../views/user/Login' import RegisterComponent from '../views/user/Register' +import OpenIdAuth from '@/views/user/OpenIdAuth' // Tasks import ShowTasksInRangeComponent from '../views/tasks/ShowTasksInRange' import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth' @@ -267,5 +268,10 @@ export default new Router({ name: 'filters.create', component: CreateSavedFilter, }, + { + path: '/auth/openid/:provider', + name: 'openid.auth', + component: OpenIdAuth, + }, ], }) \ No newline at end of file diff --git a/src/store/modules/auth.js b/src/store/modules/auth.js index a33d24ed..befe2032 100644 --- a/src/store/modules/auth.js +++ b/src/store/modules/auth.js @@ -98,6 +98,40 @@ export default { ctx.commit(LOADING, false, {root: true}) }) }, + openIdAuth(ctx, {provider, code}) { + const HTTP = HTTPFactory() + ctx.commit(LOADING, true, {root: true}) + + const data = { + code: code, + } + + // Delete an eventually preexisting old token + localStorage.removeItem('token') + return HTTP.post(`/auth/openid/${provider}/callback`, data) + .then(response => { + // Save the token to local storage for later use + localStorage.setItem('token', response.data.token) + + // Tell others the user is autheticated + ctx.commit('isLinkShareAuth', false) + ctx.dispatch('checkAuth') + return Promise.resolve() + }) + .catch(e => { + if (e.response) { + let errorMsg = e.response.data.message + if (e.response.status === 401) { + errorMsg = 'Wrong username or password.' + } + ctx.commit(ERROR_MESSAGE, errorMsg, {root: true}) + } + return Promise.reject() + }) + .finally(() => { + ctx.commit(LOADING, false, {root: true}) + }) + }, linkShareAuth(ctx, hash) { const HTTP = HTTPFactory() diff --git a/src/store/modules/config.js b/src/store/modules/config.js index c9b65131..1c0b5b34 100644 --- a/src/store/modules/config.js +++ b/src/store/modules/config.js @@ -1,5 +1,8 @@ +import Vue from 'vue' + import {CONFIG} from '../mutation-types' import {HTTPFactory} from '@/http-common' +import {objectToCamelCase} from '@/helpers/case' export default { namespaced: true, @@ -20,6 +23,16 @@ export default { privacyPolicyUrl: '', }, caldavEnabled: false, + auth: { + local: { + enabled: true, + }, + openidConnect: { + enabled: false, + redirectUrl: '', + providers: [], + }, + }, }), mutations: { [CONFIG](state, config) { @@ -36,6 +49,11 @@ export default { state.legal.imprintUrl = config.legal.imprint_url state.legal.privacyPolicyUrl = config.legal.privacy_policy_url state.caldavEnabled = config.caldav_enabled + const auth = objectToCamelCase(config.auth) + state.auth.local.enabled = auth.local.enabled + state.auth.openidConnect.enabled = auth.openidConnect.enabled + state.auth.openidConnect.redirectUrl = auth.openidConnect.redirectUrl + Vue.set(state.auth.openidConnect, 'providers', auth.openidConnect.providers) }, }, actions: { diff --git a/src/views/user/Login.vue b/src/views/user/Login.vue index 7b65fd7f..98cc03a6 100644 --- a/src/views/user/Login.vue +++ b/src/views/user/Login.vue @@ -6,7 +6,7 @@ You successfully confirmed your email! You can log in now. -
+
@@ -54,15 +54,16 @@ @@ -70,6 +71,13 @@ {{ errorMessage }}
+ + +
@@ -128,6 +136,8 @@ export default { errorMessage: ERROR_MESSAGE, needsTotpPasscode: state => state.auth.needsTotpPasscode, authenticated: state => state.auth.authenticated, + localAuthEnabled: state => state.config.auth.local.enabled, + openidConnect: state => state.config.auth.openidConnect, }), methods: { submit() { @@ -151,6 +161,12 @@ export default { .catch(() => { }) }, + redirectToProvider(provider) { + const state = Math.random().toString(36).substring(2, 24) + localStorage.setItem('state', state) + + window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${this.openidConnect.redirectUrl}${provider.key}&response_type=code&scope=&state=${state}` + }, }, } diff --git a/src/views/user/OpenIdAuth.vue b/src/views/user/OpenIdAuth.vue new file mode 100644 index 00000000..ebab542d --- /dev/null +++ b/src/views/user/OpenIdAuth.vue @@ -0,0 +1,66 @@ + + +