From 30edcca4ae4bd9396d2b6de7caca29ae76a1ca8c Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Thu, 28 Jan 2021 16:38:52 +0100 Subject: [PATCH 01/20] feature/keycloak: add support for keycloak as a provider --- CHANGELOG.md | 4 ++ README.md | 5 +++ functions/.gitignore | 1 + functions/index.js | 7 ++- functions/tokenCreator.js | 30 +++++++++++++ package-lock.json | 21 +++++++++ package.json | 1 + public/silent-check-sso.html | 1 + src/App.vue | 8 +++- src/components/Navigation/SiteHeader.vue | 7 ++- src/config/firebaseConfig.js | 2 +- src/db/User/User.js | 6 ++- src/main.js | 39 ++++++++++++++++- src/store/index.js | 43 +++++++++++++++++- src/views/Login.vue | 56 +++++++++++++++++++++--- 15 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 functions/tokenCreator.js create mode 100644 public/silent-check-sso.html diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd138261..f9fe80f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format ## Unreleased +### Experimental + +- (WIP) Keycloak integration: User a Keycloak token to create custom Firebase token + ## [2.0.0-rc.3] 2021-01-26 ### Added diff --git a/README.md b/README.md index 37ba770a5..8ce3e8b83 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,11 @@ Get your Firebase SDK snippet from your [Firebase Console](https://console.fireb | `VUE_APP_SHEETS_SERVICE_ACCOUNT` | \ | | `VUE_APP_I18N_LOCALE` | `nb-NO OR en-US` | | `VUE_APP_REGION` | `europe-west2` | +| `VUE_APP_LOGIN_PROVIDERS` | login providers allowed separated with hyphen - only implemented google, email and keycloak. Ex: `google-keycloak-email` | +| `VUE_APP_KEYCLOAK_URL` | _from keycloak server_ (optional) | +| `VUE_APP_KEYCLOAK_REALM` | _from keycloak server_ (optional) | +| `VUE_APP_KEYCLOAK_CLIENT_ID` | _from keycloak server_ (optional) | +| `VUE_APP_KEYCLOAK_LOGOUT_URL` | `app url` | ### Link project diff --git a/functions/.gitignore b/functions/.gitignore index e34ca3b1a..fae2ac742 100644 --- a/functions/.gitignore +++ b/functions/.gitignore @@ -1,2 +1,3 @@ node_modules/ .runtimeconfig.json +origo-okr-tracker-private-key.json diff --git a/functions/index.js b/functions/index.js index 554197ca0..45b3c7845 100644 --- a/functions/index.js +++ b/functions/index.js @@ -1,7 +1,10 @@ const admin = require('firebase-admin'); +const serviceAccount = require('./origo-okr-tracker-private-key.json'); // Initialize the app to get everything started -admin.initializeApp(); +admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), +}); /** * Functions for backup and restoring the Firestore database @@ -59,3 +62,5 @@ exports.handleKeyResultProgressOnObjectiveUpdate = require('./progress').handleK exports.slackNotificationOnUserRequest = require('./requestAccess').slackNotificationOnUserRequest; exports.slackNotificationInteractiveOnRequest = require('./requestAccess').slackNotificationInteractiveOnRequest; + +exports.createCustomToken = require('./tokenCreator').createCustomToken; diff --git a/functions/tokenCreator.js b/functions/tokenCreator.js new file mode 100644 index 000000000..9f775fa02 --- /dev/null +++ b/functions/tokenCreator.js @@ -0,0 +1,30 @@ +const admin = require('firebase-admin'); +const functions = require('firebase-functions'); +const config = require('./config'); + +const auth = admin.auth(); + +/** + * Manually trigger the scheduled function + */ +exports.createCustomToken = functions + .runWith(config.runtimeOpts) + .region(config.region) + .https.onCall((token, context) => { + console.log(context); + console.log(token); + const userId = token.preferred_username; + const additionalClaims = { + email: token.email, + firstName: token.given_name, + lastName: token.family_name, + }; + return auth + .createCustomToken(userId, additionalClaims) + .then((customToken) => { + return customToken; + }) + .catch((error) => { + console.log(error); + }); + }); diff --git a/package-lock.json b/package-lock.json index ae0b1f5bb..799f5356d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11125,6 +11125,11 @@ "easy-stack": "^1.0.1" } }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11343,6 +11348,22 @@ "safe-buffer": "^5.0.1" } }, + "keycloak-js": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-10.0.2.tgz", + "integrity": "sha512-7nkg4Ob1khHGcNbuK36AMndKUEuIQFpNlWU9ygWs7nSBPCI9VZ8dJjjXfKJHm0ewgcqLFGPIJ6bxxRlfcQ6sLg==", + "requires": { + "base64-js": "1.3.1", + "js-sha256": "0.9.0" + }, + "dependencies": { + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + } + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", diff --git a/package.json b/package.json index 70c640160..ed5b64cb9 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "flatpickr": "^4.6.9", "fuse.js": "^6.4.6", "image-file-resize": "^1.0.3", + "keycloak-js": "^10.0.2", "marked": "^1.2.7", "v-tooltip": "^2.1.2", "vee-validate": "^3.4.5", diff --git a/public/silent-check-sso.html b/public/silent-check-sso.html new file mode 100644 index 000000000..60a7af916 --- /dev/null +++ b/public/silent-check-sso.html @@ -0,0 +1 @@ + diff --git a/src/App.vue b/src/App.vue index 9ee8eec90..ff5782b9e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,8 +16,10 @@ + + From 97ffb28ee182fdefcced219cbf11e7ad33276043 Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Mon, 15 Feb 2021 18:27:09 +0100 Subject: [PATCH 07/20] feature/keycloak: if you sign in with keycloak it now creates a user - replaces newUser with oldUser and deletes the oldUser --- CHANGELOG.md | 1 + firestore.rules | 28 ++++++++++++----- src/components/Navigation/Sidebar.vue | 2 +- src/config/firebaseConfig.js | 4 +-- src/db/RequestAccess/RequestAccess.js | 2 +- src/db/User/User.js | 45 +++++++++++++++++++++------ src/db/common/util/metadata.js | 4 +-- src/locale/locales/en-US.json | 2 +- src/main.js | 30 +++++++++++++----- src/router/router-guards/home.js | 1 - src/router/router-guards/login.js | 2 -- src/store/actions/set_user.js | 8 ++--- 12 files changed, 91 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71804bd7e..2dd016ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. The format ### BREAKING CHANGE +- FIRESTORE RULES ARE CHANGED - We now check for uid and not email when accessing documents. THIS WILL BREAK YOUR INSTANCE. Check the readme for more information - Service account private key is not optional - it is required to add the private key json-file to firebase functions config (check out the README) ### Experimental diff --git a/firestore.rules b/firestore.rules index 70b34fd61..0ef8dd458 100644 --- a/firestore.rules +++ b/firestore.rules @@ -3,7 +3,9 @@ service cloud.firestore { match /databases/{database}/documents { function isAdmin() { - return isSignedIn() && get(/databases/$(database)/documents/users/$(request.auth.token.email)).data.admin == true + let oldUser = get(/databases/$(database)/documents/users/$(request.auth.token.email)).data.admin == true; + let newUser = get(/databases/$(database)/documents/users/$(request.auth.token.uid)).data.admin == true; + return isSignedIn() && (oldUser || newUser); } function isSignedIn() { @@ -12,27 +14,39 @@ service cloud.firestore { } function isTeamMember() { - return /databases/$(database)/documents/users/$(request.auth.token.email) in resource.data.team; + let oldUser = /databases/$(database)/documents/users/$(request.auth.token.email) in resource.data.team; + let newUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in resource.data.team; + return oldUser || newUser; } function isMemberOfParent() { - return /databases/$(database)/documents/users/$(request.auth.token.email) in getAfter(request.resource.data.parent).data.team + let oldUser = /databases/$(database)/documents/users/$(request.auth.token.email) in getAfter(request.resource.data.parent).data.team; + let newUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in getAfter(request.resource.data.parent).data.team; + return oldUser || newUser; } function isMemberOfParentDelete() { - return /databases/$(database)/documents/users/$(request.auth.token.email) in get(resource.data.parent).data.team + let oldUser = /databases/$(database)/documents/users/$(request.auth.token.email) in get(resource.data.parent).data.team; + let newUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in get(resource.data.parent).data.team; + return oldUser || newUser; } function isMemberOfKeyResParent(document) { - return /databases/$(database)/documents/users/$(request.auth.token.email) in getAfter(get(/databases/$(database)/documents/keyResults/$(document)).data.parent).data.team + let oldUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in getAfter(get(/databases/$(database)/documents/keyResults/$(document)).data.parent).data.team; + let newUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in getAfter(get(/databases/$(database)/documents/keyResults/$(document)).data.parent).data.team; + return oldUser || newUser; } function isMemberOfKpiParent(document) { - return /databases/$(database)/documents/users/$(request.auth.token.email) in get(get(/databases/$(database)/documents/kpis/$(document)).data.parent).data.team + let oldUser = /databases/$(database)/documents/users/$(request.auth.token.email) in get(get(/databases/$(database)/documents/kpis/$(document)).data.parent).data.team; + let newUser = /databases/$(database)/documents/users/$(request.auth.token.uid) in get(get(/databases/$(database)/documents/kpis/$(document)).data.parent).data.team; + return oldUser || newUser; } function isSelf(document) { - return document == request.auth.token.email + let oldUser = document == request.auth.token.email; + let newUser = document == request.auth.token.uid; + return oldUser || newUser; } diff --git a/src/components/Navigation/Sidebar.vue b/src/components/Navigation/Sidebar.vue index ffabd08db..6e5009e19 100644 --- a/src/components/Navigation/Sidebar.vue +++ b/src/components/Navigation/Sidebar.vue @@ -16,7 +16,7 @@ {{ item.name }} diff --git a/src/config/firebaseConfig.js b/src/config/firebaseConfig.js index dc61f3e61..fc81743e3 100644 --- a/src/config/firebaseConfig.js +++ b/src/config/firebaseConfig.js @@ -35,7 +35,7 @@ const storage = firebase.storage(); const auth = firebase.auth(); const analytics = firebase.analytics(); const functions = firebase.app().functions(process.env.VUE_APP_REGION); -const { serverTimestamp, arrayRemove } = firebase.firestore.FieldValue; +const { serverTimestamp, arrayRemove, arrayUnion } = firebase.firestore.FieldValue; if (process.env.NODE_ENV === 'development' || window.Cypress) { db.settings(firestoreEmulator); @@ -45,4 +45,4 @@ if (process.env.NODE_ENV === 'development' || window.Cypress) { console.log('Established connection to Firestore server'); } -export { db, auth, loginProvider, storage, analytics, functions, serverTimestamp, arrayRemove }; +export { db, auth, loginProvider, storage, analytics, functions, serverTimestamp, arrayRemove, arrayUnion }; diff --git a/src/db/RequestAccess/RequestAccess.js b/src/db/RequestAccess/RequestAccess.js index 2cfc34866..71c0b2949 100644 --- a/src/db/RequestAccess/RequestAccess.js +++ b/src/db/RequestAccess/RequestAccess.js @@ -30,7 +30,7 @@ export const accept = async (id) => { .get() .then((snapshot) => snapshot.data()); - return User.create({ email }).then(reject.bind(null, id)); + return User.create({ id: email, email }).then(reject.bind(null, id)); } catch { throw new Error(`Cannot accept access request (${id})`); } diff --git a/src/db/User/User.js b/src/db/User/User.js index d3a5c81f4..a31207a7a 100644 --- a/src/db/User/User.js +++ b/src/db/User/User.js @@ -1,4 +1,4 @@ -import { arrayRemove, db, storage } from '@/config/firebaseConfig'; +import { arrayRemove, arrayUnion, db, storage } from '@/config/firebaseConfig'; import preferences from './defaultPreferences'; import UploadImage from '../common/uploadImage'; @@ -7,22 +7,21 @@ const collectionReference = db.collection('users'); export const getAllUserIds = () => collectionReference.get().then(({ docs }) => docs.map(({ id }) => id)); export const getUserFromId = (id) => collectionReference.doc(id).get(); -export const create = async ({ email }) => { +export const create = async (user) => { try { - if (!email) throw new Error('Invalid email'); + if (!user.email) throw new Error('Invalid email'); - const { exists } = await collectionReference.doc(email).get(); - if (exists) throw new Error(`User ${email} already exists!`); + const { exists } = await collectionReference.doc(user.id).get(); + if (exists) throw new Error(`User ${user.id} already exists!`); - await collectionReference.doc(email).set({ - id: email, - email, + await collectionReference.doc(user.id).set({ + ...user, preferences, }); return true; } catch (error) { - throw new Error(`Could not add user ${email}`); + throw new Error(`Could not add user ${user.id}`); } }; @@ -49,7 +48,7 @@ export const update = async (user) => { export const addUsers = async (userList) => { if (!userList || !userList.length) throw new Error('Invalid data'); - const promises = userList.map((email) => ({ email })).map(create); + const promises = userList.map((email) => ({ id: email, email })).map(create); try { return Promise.all(promises); @@ -93,3 +92,29 @@ async function removeFromTeams(docRef) { throw new Error(error.message); } } + +export const replaceFromTeams = async (oldDocId, newDocId) => { + try { + const oldDocRef = collectionReference.doc(oldDocId); + const newDocRef = collectionReference.doc(newDocId); + const products = await db.collection('products').where('team', 'array-contains', oldDocRef).get(); + + await Promise.all( + products.docs.map(({ ref }) => { + return ref.update({ + team: arrayRemove(oldDocRef), + }); + }) + ); + + return Promise.all( + products.docs.map(({ ref }) => { + return ref.update({ + team: arrayUnion(newDocRef), + }); + }) + ); + } catch (error) { + throw new Error(error.message); + } +}; diff --git a/src/db/common/util/metadata.js b/src/db/common/util/metadata.js index 155ca32f6..6932f09e7 100644 --- a/src/db/common/util/metadata.js +++ b/src/db/common/util/metadata.js @@ -3,12 +3,12 @@ import { db, auth, serverTimestamp } from '@/config/firebaseConfig'; const created = () => ({ archived: false, created: serverTimestamp(), - createdBy: db.collection('users').doc(auth.currentUser.email), + createdBy: db.collection('users').doc(auth.currentUser.uid), }); const edited = () => ({ edited: serverTimestamp(), - editedBy: db.collection('users').doc(auth.currentUser.email), + editedBy: db.collection('users').doc(auth.currentUser.uid), }); export default { created, edited }; diff --git a/src/locale/locales/en-US.json b/src/locale/locales/en-US.json index 2fa3ca6bb..17fc5d370 100644 --- a/src/locale/locales/en-US.json +++ b/src/locale/locales/en-US.json @@ -110,7 +110,7 @@ "acceptRequest": "Grant access", "rejectRequest": "Delete request", "deleteImage": "Delete image", - "deleteUser": "Delte user", + "deleteUser": "Delete user", "create": "Create", "delete": "Delete", "archive": "Archive", diff --git a/src/main.js b/src/main.js index 4e35e941e..98292986c 100644 --- a/src/main.js +++ b/src/main.js @@ -95,16 +95,33 @@ auth.onAuthStateChanged(async (user) => { const keycloakParsedToken = store.state.keycloak ? store.state.keycloak.idTokenParsed : null; const keycloakProvider = store.state.providers.includes('keycloak'); - // Handle keycloak integration - if (user && !user.email && keycloakParsedToken.email && keycloakProvider) { + if (user && keycloakProvider && keycloakParsedToken) { const firstName = capitalizeFirstLetterOfNames(keycloakParsedToken.given_name); const lastName = capitalizeFirstLetterOfNames(keycloakParsedToken.family_name); + const { preferred_username, email } = keycloakParsedToken; // eslint-disable-line await store.dispatch('setLoading', true); - await user.updateEmail(keycloakParsedToken.email); - if (!(await User.getUserFromId(keycloakParsedToken.email))) { - await User.create({ email: keycloakParsedToken.email }); + if (!user.email) { + try { + await user.updateEmail(email); + } catch (e) { + store.state.keycloak.logout({ redirectUri: `${process.env.VUE_APP_KEYCLOAK_ERROR_URL}${e.code}` }); + } + } + + const { exists } = await User.getUserFromId(preferred_username); + if (!exists) { + const oldUserRef = await User.getUserFromId(email); + + await User.create({ ...oldUserRef.data(), id: preferred_username, email }); + const newUserRef = await User.getUserFromId(preferred_username); + + if (oldUserRef) { + await User.replaceFromTeams(oldUserRef.id, newUserRef.id); + await User.remove(oldUserRef.data()); + } + const newUser = { ...user, displayName: `${firstName} ${lastName}`, @@ -113,11 +130,10 @@ auth.onAuthStateChanged(async (user) => { } else { await store.dispatch('set_user', user); } - - await store.dispatch('setLoading', false); } else { await store.dispatch('set_user', user); } + await store.dispatch('init_state'); if (router.currentRoute.query.redirectFrom) { diff --git a/src/router/router-guards/home.js b/src/router/router-guards/home.js index b1b9c1435..3ad331466 100644 --- a/src/router/router-guards/home.js +++ b/src/router/router-guards/home.js @@ -2,7 +2,6 @@ import store from '@/store'; export default async function routerGuardHome(to, from, next) { if (!store.state || !store.state.user) { - console.log('yo'); next({ name: 'Login', query: { redirectFrom: to.fullPath }, diff --git a/src/router/router-guards/login.js b/src/router/router-guards/login.js index 69a6a3eca..474c871af 100644 --- a/src/router/router-guards/login.js +++ b/src/router/router-guards/login.js @@ -1,8 +1,6 @@ import store from '@/store'; export default async function login(to, from, next) { - console.log(to); - console.log(store.state.user); if (store.state.user) { next({ name: 'Home', diff --git a/src/store/actions/set_user.js b/src/store/actions/set_user.js index d32f46f80..3818d6402 100644 --- a/src/store/actions/set_user.js +++ b/src/store/actions/set_user.js @@ -14,14 +14,14 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, if (!user || !user.email) rejectAccess(); // Check if user is whitelisted - const userRef = db.collection('users').doc(user.email); + const userRef = db.collection('users').doc(user.uid); const documentSnapshot = await userRef.get(); if (!documentSnapshot.exists) rejectAccess(); - const { email, displayName, preferences } = await documentSnapshot.data(); + const { id, email, displayName, preferences } = await documentSnapshot.data(); if (!preferences) { - await User.update({ id: email, preferences: defaultPreferences }); + await User.update({ id, email, preferences: defaultPreferences }); } if (preferences.lang) { @@ -29,7 +29,7 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, } if (!displayName) { - await User.update({ id: email, displayName: user.displayName }); + await User.update({ id, email, displayName: user.displayName }); } // Bind the user object From dc441d23f692190a93d52484906b9cf22db90976 Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Tue, 16 Feb 2021 11:15:51 +0100 Subject: [PATCH 08/20] feature/keycloak: went back to using emails as ids until we can migrate in a safe way --- firestore.rules | 2 ++ src/components/Navigation/Sidebar.vue | 2 +- src/db/common/util/metadata.js | 4 +-- src/main.js | 40 ++++++++------------------- src/store/actions/set_user.js | 10 +++++-- 5 files changed, 25 insertions(+), 33 deletions(-) diff --git a/firestore.rules b/firestore.rules index 0ef8dd458..c292866c8 100644 --- a/firestore.rules +++ b/firestore.rules @@ -1,3 +1,5 @@ +// TODO: Migrate from email-check to uid-check +// Allow both at first and then remove email-check later on rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { diff --git a/src/components/Navigation/Sidebar.vue b/src/components/Navigation/Sidebar.vue index 6e5009e19..ffabd08db 100644 --- a/src/components/Navigation/Sidebar.vue +++ b/src/components/Navigation/Sidebar.vue @@ -16,7 +16,7 @@ {{ item.name }} diff --git a/src/db/common/util/metadata.js b/src/db/common/util/metadata.js index 6932f09e7..155ca32f6 100644 --- a/src/db/common/util/metadata.js +++ b/src/db/common/util/metadata.js @@ -3,12 +3,12 @@ import { db, auth, serverTimestamp } from '@/config/firebaseConfig'; const created = () => ({ archived: false, created: serverTimestamp(), - createdBy: db.collection('users').doc(auth.currentUser.uid), + createdBy: db.collection('users').doc(auth.currentUser.email), }); const edited = () => ({ edited: serverTimestamp(), - editedBy: db.collection('users').doc(auth.currentUser.uid), + editedBy: db.collection('users').doc(auth.currentUser.email), }); export default { created, edited }; diff --git a/src/main.js b/src/main.js index 98292986c..cd1f44633 100644 --- a/src/main.js +++ b/src/main.js @@ -14,7 +14,6 @@ import App from '@/App.vue'; import router from '@/router'; import store from '@/store'; import i18n from '@/locale/i18n'; -import User from '@/db/User'; import { capitalizeFirstLetterOfNames } from '@/util/'; import './styles/main.scss'; @@ -95,41 +94,26 @@ auth.onAuthStateChanged(async (user) => { const keycloakParsedToken = store.state.keycloak ? store.state.keycloak.idTokenParsed : null; const keycloakProvider = store.state.providers.includes('keycloak'); - if (user && keycloakProvider && keycloakParsedToken) { + if (user && !user.email && keycloakProvider && keycloakParsedToken) { + console.log(user); const firstName = capitalizeFirstLetterOfNames(keycloakParsedToken.given_name); const lastName = capitalizeFirstLetterOfNames(keycloakParsedToken.family_name); - const { preferred_username, email } = keycloakParsedToken; // eslint-disable-line + const { email } = keycloakParsedToken; // eslint-disable-line await store.dispatch('setLoading', true); - if (!user.email) { - try { - await user.updateEmail(email); - } catch (e) { - store.state.keycloak.logout({ redirectUri: `${process.env.VUE_APP_KEYCLOAK_ERROR_URL}${e.code}` }); - } + try { + await user.updateEmail(email); + } catch (e) { + store.state.keycloak.logout({ redirectUri: `${process.env.VUE_APP_KEYCLOAK_ERROR_URL}${e.code}` }); } - const { exists } = await User.getUserFromId(preferred_username); - if (!exists) { - const oldUserRef = await User.getUserFromId(email); - - await User.create({ ...oldUserRef.data(), id: preferred_username, email }); - const newUserRef = await User.getUserFromId(preferred_username); + const newUser = { + ...user, + displayName: `${firstName} ${lastName}`, + }; - if (oldUserRef) { - await User.replaceFromTeams(oldUserRef.id, newUserRef.id); - await User.remove(oldUserRef.data()); - } - - const newUser = { - ...user, - displayName: `${firstName} ${lastName}`, - }; - await store.dispatch('set_user', newUser); - } else { - await store.dispatch('set_user', user); - } + await store.dispatch('set_user', newUser); } else { await store.dispatch('set_user', user); } diff --git a/src/store/actions/set_user.js b/src/store/actions/set_user.js index 3818d6402..029bdec88 100644 --- a/src/store/actions/set_user.js +++ b/src/store/actions/set_user.js @@ -14,11 +14,13 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, if (!user || !user.email) rejectAccess(); // Check if user is whitelisted - const userRef = db.collection('users').doc(user.uid); + const userRef = db.collection('users').doc(user.email); const documentSnapshot = await userRef.get(); if (!documentSnapshot.exists) rejectAccess(); - const { id, email, displayName, preferences } = await documentSnapshot.data(); + const { id, email, displayName, preferences, uid } = await documentSnapshot.data(); + + console.log(id); if (!preferences) { await User.update({ id, email, preferences: defaultPreferences }); @@ -32,6 +34,10 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, await User.update({ id, email, displayName: user.displayName }); } + if (!uid) { + await User.update({ id, email, uid: user.uid }); + } + // Bind the user object const options = { serialize, From 73b7a53a0c7ee0ef56d1141261973e1278aa18a1 Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Tue, 16 Feb 2021 11:44:50 +0100 Subject: [PATCH 09/20] feature/keycloak: added uid to userobject for later migration from emails to uid --- src/main.js | 4 ++-- src/store/actions/set_user.js | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.js b/src/main.js index cd1f44633..006a8e0e1 100644 --- a/src/main.js +++ b/src/main.js @@ -95,10 +95,9 @@ auth.onAuthStateChanged(async (user) => { const keycloakProvider = store.state.providers.includes('keycloak'); if (user && !user.email && keycloakProvider && keycloakParsedToken) { - console.log(user); const firstName = capitalizeFirstLetterOfNames(keycloakParsedToken.given_name); const lastName = capitalizeFirstLetterOfNames(keycloakParsedToken.family_name); - const { email } = keycloakParsedToken; // eslint-disable-line + const { preferred_username, email } = keycloakParsedToken; // eslint-disable-line await store.dispatch('setLoading', true); @@ -111,6 +110,7 @@ auth.onAuthStateChanged(async (user) => { const newUser = { ...user, displayName: `${firstName} ${lastName}`, + username: preferred_username, }; await store.dispatch('set_user', newUser); diff --git a/src/store/actions/set_user.js b/src/store/actions/set_user.js index 029bdec88..b577a8290 100644 --- a/src/store/actions/set_user.js +++ b/src/store/actions/set_user.js @@ -20,8 +20,6 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, const { id, email, displayName, preferences, uid } = await documentSnapshot.data(); - console.log(id); - if (!preferences) { await User.update({ id, email, preferences: defaultPreferences }); } @@ -34,7 +32,7 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, await User.update({ id, email, displayName: user.displayName }); } - if (!uid) { + if (user.username && user.username !== uid) { await User.update({ id, email, uid: user.uid }); } From 49d645916cd0dd8b8c596c7727c70aea8218e472 Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Tue, 16 Feb 2021 11:47:11 +0100 Subject: [PATCH 10/20] feature/keycloak: change from user.username to user.uuid --- src/main.js | 2 +- src/store/actions/set_user.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index 006a8e0e1..407edcfce 100644 --- a/src/main.js +++ b/src/main.js @@ -110,7 +110,7 @@ auth.onAuthStateChanged(async (user) => { const newUser = { ...user, displayName: `${firstName} ${lastName}`, - username: preferred_username, + uuid: preferred_username, }; await store.dispatch('set_user', newUser); diff --git a/src/store/actions/set_user.js b/src/store/actions/set_user.js index b577a8290..8ced1de1f 100644 --- a/src/store/actions/set_user.js +++ b/src/store/actions/set_user.js @@ -32,7 +32,7 @@ export default firestoreAction(async ({ bindFirestoreRef, unbindFirestoreRef }, await User.update({ id, email, displayName: user.displayName }); } - if (user.username && user.username !== uid) { + if (user.uuid && user.uuid !== uid) { await User.update({ id, email, uid: user.uid }); } From 7cf6aacfbb94cc8af04f6040d2f51fe7ce5f02b7 Mon Sep 17 00:00:00 2001 From: Aulon Mujaj Date: Tue, 16 Feb 2021 14:50:22 +0100 Subject: [PATCH 11/20] feature/keycloak: added a logout page with information on what went wrong --- src/locale/locales/en-US.json | 9 +++ src/locale/locales/nb-NO.json | 9 +++ src/styles/_alerts.scss | 94 +++++++++++++++++++++++++++ src/styles/main.scss | 1 + src/views/ItemAdmin/ItemAdminKPIs.vue | 2 +- src/views/Logout.vue | 23 ++++++- 6 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/styles/_alerts.scss diff --git a/src/locale/locales/en-US.json b/src/locale/locales/en-US.json index 17fc5d370..16015ef93 100644 --- a/src/locale/locales/en-US.json +++ b/src/locale/locales/en-US.json @@ -139,6 +139,15 @@ }, "loginWithUsername": "Sign in with username" }, + "logout": { + "header": "You have been logged out!", + "reasons": { + "internal": "There was an internal error when trying to log you in. Please try to log in again. Contact the administrator of the site if the problem persists.", + "permission-denied": "The account you tried to log in with does not meet the required permissions to access the site. Contact the administrator to get the right permissions.", + "email-in-use": "The e-mail you tried to log in with is already in use. Contact the administrator for further assistance.", + "default": "Something went wrong with your sign in. Please contact the administrator and refer to your url." + } + }, "toaster": { "welcome": "Welcome {user}", "add": { diff --git a/src/locale/locales/nb-NO.json b/src/locale/locales/nb-NO.json index d8d0a93a6..52736d98b 100644 --- a/src/locale/locales/nb-NO.json +++ b/src/locale/locales/nb-NO.json @@ -137,6 +137,15 @@ }, "loginWithUsername": "Logg inn med brukernavn" }, + "logout": { + "header": "Du har blitt logget ut!", + "reasons": { + "internal": "Det oppstod en intern feil når du prøvde å logge deg på. Vennligst prøv å logg inn på nytt. Kontakt administratoren hvis problemet vedvarer.", + "permission-denied": "Kontoen du prøvde å logge på med oppfyller ikke de nødvendige tillatelsene for å få tilgang til nettstedet. Kontakt administratoren for å få de riktige tillatelsene.", + "email-in-use": "E-posten du prøve å logge på med er allerede i bruk. Kontakt administratoren for ytterligere hjelp.", + "default": "Noe gikk galt med innloggingen din. Vennligst ta kontakt med administratoren og henvis til url-en din." + } + }, "toaster": { "welcome": "Velkommen {user}", "add": { diff --git a/src/styles/_alerts.scss b/src/styles/_alerts.scss new file mode 100644 index 000000000..c041b3ebc --- /dev/null +++ b/src/styles/_alerts.scss @@ -0,0 +1,94 @@ +@import '_colors.scss'; +@import 'typography.scss'; + +$borderRadius: 1.5px; + +@mixin boxShadow($color) { + box-shadow: 0 1px 3px rgba($color, 0.2), 0 3px 12px rgba($color, 0.05); +} + +@mixin boxColor($color, $shadowColor: $color-purple) { + background: rgba($color, 0.1); + @include boxShadow($shadowColor); + + &::after { + background: linear-gradient($color, darken($color, 5%)); + } + + &::before { + background: $color; + } +} + +.ok-alert { + position: relative; + margin: 0.5rem 0 1rem; + padding: 1rem; + overflow: hidden; + border-radius: $borderRadius; + + @media screen and (min-width: bp(m)) { + padding-top: 1rem; + padding-right: 2.5rem; + padding-left: 1.5rem; + } + + &::after { + position: absolute; + top: 0; + right: 0; + left: 0; + display: inline-flex; + align-items: center; + height: 0.25rem; + padding-left: 1rem; + font-size: 1.75rem; + content: ''; + + @media screen and (min-width: bp(m)) { + bottom: 0; + align-items: inherit; + justify-content: center; + width: 0.25rem; + height: 100%; + padding-top: 1rem; + padding-left: 0; + text-align: center; + } + } +} + +.ok-alert { + @include boxColor($color-grey-100); +} + +.ok-alert--success { + @include boxColor($color-green, darken($color-green, 20%)); +} + +.ok-alert--danger { + @include boxColor($color-red, darken($color-red, 20%)); +} + +.ok-alert--warning { + @include boxColor($color-yellow, darken($color-yellow, 20%)); +} + +.ok-alert--white { + background: white; +} + +.ok-alert--dark { + background: $color-blue; + @include boxShadow($color-purple); + + &::after { + color: $color-blue; + background: linear-gradient($color-purple, darken($color-purple, 5%)); + } + + &::before { + color: white; + background: $color-purple; + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss index f3bbfd796..7edd0f045 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -10,6 +10,7 @@ @import 'widgets'; @import 'tooltip'; @import 'admin'; +@import '_alerts'; @import '~@braid/griddle/scss/griddle-overlay.scss'; diff --git a/src/views/ItemAdmin/ItemAdminKPIs.vue b/src/views/ItemAdmin/ItemAdminKPIs.vue index 8afd2cea2..413ec4ff0 100644 --- a/src/views/ItemAdmin/ItemAdminKPIs.vue +++ b/src/views/ItemAdmin/ItemAdminKPIs.vue @@ -1,4 +1,4 @@ -