Skip to content

Commit

Permalink
Merge pull request #4 from akaene/main
Browse files Browse the repository at this point in the history
[23ava-distribution#6] Support OAuth2
  • Loading branch information
blcham committed Nov 2, 2023
2 parents 673b546 + 981b988 commit 3313f22
Show file tree
Hide file tree
Showing 27 changed files with 484 additions and 120 deletions.
14 changes: 11 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ RECORD_MANAGER_PROD_SERVER_PORT=8080
RECORD_MANAGER_LANGUAGE=cs
# Flag if language of the UI should be determined from browser settings. Possible values: true, false.
RECORD_MANAGER_NAVIGATOR_LANGUAGE=true
# Context path added to URL or "" in case application path should not be modified.
# Context path added to URL or "/" in case application path should not be modified.
RECORD_MANAGER_BASENAME=/record-manager
# List of extensions seperated by comma, currently supports only values: "kodi"
RECORD_MANAGER_EXTENSIONS=kodi
# List of extensions separated by comma, currently supports only values: "kodi"
RECORD_MANAGER_EXTENSIONS=kodi
# Authentication method - use "internal" for internal authentication or "oidc" for an external auth service compatible with OIDC
RECORD_MANAGER_AUTHENTICATION=internal
# Authentication server URL, applicable when AUTHENTICATION=oidc. In case of Keycloak, this would be the server URL including the
# realm used to authenticate users, e.g. http://localhost:8080/realms/record-manager
RECORD_MANAGER_AUTH_SERVER_URL=
# Client ID of this application in the OIDC authentication server. The client should have public access and valid redirect and origin URIs
# configured
RECORD_MANAGER_AUTH_CLIENT_ID=record-manager-ui
11 changes: 11 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
RECORD_MANAGER_API_URL=http://localhost:8080/record-manager
RECORD_MANAGER_APP_TITLE=OFN Record Manager
RECORD_MANAGER_DEV_SERVER_PORT=3000
RECORD_MANAGER_PROD_SERVER_PORT=8080
RECORD_MANAGER_LANGUAGE=en
RECORD_MANAGER_NAVIGATOR_LANGUAGE=false
RECORD_MANAGER_BASENAME=/record-manager
RECORD_MANAGER_EXTENSIONS=
RECORD_MANAGER_AUTHENTICATION=internal
RECORD_MANAGER_AUTH_SERVER_URL=
RECORD_MANAGER_AUTH_CLIENT_ID=
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"plugin:react/recommended"
],
"rules": {

"react/prop-types": "off"
}
}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.idea
*.iml
node_modules
build
coverage
junit.xml
.DS_store
13 changes: 0 additions & 13 deletions WEB-INF/web.xml

This file was deleted.

2 changes: 1 addition & 1 deletion .babelrc → babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
module.exports = {
"plugins": [
"lodash",
"@babel/plugin-proposal-class-properties",
Expand Down
1 change: 0 additions & 1 deletion doc/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,3 @@ Uses `.env` to configure options like:
- internationalization settings

See `.env.example` for detailed description of options.

1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
transform: {
'^.+\\.(js|jsx)$': 'babel-jest'
},
transformIgnorePatterns: ["node_modules/(?!@kbss-cvut)/"],
reporters: ['default'],
"moduleNameMapper": {
"\\.(css)$": "<rootDir>/tests/__mocks__/styleMock.js"
Expand Down
24 changes: 17 additions & 7 deletions js/App.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
'use strict';

import React from "react";
import {IntlProvider} from "react-intl";
import {Route, Router} from "react-router-dom";
import MainView from "./components/MainView";
import {connect} from "react-redux";
import {history} from "./utils/Routing";
import {BASENAME} from "../config";
import OidcAuthWrapper from "./components/misc/oidc/OidcAuthWrapper";
import OidcSignInCallback from "./components/pages/OidcSignInCallback";
import OidcSilentCallback from "./components/pages/OidcSilentCallback";
import {isUsingOidcAuth} from "./utils/OidcUtils";

const App = (props) => (
<IntlProvider {...props.intl}>
const App = (props) => {
return <IntlProvider {...props.intl}>
<Router history={history} basename={BASENAME}>
<Route path='/' component={MainView}/>
<Route path='/oidc-signin-callback' component={OidcSignInCallback}/>
<Route path='/oidc-silent-callback' component={OidcSilentCallback}/>
<Route path='/' component={isUsingOidcAuth() ? OidcMainView : MainView}/>
</Router>
</IntlProvider>
);
</IntlProvider>;
}

const OidcMainView = () => {
return <OidcAuthWrapper>
<MainView/>
</OidcAuthWrapper>;
}

export default connect((state) => {
return {intl: state.intl}
Expand Down
2 changes: 1 addition & 1 deletion js/actions/AuthActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function userAuthError(error) {
export function logout() {
//console.log("Logouting user");
return function (dispatch) {
axiosBackend.post(`${API_URL}/j_spring_security_logout`).then(() => {
return axiosBackend.post(`${API_URL}/j_spring_security_logout`).then(() => {
dispatch(unauthUser());
//Logger.log('User successfully logged out.');
}).catch(() => {
Expand Down
14 changes: 14 additions & 0 deletions js/actions/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import axios from 'axios';
import Routes from "../constants/RoutesConstants";
import {transitionTo} from "../utils/Routing";
import {HttpHeaders} from "../constants/DefaultConstants";
import {getOidcToken} from "../utils/SecurityUtils";
import {isUsingOidcAuth} from "../utils/OidcUtils";

// Axios instance for communicating with Backend
export let axiosBackend = axios.create({
withCredentials: true
});

axiosBackend.interceptors.request.use((reqConfig) => {
if (!isUsingOidcAuth()) {
return reqConfig;
}
if (!reqConfig.headers) {
reqConfig.headers = {};
}
reqConfig.headers[HttpHeaders.AUTHORIZATION] = getOidcToken();
return reqConfig;
});

axiosBackend.interceptors.response.use(
response => response,
error => {
Expand Down
67 changes: 41 additions & 26 deletions js/components/MainView.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import {ACTION_STATUS, ROLE} from "../constants/DefaultConstants";
import {loadUserProfile} from "../actions/AuthActions";
import * as Constants from "../constants/DefaultConstants";
import {LoaderMask} from "./Loader";
import {NavLink} from 'react-router-dom';
import {NavLink, withRouter} from 'react-router-dom';
import {IfGranted} from "react-authorization";
import {transitionTo, transitionToWithOpts} from "../utils/Routing";
import {isUsingOidcAuth, userProfileLink} from "../utils/OidcUtils";

class MainView extends React.Component {
constructor(props) {
Expand All @@ -22,17 +25,19 @@ class MainView extends React.Component {
}

componentDidMount() {
this.props.loadUserProfile();
I18nStore.setIntl(this.props.intl);
}

_renderUsers() {
const path = this.props.location.pathname;

return this.props.user.role === ROLE.ADMIN ?
return <IfGranted expected={ROLE.ADMIN} actual={this.props.user.role}>
<NavItem>
<NavLink to={Routes.users.path} isActive={() => path.startsWith(Routes.users.path)}
className="nav-link">{this.i18n('main.users-nav')}</NavLink>
</NavItem> : null
</NavItem>
</IfGranted>;
}

removeUnsupportedBrowserWarning() {
Expand All @@ -42,6 +47,14 @@ class MainView extends React.Component {
}
}

onProfileClick() {
if (isUsingOidcAuth()) {
window.location = userProfileLink();
} else {
transitionToWithOpts(Routes.editUser, {params: {username: this.props.user.username}});
}
}

render() {
if (this.props.status === ACTION_STATUS.PENDING) {
return <LoaderMask/>;
Expand All @@ -53,7 +66,7 @@ class MainView extends React.Component {
return (<div>{unauthRoutes}</div>);
}
const user = this.props.user;
const name = user.firstName.substr(0, 1) + '. ' + user.lastName;
const name = user.firstName.substring(0, 1) + '. ' + user.lastName;
const path = this.props.location.pathname;

return (
Expand Down Expand Up @@ -85,33 +98,35 @@ class MainView extends React.Component {
</NavItem>
: null
}
{user.role === ROLE.ADMIN && <NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.records.path)}
to={Routes.records.path}>{this.i18n('main.records-nav')}</NavLink>
</NavItem>
}
{user.role === ROLE.ADMIN &&
<NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.statistics.path)}
to={Routes.statistics.path}>{this.i18n('statistics.panel-title')}</NavLink>
</NavItem>
}
{user.role === ROLE.ADMIN &&
<NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.historyActions.path)}
to={Routes.historyActions.path}>{this.i18n('main.history')}</NavLink>
</NavItem>}
<IfGranted expected={ROLE.ADMIN} actual={user.role}>
<NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.records.path)}
to={Routes.records.path}>{this.i18n('main.records-nav')}</NavLink>
</NavItem>
</IfGranted>
<IfGranted expected={ROLE.ADMIN} actual={user.role}>
<NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.statistics.path)}
to={Routes.statistics.path}>{this.i18n('statistics.panel-title')}</NavLink>
</NavItem>
</IfGranted>
<IfGranted expected={ROLE.ADMIN} actual={user.role}>
<NavItem>
<NavLink className="nav-link"
isActive={() => path.startsWith(Routes.historyActions.path)}
to={Routes.historyActions.path}>{this.i18n('main.history')}</NavLink>
</NavItem>
</IfGranted>
</Nav>

<Nav>
<NavDropdown className="pr-0" id='logout' title={name}>
<DropdownItem
onClick={() => this.props.history.push(Routes.users.path + '/' + user.username)}>{this.i18n('main.my-profile')}</DropdownItem>
onClick={() => this.onProfileClick()}>{this.i18n('main.my-profile')}</DropdownItem>
<DropdownItem
onClick={() => this.props.history.push(Routes.logout.path)}>{this.i18n('main.logout')}</DropdownItem>
onClick={() => transitionTo(Routes.logout)}>{this.i18n('main.logout')}</DropdownItem>
</NavDropdown>

</Nav>
Expand All @@ -127,7 +142,7 @@ class MainView extends React.Component {
}
}

export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(withI18n(MainView)));
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(withI18n(withRouter(MainView))));

function mapStateToProps(state) {
return {
Expand Down
39 changes: 17 additions & 22 deletions js/components/login/Logout.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
'use strict';

import React from "react";
import withI18n from "../../i18n/withI18n";
import {injectIntl} from "react-intl";
import {connect} from "react-redux";
import {bindActionCreators} from "redux";
import React, {useContext} from "react";
import {useDispatch} from "react-redux";
import {logout} from "../../actions/AuthActions";
import {transitionTo} from "../../utils/Routing";
import Routes from "../../constants/RoutesConstants";
import {isUsingOidcAuth} from "../../utils/OidcUtils";
import {AuthContext} from "../misc/oidc/OidcAuthWrapper";

class Logout extends React.Component {
componentDidMount() {
this.props.logout();
transitionTo(Routes.login);
}
const Logout = () => {
const dispatch = useDispatch();
const authCtx = useContext(AuthContext);
React.useEffect(() => {
if (isUsingOidcAuth()) {
authCtx.logout();
}
dispatch(logout()).then(() => {
transitionTo(Routes.login);
});
});

render() {
return null;
}
return null;
}

export default connect(null, mapDispatchToProps)(injectIntl(withI18n(Logout)));

function mapDispatchToProps(dispatch) {
return {
logout: bindActionCreators(logout, dispatch)
}
}
export default Logout;
11 changes: 11 additions & 0 deletions js/components/misc/oidc/IfInternalAuth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import {isUsingOidcAuth} from "../../../utils/OidcUtils";

const IfInternalAuth = ({ children }) => {
if (isUsingOidcAuth()) {
return null;
}
return <>{children}</>;
};

export default IfInternalAuth;
Loading

0 comments on commit 3313f22

Please sign in to comment.