Skip to content

Commit

Permalink
feat(roles): client-side permissions checks in the sidebar and header
Browse files Browse the repository at this point in the history
  • Loading branch information
emirotin committed Jun 9, 2018
1 parent 888e8c8 commit af772e5
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 74 deletions.
6 changes: 4 additions & 2 deletions packages/core/botpress-util-roles/.npmignore
@@ -1,2 +1,4 @@
.npmignore
src
*

!dist
!CHANGELOG.md
10 changes: 2 additions & 8 deletions packages/core/botpress/src/web/actions/index.js
Expand Up @@ -20,7 +20,7 @@ export const fetchFlows = () => dispatch => {
export const requestSaveFlows = createAction('FLOWS/SAVE')
export const receiveSaveFlows = createAction('FLOWS/SAVE/RECEIVE', flows => flows, () => ({ receiveAt: new Date() }))

export const saveAllFlows = flows => (dispatch, getState) => {
export const saveAllFlows = () => (dispatch, getState) => {
dispatch(requestSaveFlows())

const flows = _.values(getState().flows.flowsByName).map(flow => ({
Expand Down Expand Up @@ -75,7 +75,7 @@ export const setDiagramAction = createAction('FLOWS/FLOW/SET_ACTION')

// Content
export const receiveContentCategories = createAction('CONTENT/CATEGORIES/RECEIVE')
export const fetchContentCategories = id => dispatch =>
export const fetchContentCategories = () => dispatch =>
axios.get('/api/content/categories').then(({ data }) => {
dispatch(receiveContentCategories(data))
})
Expand Down Expand Up @@ -168,12 +168,6 @@ export const fetchModules = () => dispatch => {
})
}

// Rules
export const rulesReceived = createAction('RULES/RECEIVED')
export const fetchRules = () => dispatch => {
dispatch(rulesReceived([]))
}

// Notifications
export const allNotificationsReceived = createAction('NOTIFICATIONS/ALL_RECEIVED')
export const newNotificationsReceived = createAction('NOTIFICATIONS/NEW_RECEIVED')
Expand Down
10 changes: 1 addition & 9 deletions packages/core/botpress/src/web/components/App/index.jsx
@@ -1,6 +1,5 @@
import React, { Component } from 'react'
import { Component } from 'react'

import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import { authEvents } from '~/util/Auth'
Expand All @@ -12,17 +11,12 @@ import {
fetchUser,
fetchBotInformation,
fetchModules,
fetchRules,
fetchNotifications,
replaceNotifications,
addNotifications
} from '~/actions'

class App extends Component {
state = {
events: EventBus.default
}

componentWillMount() {
if (window.APP_NAME) {
window.document.title = window.APP_NAME
Expand All @@ -39,7 +33,6 @@ class App extends Component {

if (window.AUTH_ENABLED) {
this.props.fetchUser()
this.props.fetchRules()
}
}

Expand Down Expand Up @@ -70,7 +63,6 @@ const mapDispatchToProps = {
fetchUser,
fetchBotInformation,
fetchModules,
fetchRules,
fetchNotifications,
replaceNotifications,
addNotifications
Expand Down
11 changes: 5 additions & 6 deletions packages/core/botpress/src/web/components/Layout/Header.jsx
Expand Up @@ -10,8 +10,7 @@ import NotificationHub from '~/components/Notifications/Hub'
import { logout } from '~/util/Auth'
import style from './Header.scss'
import { viewModeChanged } from '~/actions'

const RulesChecker = props => props.children
import PermissionsChecker from './PermissionsChecker'

class Header extends React.Component {
state = {
Expand Down Expand Up @@ -65,14 +64,14 @@ class Header extends React.Component {
<Navbar.Collapse>
<Nav pullRight>
<NavItem onClick={this.handleFullscreen}>{this.renderFullScreenButton()}</NavItem>
<RulesChecker res="bot/logs" op="read">
<PermissionsChecker user={this.props.user} res="bot.logs" op="read">
<NavItem href="/logs">
<Glyphicon glyph="list-alt" />
</NavItem>
</RulesChecker>
<RulesChecker res="notifications" op="read">
</PermissionsChecker>
<PermissionsChecker user={this.props.user} res="notifications" op="read">
<NotificationHub />
</RulesChecker>
</PermissionsChecker>
{this.renderLogoutButton()}
</Nav>
<Nav pullRight className="bp-navbar-module-buttons" />
Expand Down
@@ -0,0 +1,13 @@
import { checkMultipleRoles } from '@botpress/util-roles'

const PermissionsChecker = ({ user, res, op, children }) => {
if (!user) {
return null
}
if (user.roles && !checkMultipleRoles(user.roles, op, res)) {
return null
}
return children
}

export default PermissionsChecker
29 changes: 17 additions & 12 deletions packages/core/botpress/src/web/components/Layout/Sidebar.jsx
Expand Up @@ -9,47 +9,48 @@ import ReactSidebar from 'react-sidebar'
import SidebarHeader from './SidebarHeader'

import GhostChecker from '~/views/GhostContent/Checker'

const RulesChecker = props => props.children
import PermissionsChecker from './PermissionsChecker'

const style = require('./Sidebar.scss')

const BASIC_MENU_ITEMS = [
{
name: 'Dashboard',
path: '/dashboard',
rule: { res: 'dashboard', op: 'read' },
rule: { res: 'bot.information', op: 'read' },
icon: 'dashboard'
},
{
name: 'Modules',
path: '/manage',
rule: { res: 'modules/list', op: 'read' },
rule: { res: 'modules.list', op: 'read' },
icon: 'build'
},
window.GHOST_ENABLED && {
name: 'Version Control',
path: 'version-control',
rule: { res: 'ghost_content', op: 'read' },
rule: { res: 'bot.ghost_content', op: 'read' },
icon: 'content_copy',
renderSuffix: () => <GhostChecker />
renderSuffix() {
return <GhostChecker />
}
},
{
name: 'Content',
path: '/content',
rule: { res: 'content', op: 'read' },
rule: { res: 'bot.content', op: 'read' },
icon: 'description'
},
{
name: 'Flows',
path: '/flows',
rule: { res: 'flows', op: 'read' },
rule: { res: 'bot.flows', op: 'read' },
icon: 'device_hub'
},
{
name: 'Middleware',
path: '/middleware',
rule: { res: 'middleware', op: 'read' },
rule: { res: 'middleware.list', op: 'read' },
icon: 'settings'
}
].filter(Boolean)
Expand Down Expand Up @@ -104,15 +105,15 @@ class Sidebar extends React.Component {

renderBasicItem = ({ name, path, rule, icon, renderSuffix }) => {
return (
<RulesChecker res={rule.res} op={rule.op} key={name}>
<PermissionsChecker user={this.props.user} res={rule.res} op={rule.op} key={name}>
<li key={path}>
<NavLink to={path} title={name} activeClassName={style.active}>
<i className="icon material-icons">{icon}</i>
{name}
{renderSuffix && renderSuffix()}
</NavLink>
</li>
</RulesChecker>
</PermissionsChecker>
)
}

Expand Down Expand Up @@ -151,6 +152,10 @@ class Sidebar extends React.Component {
}
}

const mapStateToProps = state => ({ viewMode: state.ui.viewMode, modules: state.modules })
const mapStateToProps = state => ({
user: state.user,
viewMode: state.ui.viewMode,
modules: state.modules
})

export default connect(mapStateToProps)(Sidebar)
3 changes: 1 addition & 2 deletions packages/core/botpress/src/web/reducers/index.js
Expand Up @@ -8,9 +8,8 @@ import user from './user'
import bot from './bot'
import modules from './modules'
import skills from './skills'
import rules from './rules'
import notifications from './notifications'
export * from './selectors'

const bpApp = combineReducers({ content, flows, license, ui, user, bot, modules, rules, notifications, skills })
const bpApp = combineReducers({ content, flows, license, ui, user, bot, modules, notifications, skills })
export default bpApp
14 changes: 0 additions & 14 deletions packages/core/botpress/src/web/reducers/rules.js

This file was deleted.

2 changes: 1 addition & 1 deletion packages/core/botpress/src/web/reducers/user.js
Expand Up @@ -6,7 +6,7 @@ const defaultState = {}

const reducer = handleActions(
{
[userReceived]: (state, { payload }) => ({ ...payload })
[userReceived]: (state, { payload }) => ({ ...state, ...payload })
},
defaultState
)
Expand Down
13 changes: 0 additions & 13 deletions packages/core/botpress/src/web/util/Auth.js
Expand Up @@ -15,19 +15,6 @@ export const getToken = () => {
return false
}

export const getCurrentUser = () => {
const token = getToken()

if (!token) {
return null
}

const encoded = token.token.replace(/\w+\./, '').replace(/\.[\w|\-|_]+/, '')
const decoded = JSON.parse(Buffer(encoded, 'base64').toString())

return decoded.user
}

export const setToken = token => {
localStorage.setItem(
storageKey,
Expand Down
20 changes: 14 additions & 6 deletions packages/core/botpress/src/web/views/Dashboard/index.jsx
@@ -1,19 +1,19 @@
import React from 'react'
import { Panel, Grid, Row, Col, ControlLabel, Tooltip, OverlayTrigger, Link } from 'react-bootstrap'
import { connect } from 'react-redux'

import { Panel, Grid, Row, Col } from 'react-bootstrap'

import classnames from 'classnames'
import axios from 'axios'
import _ from 'lodash'

import ContentWrapper from '~/components/Layout/ContentWrapper'
import PermissionsChecker from '~/components/Layout/PermissionsChecker'
import PageHeader from '~/components/Layout/PageHeader'
import ModulesComponent from '~/components/Modules'
import InformationComponent from '~/components/Information'
import HeroComponent from '~/components/Hero'

import { fetchModules } from '~/actions'

export default class DashboardView extends React.Component {
class Dashboard extends React.Component {
state = {
loading: true,
popularModules: [],
Expand Down Expand Up @@ -72,7 +72,9 @@ export default class DashboardView extends React.Component {
<InformationComponent />
</Col>
<Col xs={12} sm={8} md={4} smOffset={2} mdOffset={0}>
<HeroComponent />
<PermissionsChecker user={this.props.user} res="modules.list.community" op="red">
<HeroComponent />
</PermissionsChecker>
</Col>
</Row>
<Row>
Expand All @@ -88,3 +90,9 @@ export default class DashboardView extends React.Component {
)
}
}

const mapStateToProps = state => ({
user: state.user
})

export default connect(mapStateToProps)(Dashboard)
7 changes: 6 additions & 1 deletion packages/package.json
@@ -1,8 +1,13 @@
{
"name": "botpress-fake-root",
"description":
"This package describes the dependencies needed to run things locally due to Babel, Lerna and Node nto always playing well together",
"version": "10.15.0",
"private": true,
"devDependencies": {
"botpress": "^10.15.0"
"botpress": "^10.15.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-stage-3": "^6.24.1",
"babel-preset-react": "^6.24.1"
}
}

0 comments on commit af772e5

Please sign in to comment.