From 9989f6906c4397ab7d49d764c3eb9a72e7bc074c Mon Sep 17 00:00:00 2001
From: Jack Valley
Date: Wed, 13 May 2020 11:46:59 +0300
Subject: [PATCH] Feature/develop-to-experimental to feature/city-theme-temp
(#12)
* Fix double encoding #524; with caveat https://github.com/axios/axios/pull/2563
* Display all api results in react select components
* Fix event publisher field name for user rights check
* Fix #530
* Do not try to update deleted subevents
* Do not try to edit past subevents
* Refactor editability check; allow deleting and canceling series that contain deleted, canceled or past subevents
* Remove unnecessary inlined sub_events at cancel to prevent API errors
* Add instructions for internet events
* Update snapshots
* Add remote participation keyword to all internet events
* Add notification about online events to cancel confirmation dialog
* Add postpone button and badge
* Mention postponing in cancel extra text
* Add postpone button to editor too
* Show postponed events in search instead of crashing
* Update user rights managers
* Add missing
* Do not remove subevents at cancel after all; deleted items removed by API PR https://github.com/City-of-Helsinki/linkedevents/pull/407
* Fix removed future time validation; fixes #536, #528
* Update snapshots
* Feature/oidc auth (#8)
* initial oidc integration commit
* Removed Passport from user authentication
Fixed Warning with History import and removed Passport authentications.
* fetchUser and comments for OIDC
Needs proper lifecycle methods.
* Added some missing oidc features and updated tests
* Added oidc settings to production build.
* Added tests for user actions and reducer.
* Updated readme by removing mentions to builtin auth server.
* Updated node-sass package, added more tests and gitignored coverage folder.
Co-authored-by: Ducky07
* Revert "Merge branch 'experimental/city-theme' into develop"
This reverts commit f51da9b1af35b251f72032fd033bf86b74d51782, reversing
changes made to a06088f7a05f65e1e7918d1781f2f8f2f68aa4c2.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch develop
# Your branch is up to date with 'origin/develop'.
#
# You are currently reverting commit a06088f.
#
# Changes to be committed:
# modified: config/appConfig.js
# deleted: config/assetPath.js
# modified: config/webpack/dev.js
# modified: config/webpack/prod.js
# modified: config_dev.json.example
# modified: package.json
# deleted: src/assets/default/assets/main.scss
# deleted: src/assets/default/i18n/index.js
# new file: src/assets/images/turkulogowhite.png
# modified: src/components/FormFields/index.js
# modified: src/components/FormFields/index.scss
# deleted: src/components/Header/LanguageSelector.js
# deleted: src/components/Header/LanguageSelector.test.js
# modified: src/components/Header/index.scss
# modified: src/components/HelFormFields/HelKeywordSelector/HelKeywordSelector.js
# modified: src/components/HelFormFields/HelLabeledCheckboxGroup.scss
# modified: src/components/HelFormFields/HelOffersField.js
# modified: src/components/HelFormFields/HelTextField.js
# modified: src/components/ImageEdit/index.js
# modified: src/components/ImageEdit/index.scss
# modified: src/components/ImagePicker/index.js
# modified: src/components/ImagePicker/index.scss
# modified: src/components/SearchBar/index.js
# modified: src/components/SearchBar/index.scss
# modified: src/i18n/index.js
# modified: src/index.jade
# new file: src/themes/material-ui-tku.js
# new file: src/themes/tku/tku-brand-colors.js
# modified: src/views/App/index.js
# modified: yarn.lock
Manually reverting changes that were accidentally merged into develop.
Weirdly enough Github thought taking commits from develop would be the same as merging the two branches together, while not being able to understand PR reverting (aka only reverting one branch but not the other).
Please don't do that again...
Co-authored-by: Riku Oja
Co-authored-by: Aleksi Salonen
Co-authored-by: Riku Oja
Co-authored-by: aceViilee <51813121+aceViilee@users.noreply.github.com>
Co-authored-by: Santtu Alatalo
---
.eslintrc | 3 +-
.gitignore | 1 +
README.md | 40 ---
config/appConfig.js | 2 +-
config/assetPath.js | 43 ---
config/webpack/dev.js | 20 +-
config/webpack/prod.js | 19 +-
config_dev.json.example | 2 +-
package.json | 11 +-
server/auth.js | 59 ----
server/server.js | 7 -
src/actions/user.js | 130 +++-----
src/actions/userActions.test.js | 24 ++
src/actors/serializer.js | 9 +-
src/api/client.js | 4 +-
src/assets/default/assets/main.scss | 4 -
src/assets/default/i18n/index.js | 1 -
src/components/FormFields/index.js | 46 +--
src/components/FormFields/index.scss | 42 +--
src/components/Header/Header.test.js | 94 ++++++
src/components/Header/LanguageSelector.js | 90 -----
.../Header/LanguageSelector.test.js | 41 ---
src/components/Header/index.scss | 310 ++++++------------
.../HelKeywordSelector/HelKeywordSelector.js | 7 +-
.../HelLabeledCheckboxGroup.scss | 3 +-
.../HelFormFields/HelOffersField.js | 10 +-
src/components/HelFormFields/HelTextField.js | 9 +-
src/components/ImageEdit/index.js | 7 +-
src/components/ImageEdit/index.scss | 29 +-
src/components/ImagePicker/index.js | 4 +-
src/components/ImagePicker/index.scss | 7 -
src/components/SearchBar/index.js | 28 +-
src/components/SearchBar/index.scss | 25 +-
src/i18n/index.js | 25 +-
src/index.jade | 1 -
src/index.js | 54 +--
src/reducers/index.js | 2 +
src/reducers/user.js | 36 +-
src/reducers/userReducer.test.js | 64 ++++
src/store.js | 5 +-
src/utils/jestAppSettings.js | 4 +
src/utils/userManager.js | 21 ++
src/views/App/App.test.js | 48 +++
src/views/App/index.js | 18 +-
src/views/Auth/LoginCallback.js | 46 +++
src/views/Auth/LoginCallback.test.js | 75 +++++
src/views/Auth/LogoutCallback.js | 42 +++
src/views/Auth/LogoutCallback.test.js | 64 ++++
48 files changed, 776 insertions(+), 860 deletions(-)
delete mode 100644 config/assetPath.js
delete mode 100644 server/auth.js
create mode 100644 src/actions/userActions.test.js
delete mode 100644 src/assets/default/assets/main.scss
delete mode 100644 src/assets/default/i18n/index.js
create mode 100644 src/components/Header/Header.test.js
delete mode 100644 src/components/Header/LanguageSelector.js
delete mode 100644 src/components/Header/LanguageSelector.test.js
create mode 100644 src/reducers/userReducer.test.js
create mode 100644 src/utils/userManager.js
create mode 100644 src/views/App/App.test.js
create mode 100644 src/views/Auth/LoginCallback.js
create mode 100644 src/views/Auth/LoginCallback.test.js
create mode 100644 src/views/Auth/LogoutCallback.js
create mode 100644 src/views/Auth/LogoutCallback.test.js
diff --git a/.eslintrc b/.eslintrc
index c3b462a9e..12b081e9c 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -8,7 +8,8 @@
"parser": "babel-eslint",
"globals": {
"_": true,
- "appSettings": true
+ "appSettings": true,
+ "oidcSettings": true
},
"rules": {
"no-console": "off",
diff --git a/.gitignore b/.gitignore
index 2bd485e85..ef96ab0ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ server.config
yarn-error.log
config_dev.json
package-lock.json
+/coverage/
diff --git a/README.md b/README.md
index 03d65a11b..82d3d0efc 100644
--- a/README.md
+++ b/README.md
@@ -30,9 +30,6 @@ The UI is now compatible with the `courses` extension for the Linked Events API.
If you wish to include the extra fields specified in the `courses` extension,
please change the `ui_mode` setting from `events` to `courses`.
-Note that authentication server is not nicely configurable. If you wish to
-use your own authentication server, you will need code changes in server/auth.js.
-
## Running development server
```
@@ -55,10 +52,6 @@ if you'd like to change the base address for Linkedevents API, you would:
export api_base="https://api.hel.fi/linkedevents/v1"
```
-Note that the configuration is used in the different phases. Some settings
-need to be defined during build and other settings for running the
-authentication server (see below)
-
Most if not all build automation tools provide for setting environment
variables. Check the documentation for the one you are using. If you are
testing locally you can `source config_build_example.sh` to get started.
@@ -75,36 +68,3 @@ $ yarn build
You should now have the bundled javascript + some non-bundled assets in
`dist`. You can serve these using your favorite web server at whatever
address suits your fancy.
-
-You will still need the source tree for the authentication server (below)
-
-### Setting up the runtime
-
-In addition to serving the files built in previous step, you will need to
-run the built-in authentication server (or proxy really). Although
-linkedevents-ui runs completely in client, it currently uses authentication
-code based OAuth2 Authorization Code flow. This is a historical accident,
-that will be remedied one day.
-
-We recommend running the authentication server with some sort of process
-manager, possibly one specialized in running Node applications. Your system
-process manager, like systemd, is another good candidate
-
-The authentication server will need configuration passed in through
-environment variables (see Congiration). If you use a process manager to
-run the server, it should provide for setting them.
-
-The server is run by executing `npm run production`. If your process
-manager wants to run node by itself, you can also run specify `server` as
-the script (that will actually run server/index.js). In this case you will
-also need to set environment variable `NODE_ENV=production` by yourself.
-
-After you have the authentication server running, you will need to set up a
-web server to serve the files in `dist` and forward authentication requests
-to the authentication server. The table below shows what needs to served:
-| URL | what is served |
-| /auth | forward to authentication server |
-| filename | serve from dist-directory |
-| unknown files | serve index.html from dist-directory |
-
-The last part is needed for deep linking into the application.
\ No newline at end of file
diff --git a/config/appConfig.js b/config/appConfig.js
index ab52cd778..f6fa7f2ee 100644
--- a/config/appConfig.js
+++ b/config/appConfig.js
@@ -36,7 +36,7 @@ nconf.defaults({
/**
* Function to retrieve value from config
- * @param {undefined|string|string[]} keys
+ * @param {undefined|string|string[]} keys
*/
function getConfig(keys) {
// Return all config if no keys provided
diff --git a/config/assetPath.js b/config/assetPath.js
deleted file mode 100644
index 87ec62d2c..000000000
--- a/config/assetPath.js
+++ /dev/null
@@ -1,43 +0,0 @@
-const path = require('path');
-const nconf = require('nconf');
-
-let cityConfig;
-let cityAssets;
-let cityImages;
-let cityi18n;
-/**
- * city_theme package could have the following folders
- * some-ui /
- * assets /
- * -----> images /
- * --------------> images that used for scss, can be imported directly
- * -----> main.scss
- * -----> some scss files that are imported to main.scss
- * i18n /
- * -----> language .json files that override the default ones
- * -----> fi.json
- * plus whatever
- */
-
-if (nconf.get('city_theme')) {
- // used in development, checks from config_dev.json
- cityConfig = path.resolve(__dirname, `../node_modules/${nconf.get('city_theme')}/`);
- cityAssets = path.resolve(cityConfig, 'assets/');
- cityImages = path.resolve(cityAssets, 'images/');
- cityi18n = path.resolve(cityConfig, 'i18n/');
-}
-else if (process.env.city_theme) {
- // used in production(???), checks from process.env
- cityConfig = path.resolve(__dirname, `../node_modules/${process.env.city_theme}/`);
- cityAssets = path.resolve(cityConfig, 'assets/');
- cityImages = path.resolve(cityAssets, 'images/');
- cityi18n = path.resolve(cityConfig, 'i18n/');
-}
-else {
- // used when no city_theme is available
- cityConfig = path.resolve(__dirname, '../src/assets/default/');
- cityAssets = path.resolve(cityConfig, 'assets/');
- cityImages = path.resolve(cityAssets, 'images/');
- cityi18n = path.resolve(cityConfig, 'i18n/');
-}
-module.exports = {cityConfig, cityAssets, cityImages, cityi18n};
diff --git a/config/webpack/dev.js b/config/webpack/dev.js
index 6c668181b..dd3572702 100644
--- a/config/webpack/dev.js
+++ b/config/webpack/dev.js
@@ -3,7 +3,7 @@ import common from './common.js';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import {readConfig} from '../appConfig';
-import assetPath from '../assetPath';
+
const publicUrl = readConfig('publicUrl')
const ui_mode = readConfig('ui_mode')
@@ -23,17 +23,12 @@ export default {
resolve: {
modules: [common.paths.ROOT, 'node_modules'],
extensions: ['.', '.webpack.js', '.web.js', '.jsx', '.js'],
- alias: {
- '@city-assets': assetPath.cityAssets,
- '@city-images': assetPath.cityImages,
- '@city-i18n': assetPath.cityi18n,
- },
},
module: {
rules: [
{
- test: /\.(js|jsx)?$/,
- exclude: /node_modules/,
+ test: /\.(js|jsx)?$/,
+ exclude: /node_modules/,
enforce: 'pre',
use: ['babel-loader', 'eslint-loader'],
},
@@ -46,7 +41,7 @@ export default {
{
loader: 'sass-loader',
options: {
- data: "$ui-mode: " + ui_mode + " !global;",
+ data: '$ui-mode: ' + ui_mode + ' !global;',
},
},
],
@@ -72,6 +67,13 @@ export default {
jQuery: 'jquery',
'window.jQuery': 'jquery',
}),
+ new webpack.DefinePlugin({
+ oidcSettings: {
+ client_id: JSON.stringify(readConfig('client_id')),
+ openid_audience: JSON.stringify(readConfig('openid_audience')),
+ openid_authority: JSON.stringify(readConfig('openid_authority')),
+ },
+ }),
],
mode: 'development',
};
diff --git a/config/webpack/prod.js b/config/webpack/prod.js
index 1ce556055..6b314d419 100644
--- a/config/webpack/prod.js
+++ b/config/webpack/prod.js
@@ -3,7 +3,6 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const GitRevisionPlugin = require('git-revision-webpack-plugin');
const common = require('./common');
const appConfig = require('../appConfig');
-const assetPath = require('../assetPath');
// There are defined in common.js as well, but that is not available without
// transpilation, which is not done for webpack configuration file
@@ -34,17 +33,12 @@ const config = {
resolve: {
modules: [common.paths.ROOT, 'node_modules'],
extensions: ['.', '.webpack.js', '.web.js', '.jsx', '.js'],
- alias: {
- '@city-assets': assetPath.cityAssets,
- '@city-images': assetPath.cityImages,
- '@city-i18n': assetPath.cityi18n,
- },
},
module: {
rules: [
{
- test: /\.(js|jsx)?$/,
- exclude: /node_modules/,
+ test: /\.(js|jsx)?$/,
+ exclude: /node_modules/,
enforce: 'pre',
use: ['babel-loader', 'eslint-loader'],
},
@@ -54,7 +48,7 @@ const config = {
use: [
{loader: 'style-loader'},
{loader: 'css-loader'},
- {loader: 'sass-loader', options: {data: "$ui-mode: " + ui_mode + " !global;"}},
+ {loader: 'sass-loader', options: {data: '$ui-mode: ' + ui_mode + ' !global;'}},
],
},
{test: /\.css$/, use: ['style-loader', 'css-loader']},
@@ -81,6 +75,13 @@ const config = {
inject: true,
templateContent: indexTemplate,
}),
+ new webpack.DefinePlugin({
+ oidcSettings: {
+ client_id: JSON.stringify(appConfig.readConfig('client_id')),
+ openid_audience: JSON.stringify(appConfig.readConfig('openid_audience')),
+ openid_authority: JSON.stringify(appConfig.readConfig('openid_authority')),
+ },
+ }),
],
};
diff --git a/config_dev.json.example b/config_dev.json.example
index de7efdc1a..4f595e63f 100644
--- a/config_dev.json.example
+++ b/config_dev.json.example
@@ -9,7 +9,7 @@
"publicUrl": "http://localhost:8080",
"sessionSecret": "dev-secret-do-not-use-in-production",
"ui_mode": "events",
- "client_id": "CLIENT_ID",
+ "client_id": "CLIENT_ID",
"openid_audience": "OPENID_AUDIENCE",
"openid_authority": "OPENID_AUTHORITY",
"city_theme": "city_theme"
diff --git a/package.json b/package.json
index 6f1e5734a..f19dcf27c 100644
--- a/package.json
+++ b/package.json
@@ -52,15 +52,14 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.27",
"nconf": "^0.9.1",
+ "node-sass": "^4.14.1",
"object-assign": "^4.1.1",
- "passport": "^0.3.2",
- "passport-helsinki": "https://github.com/City-of-Helsinki/passport-helsinki.git",
+ "oidc-client": "^1.10.1",
"progress": "^1.1.8",
"prop-types": "^15.7.2",
"raven-js": "^2.3.0",
"react": "16.12.0",
"react-addons-pure-render-mixin": "^15.6.2",
- "react-bootstrap": "^1.0.1",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "16.12.0",
"react-helmet": "^5.2.1",
@@ -71,9 +70,9 @@
"react-router-dom": "^4.2.2",
"react-router-redux": "^5.0.0-alpha.9",
"react-select": "^3.0.8",
- "reactstrap": "^8.4.1",
"redux": "4.0.4",
"redux-actions": "^0.9.0",
+ "redux-oidc": "^4.0.0-beta1",
"redux-thunk": "^2.3.0",
"typeahead.js": "^0.11.1"
},
@@ -106,7 +105,6 @@
"js-yaml": "^3.13.1",
"json-loader": "^0.5.4",
"morgan": "^1.6.1",
- "node-sass": "^4.13.1",
"pug": "^2.0.3",
"pug-loader": "^2.4.0",
"react-transform-hmr": "^1.0.4",
@@ -126,6 +124,7 @@
"jest": {
"setupTestFrameworkScriptFile": "src/setupTests.js",
"testEnvironment": "jsdom",
+ "testURL": "http://localhost/",
"moduleFileExtensions": [
"js",
"jsx",
@@ -136,7 +135,7 @@
"node_modules"
],
"moduleNameMapper": {
- "^.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy"
+ "^.+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$": "identity-obj-proxy"
},
"snapshotSerializers": [
"enzyme-to-json/serializer"
diff --git a/server/auth.js b/server/auth.js
deleted file mode 100644
index 5a09aad8b..000000000
--- a/server/auth.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import {Passport} from 'passport'
-import HelsinkiStrategy from 'passport-helsinki'
-import _debug from 'debug'
-
-const debug = _debug('auth')
-
-export function getPassport(settings) {
- const passport = new Passport()
-
- const helsinkiStrategy = new HelsinkiStrategy({
- clientID: settings.helsinkiAuthId,
- clientSecret: settings.helsinkiAuthSecret,
- callbackURL: settings.publicUrl + '/auth/login/helsinki/return',
- }, (accessToken, refreshToken, profile, done) => {
- debug('access token:', accessToken)
- debug('refresh token:', refreshToken)
- debug('acquiring token from api...')
-
- helsinkiStrategy.getAPIToken(accessToken, settings.helsinkiTargetApp, (token) => {
- profile.token = token
- return done(null, profile)
- })
- })
-
- passport.use(helsinkiStrategy)
-
- passport.serializeUser((user, done) => done(null, user))
- passport.deserializeUser((user, done) => done(null, user))
-
- return passport;
-}
-
-function successfulLoginHandler(req, res) {
- const js =
- `setTimeout(function() {
- if(window.opener) {
- window.close();
- }
- else { location.href = "/"; }
- }, 300);`
- res.send('Login successful.');
-}
-
-export function addAuth(server, passport, settings) {
- server.use(passport.initialize());
- server.use(passport.session());
- server.get('/auth/login/helsinki', passport.authenticate('helsinki'));
- server.get('/auth/login/helsinki/return', passport.authenticate('helsinki'), successfulLoginHandler);
- server.get('/auth/logout', (req, res) => {
- res.send('');
- });
- server.post('/auth/logout', (req, res) => {
- req.logout();
- res.send('OK');
- });
- server.get('/auth/me', (req, res) => {
- res.json(req.user || {});
- });
-}
diff --git a/server/server.js b/server/server.js
index e1567ca2a..06d82f393 100644
--- a/server/server.js
+++ b/server/server.js
@@ -7,7 +7,6 @@ import cookieSession from 'cookie-session'
import getSettings from './getSettings'
import express from 'express'
-import {getPassport, addAuth} from './auth'
import webpack from 'webpack'
import webpackMiddleware from 'webpack-dev-middleware'
@@ -16,17 +15,11 @@ import config from '../config/webpack/dev.js'
const settings = getSettings()
const app = express()
-const passport = getPassport(settings)
-
app.use(cookieParser());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieSession({name: 's', secret: settings.sessionSecret, maxAge: 86400 * 1000}));
-app.use(passport.initialize());
-app.use(passport.session());
-addAuth(app, passport, settings);
-
if(process.env.NODE_ENV !== 'development') {
app.use('/', express.static(path.resolve(__dirname, '..', 'dist')));
app.get('*', function (req, res) {
diff --git a/src/actions/user.js b/src/actions/user.js
index f56bbf2d4..24250ccf7 100644
--- a/src/actions/user.js
+++ b/src/actions/user.js
@@ -1,9 +1,7 @@
import constants from '../constants.js'
-import fetch from 'isomorphic-fetch'
import {set, get} from 'lodash'
import {setEditorAuthFlashMsg} from './editor'
import client from '../api/client'
-import axios from 'axios'
import {getAdminOrganizations, getRegularOrganizations} from '../utils/user'
const {RECEIVE_USERDATA, CLEAR_USERDATA, USER_TYPE} = constants
@@ -32,97 +30,53 @@ const getUserType = (permissions) => {
}
}
-// Adds an expiration time for user and saves it to localStorage.
-function saveUserToLocalStorage(user) {
- let modifiedUser = Object.assign({}, user)
-
- let expiryDate = new Date()
- let expiryTime = appSettings.local_storage_user_expiry_time || 12
- expiryDate.setHours(expiryDate.getHours() + expiryTime)
- modifiedUser.localStorageExpires = expiryDate.toISOString()
- localStorage.setItem('user', JSON.stringify(modifiedUser))
-}
-
-export const retrieveUserFromSession = () => async (dispatch) => {
+// Handles getting user data from backend api with given id.
+export const fetchUser = (id) => async (dispatch) => {
try {
- const meResponse = await axios.get(`/auth/me?${+new Date()}`)
- const user = meResponse.data
-
- if (user.token) {
- const userResponse = await client.get(`user/${user.username}`, {}, {
- headers: {Authorization: `JWT ${user.token}`},
- })
- const userData = userResponse.data
- const permissions = []
+ // try to get user data from user endpoint
+ const userResponse = await client.get(`user/${id}`)
+ const userData = userResponse.data
- if (get(userData, 'admin_organizations', []).length > 0) {
- permissions.push(USER_TYPE.ADMIN)
- }
- if (get(userData, 'organization_memberships', []).length > 0) {
- permissions.push(USER_TYPE.REGULAR)
- }
-
- const mergedUser = {
- ...user,
- organization: get(userData, 'organization', null),
- adminOrganizations: get(userData, 'admin_organizations', null),
- organizationMemberships: get(userData, 'organization_memberships', null),
- permissions,
- userType: getUserType(permissions),
- }
-
- const adminOrganizations = await Promise.all(getAdminOrganizations(mergedUser))
- const regularOrganizations = await Promise.all(getRegularOrganizations(mergedUser))
-
- // store data of all the organizations that the user is admin in
- mergedUser.adminOrganizationData = adminOrganizations
- .reduce((acc, organization) => set(acc, `${organization.data.id}`, organization.data), {})
- // store data of all the organizations where the user is a regular user
- mergedUser.regularOrganizationData = regularOrganizations
- .reduce((acc, organization) => set(acc, `${organization.data.id}`, organization.data), {})
- // get organizations with regular users
- mergedUser.organizationsWithRegularUsers = adminOrganizations
- .filter(organization => get(organization, ['data', 'has_regular_users'], false))
- .map(organization => organization.data.id)
+ // add correct permissions to user based on user's organizations
+ const permissions = []
+ if (get(userData, 'admin_organizations', []).length > 0) {
+ permissions.push(USER_TYPE.ADMIN)
+ }
+ if (get(userData, 'organization_memberships', []).length > 0) {
+ permissions.push(USER_TYPE.REGULAR)
+ }
- saveUserToLocalStorage(mergedUser)
- dispatch(receiveUserData(mergedUser))
- dispatch(setEditorAuthFlashMsg())
+ // add all desired user data in an object which will be stored into redux store
+ const mergedUser = {
+ id: get(userData, 'uuid', null),
+ displayName: get(userData, 'display_name', null),
+ firstName: get(userData, 'first_name', null),
+ lastName: get(userData, 'last_name', null),
+ username: get(userData, 'username', null),
+ email: get(userData, 'email', null),
+ organization: get(userData, 'organization', null),
+ adminOrganizations: get(userData, 'admin_organizations', null),
+ organizationMemberships: get(userData, 'organization_memberships', null),
+ permissions,
+ userType: getUserType(permissions),
}
+
+ const adminOrganizations = await Promise.all(getAdminOrganizations(mergedUser))
+ const regularOrganizations = await Promise.all(getRegularOrganizations(mergedUser))
+ // store data of all the organizations that the user is admin in
+ mergedUser.adminOrganizationData = adminOrganizations
+ .reduce((acc, organization) => set(acc, `${organization.data.id}`, organization.data), {})
+ // store data of all the organizations where the user is a regular user
+ mergedUser.regularOrganizationData = regularOrganizations
+ .reduce((acc, organization) => set(acc, `${organization.data.id}`, organization.data), {})
+ // get organizations with regular users
+ mergedUser.organizationsWithRegularUsers = adminOrganizations
+ .filter(organization => get(organization, ['data', 'has_regular_users'], false))
+ .map(organization => organization.data.id)
+
+ dispatch(receiveUserData(mergedUser))
+ dispatch(setEditorAuthFlashMsg())
} catch (e) {
throw Error(e)
}
}
-
-export function login() {
- return (dispatch) => {
- return new Promise((resolve) => {
- if (typeof window === 'undefined') { // Not in DOM? Just try to get an user then and see how that goes.
- return resolve(true);
- }
- const loginPopup = window.open(
- '/auth/login/helsinki',
- 'kkLoginWindow',
- 'location,scrollbars=on,width=720,height=600'
- );
- const wait = function wait() {
- if (loginPopup.closed) { // Is our login popup gone?
- return resolve(true);
- }
- setTimeout(wait, 500); // Try again in a bit...
- };
- wait();
- }).then(() => {
- return dispatch(retrieveUserFromSession());
- });
- };
-}
-
-export function logout() {
- return (dispatch) => {
- fetch('/auth/logout', {method: 'POST', credentials: 'same-origin'}) // Fire-and-forget
- localStorage.removeItem('user')
- dispatch(clearUserData())
- dispatch(setEditorAuthFlashMsg())
- };
-}
diff --git a/src/actions/userActions.test.js b/src/actions/userActions.test.js
new file mode 100644
index 000000000..cc38e2c3a
--- /dev/null
+++ b/src/actions/userActions.test.js
@@ -0,0 +1,24 @@
+import {receiveUserData, clearUserData} from './user';
+import constants from '../constants.js'
+
+
+const {RECEIVE_USERDATA, CLEAR_USERDATA} = constants
+
+describe('actions/user', () => {
+ describe('receiveUserData', () => {
+ test('returns object with correct type and payload', () => {
+ const data = {testData: 123};
+ const expectedResult = {type: RECEIVE_USERDATA, payload: data};
+ const result = receiveUserData(data);
+ expect(result).toEqual(expectedResult);
+ });
+ });
+
+ describe('clearUserData', () => {
+ test('returns object with correct type', () => {
+ const expectedResult = {type: CLEAR_USERDATA};
+ const result = clearUserData();
+ expect(result).toEqual(expectedResult);
+ });
+ });
+});
diff --git a/src/actors/serializer.js b/src/actors/serializer.js
index 858a7d54c..edb9c7317 100644
--- a/src/actors/serializer.js
+++ b/src/actors/serializer.js
@@ -7,14 +7,7 @@ let arg = {};
window.ARG = arg;
-var jwtDecode = require('jwt-decode');
-
export default (store) => {
- const state = _.cloneDeep(_.omit(store.getState(), ['userEvents']));
- if (state.user) {
- let token = jwtDecode(state.user.token);
- state.user.token = null; // JWT token is of not to be saved into Sentry
- state.user.exp = token.exp;
- }
+ const state = _.cloneDeep(_.omit(store.getState(), ['userEvents', 'auth']));
window.ARG = state;
}
diff --git a/src/api/client.js b/src/api/client.js
index 50d20a0ba..b462a7052 100644
--- a/src/api/client.js
+++ b/src/api/client.js
@@ -8,7 +8,7 @@ let authToken
const getToken = () => {
const state = store.getState()
- return get(state, 'user.token')
+ return get(state, 'auth.user.id_token')
}
store.subscribe(() => {
@@ -32,7 +32,7 @@ export class ApiClient {
getHeaders = () => ({
...CONSTANTS.API_HEADERS,
...(authToken
- ? {Authorization: `JWT ${authToken}`}
+ ? {Authorization: `Bearer ${authToken}`}
: {}),
})
diff --git a/src/assets/default/assets/main.scss b/src/assets/default/assets/main.scss
deleted file mode 100644
index fec3d0536..000000000
--- a/src/assets/default/assets/main.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-// THIS IS A DUMMY SCSS FILE THAT IS IMPORTED WHEN NO CITY_THEME IS INSTALLED
-body {
- background-color: green;
-}
diff --git a/src/assets/default/i18n/index.js b/src/assets/default/i18n/index.js
deleted file mode 100644
index ff8b4c563..000000000
--- a/src/assets/default/i18n/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export default {};
diff --git a/src/components/FormFields/index.js b/src/components/FormFields/index.js
index 7ad149e6f..6509f3f5e 100644
--- a/src/components/FormFields/index.js
+++ b/src/components/FormFields/index.js
@@ -17,8 +17,8 @@ import {
HelKeywordSelector,
} from 'src/components/HelFormFields'
import RecurringEvent from 'src/components/RecurringEvent'
-import {Button,Form, FormGroup, Label, Input} from 'reactstrap';
-import {Add, Autorenew} from '@material-ui/icons'
+import {Button, TextField} from '@material-ui/core'
+import {Add, Autorenew, FileCopyOutlined} from '@material-ui/icons'
import {mapKeywordSetToForm, mapLanguagesSetToForm} from '../../utils/apiDataMapping'
import {setEventData, setData} from '../../actions/editor'
import {get, isNull, pickBy} from 'lodash'
@@ -26,10 +26,10 @@ import API from '../../api'
import CONSTANTS from '../../constants'
import OrganizationSelector from '../HelFormFields/OrganizationSelector';
import UmbrellaSelector from '../HelFormFields/UmbrellaSelector/UmbrellaSelector'
+import {HelMaterialTheme} from '../../themes/material-ui'
import moment from 'moment'
import HelVideoFields from '../HelFormFields/HelVideoFields/HelVideoFields'
-
let FormHeader = (props) => (