Skip to content
This repository has been archived by the owner on Feb 25, 2024. It is now read-only.

Commit

Permalink
[S&C] Add a page to manager security access
Browse files Browse the repository at this point in the history
- Issue #49
  • Loading branch information
DjLeChuck committed Nov 19, 2017
1 parent 8f7c8a9 commit b6430ff
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 9 deletions.
7 changes: 7 additions & 0 deletions client/src/components/Layout.js
Expand Up @@ -106,6 +106,11 @@ const Layout = ({ t, i18n, children }) => {
glyph: 'log-out',
label: t('Déconnexion'),
}, 'logout');
const securityEntry = renderMenuEntry({
link: '/security',
glyph: 'lock',
label: t('Sécurité'),
}, 'security');

return (
<div>
Expand Down Expand Up @@ -137,6 +142,7 @@ const Layout = ({ t, i18n, children }) => {
{secondMenuEntries.map(renderMenuEntry)}
</NavDropdown>

{securityEntry}
{logOutEntry}
</Nav>
</Navbar.Collapse>
Expand All @@ -156,6 +162,7 @@ const Layout = ({ t, i18n, children }) => {
{secondMenuEntries.map(renderMenuEntry)}
</Nav>
<Nav className="nav-sidebar manager-version">
{securityEntry}
{logOutEntry}
<NavItem disabled><em>v2.0.2</em></NavItem>
</Nav>
Expand Down
74 changes: 74 additions & 0 deletions client/src/components/security/Container.js
@@ -0,0 +1,74 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { get, post } from '../../api';
import { promisifyData, cancelPromises } from '../../utils';
import Security from './Security';

class SecurityContainer extends Component {
static propTypes = {
t: PropTypes.func.isRequired,
};

constructor(props) {
super(props);

this.state = {
loaded: false,
saving: false,
stickyContent: null,
stickyStyle: 'danger',
authConfig: {},
};
}

async componentWillMount() {
const state = await promisifyData(get('authConfig'));
state.loaded = true;

this.setState(state);
}

componentWillUnmount() {
cancelPromises();
}

onSubmit = (values) => {
const { t } = this.props;

this.setState({ saving: true });

post('security', values).then(() => (
this.setState({
saving: false,
stickyContent: t('La configuration a bien été sauvegardée.'),
stickyStyle: 'success',
}, () => {
// Clear cookies
document.cookie.split(";").forEach(c =>
document.cookie = c.replace(/^ +/, "")
.replace(/=.*/, "=;expires=" + new Date()
.toUTCString() + ";path=/")
);
})
), () => (
this.setState({
saving: false,
stickyContent: t('Une erreur est survenue lors de la sauvegarde de la configuration.'),
stickyStyle: 'danger',
})
));
};

render() {
const { authConfig, ...rest } = this.state;

return (
<Security
{...rest} onSubmit={this.onSubmit} defaultValues={{ ...authConfig }}
/>
);
}
}

export default translate()(SecurityContainer);
35 changes: 35 additions & 0 deletions client/src/components/security/Security.js
@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import Loader from 'react-loader';
import StickyAlert from '../utils/StickyAlert';
import SecurityForm from '../forms/Security';

let stickyContainer;

const Security = ({ t, loaded, stickyStyle, stickyContent, ...rest }) => (
<div ref={el => stickyContainer = el}>
<div className="page-header"><h1>{t('Sécurité')}</h1></div>

<p className="important">
{t("Cette page permet de sécuriser l'accès à Recalbox Manager grâce à un identifiant et un mot de passe.")}
</p>

<StickyAlert bsStyle={stickyStyle} container={stickyContainer}>
{stickyContent}
</StickyAlert>

<Loader loaded={loaded}>
<SecurityForm {...rest} />
</Loader>
</div>
);

Security.propTypes = {
t: PropTypes.func.isRequired,
loaded: PropTypes.bool.isRequired,
stickyStyle: PropTypes.string,
stickyContent: PropTypes.string,
};

export default translate()(Security);
2 changes: 2 additions & 0 deletions client/src/routes.js
Expand Up @@ -16,6 +16,7 @@ import RomsList from './components/roms/list/Container';
import RomsView from './components/roms/view/Container';
import Screenshots from './components/screenshots/Container';
import Systems from './components/systems/Container';
import Security from './components/security/Container';
import NotFound from './components/NotFound';
import { get } from './api';

Expand Down Expand Up @@ -58,6 +59,7 @@ const routes = (
</Route>
<Route path="screenshots" component={Screenshots} />
<Route path="systems" component={Systems} />
<Route path="security" component={Security} />
<Route path="logout" onEnter={logOut} />
<Route path="*" component={NotFound} />
</Route>
Expand Down
15 changes: 15 additions & 0 deletions src/routes/get.js
Expand Up @@ -178,6 +178,21 @@ router.get('/', async (req, res, next) => {
case 'logout':
req.session = null;
data = true;
break;
case 'authConfig':
if (!fs.existsSync(config.get('auth'))) {
data = { needAuth: false };
} else {
const authCredentials = JSON.parse(
fs.readFileSync(config.get('auth')).toString()
);

data = {
needAuth: 1,
login: authCredentials.login,
};
}

break;
default:
throw new Error(`Option "${option}" unknown`);
Expand Down
45 changes: 36 additions & 9 deletions src/routes/post.js
Expand Up @@ -136,27 +136,46 @@ router.post('/', async (req, res, next) => {
}
break;
case 'login':
const { login, password } = body;
const authFile = config.get('auth');

try {
const authFile = config.get('auth');
const credentials = JSON.parse(fs.readFileSync(authFile).toString());

if (
credentials.login !== login || credentials.password !== password
credentials.login !== body.login ||
credentials.password !== body.password
) {
throw new Error('Bad credentials.');
}

const hashedCredentials = crypto.createHash('sha256')
.update(`${credentials.login}\n${credentials.password}`, 'utf8')
.digest().toString();

req.session.isAuthenticated = hashedCredentials;
setSessionCredentials(req, credentials);
} catch (error) {
throw new Error('Bad credentials.');
}

break;
case 'security':
const { needAuth, ...securityRest } = body;
const authFile = config.get('auth');

try {
if (!needAuth) {
if (fs.existsSync(authFile)) {
fs.unlinkSync(authFile);

req.session = null;
}
} else {
const securityCredentials = { ...securityRest };
const content = JSON.stringify(securityCredentials);

fs.writeFileSync(authFile, content);

setSessionCredentials(req, securityCredentials);
}
} catch (error) {
throw new Error('Error while saving security credentials.');
}

break;
default:
throw new Error(`Action "${action}" unknown.`);
Expand All @@ -168,6 +187,14 @@ router.post('/', async (req, res, next) => {
}
});

function setSessionCredentials(req, credentials) {
const hashedCredentials = crypto.createHash('sha256')
.update(`${credentials.login}\n${credentials.password}`, 'utf8')
.digest().toString();

req.session.isAuthenticated = hashedCredentials;
}

async function deleteRom(payload) {
// Delete ROM file
for (let i = 0; i <= payload.files.length; i++) {
Expand Down

0 comments on commit b6430ff

Please sign in to comment.