Skip to content

Commit

Permalink
Adjusting token generation form and adding plagiarism and ref-solutio…
Browse files Browse the repository at this point in the history
…n scopes.
  • Loading branch information
krulis-martin committed Oct 5, 2023
1 parent c76894c commit 5e28e53
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 179 deletions.
288 changes: 141 additions & 147 deletions src/components/forms/GenerateTokenForm/GenerateTokenForm.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { reduxForm, Field } from 'redux-form';
import { OverlayTrigger, Tooltip, Container, Row, Col } from 'react-bootstrap';
import { OverlayTrigger, Tooltip, Row, Col } from 'react-bootstrap';
import { CopyToClipboard } from 'react-copy-to-clipboard';

import FormBox from '../../widgets/FormBox';
import Button from '../../widgets/TheButton';
import Button, { TheButtonGroup } from '../../widgets/TheButton';
import Callout from '../../widgets/Callout';
import InsetPanel from '../../widgets/InsetPanel';
import SubmitButton from '../SubmitButton';
import { CheckboxField, SelectField } from '../Fields';
import { CopyIcon } from '../../icons';
import Icon, { CopyIcon } from '../../icons';
import { objectMap } from '../../../helpers/common';

import './GenerateTokenForm.css';

const availableScopes = defineMessages({
'read-all': {
id: 'app.generateTokenForm.scope.readAll',
defaultMessage: 'Read-only',
const scopes = [
{
name: (
<FormattedMessage id="app.generateTokenForm.scope.master" defaultMessage="Master (all operations, default)" />
),
key: 'master',
},
master: {
id: 'app.generateTokenForm.scope.master',
defaultMessage: 'Read-write',
{ name: <FormattedMessage id="app.generateTokenForm.scope.readAll" defaultMessage="Read-only" />, key: 'read-all' },
{
name: <FormattedMessage id="app.generateTokenForm.scope.plagiarism" defaultMessage="Plagiarism detection" />,
key: 'plagiarism',
},
refresh: {
id: 'app.generateTokenForm.scope.refresh',
defaultMessage: 'Allow refreshing indefinitely',
{
name: <FormattedMessage id="app.generateTokenForm.scope.ref-solutions" defaultMessage="Reference solutions" />,
key: 'ref-solutions',
},
});
];

const HOUR_SEC = 3600;
const DAY_SEC = 24 * HOUR_SEC;
Expand Down Expand Up @@ -60,8 +63,13 @@ const messages = defineMessages({
},
});

export const initialValues = {
scope: 'master',
expiration: '604800', // one week (in string)
refresh: true,
};

const GenerateTokenForm = ({
warning,
error,
submitting,
handleSubmit,
Expand All @@ -70,117 +78,130 @@ const GenerateTokenForm = ({
invalid,
lastToken,
intl: { formatMessage },
}) => (
<FormBox
title={<FormattedMessage id="app.generateTokenForm.title" defaultMessage="Generate Application Token" />}
type={submitSucceeded ? 'success' : undefined}
footer={
<div className="text-center">
{lastToken && (
<CopyToClipboard text={lastToken}>
<OverlayTrigger
trigger="click"
rootClose
placement="bottom"
overlay={
<Tooltip id={lastToken}>
<FormattedMessage id="app.generateTokenForm.copied" defaultMessage="Copied!" />
</Tooltip>
}>
<Button variant="info">
<CopyIcon gapRight fixedWidth />
<FormattedMessage id="app.generateTokenForm.copyToClipboard" defaultMessage="Copy to Clipboard" />
</Button>
</OverlayTrigger>
</CopyToClipboard>
)}

<SubmitButton
id="generateToken"
handleSubmit={handleSubmit}
submitting={submitting}
hasSucceeded={submitSucceeded}
hasFailed={submitFailed}
invalid={invalid}
messages={{
submit: <FormattedMessage id="app.generateTokenForm.generate" defaultMessage="Generate" />,
submitting: <FormattedMessage id="app.generateTokenForm.generating" defaultMessage="Generating..." />,
success: <FormattedMessage id="app.generateTokenForm.generated" defaultMessage="Generated" />,
}}
}) => {
const [copied, setCopied] = useState(false);
return (
<FormBox
title={<FormattedMessage id="app.generateTokenForm.title" defaultMessage="Generate Application Token" />}
type={submitSucceeded ? 'success' : undefined}
footer={
<div className="text-center">
<TheButtonGroup>
{lastToken &&
(copied ? (
<OverlayTrigger
trigger={['hover', 'focus', 'click']}
defaultShow={true}
placement="bottom"
onToggle={shown => !shown && setCopied(false)}
overlay={
<Tooltip id={lastToken}>
<FormattedMessage id="app.generateTokenForm.copied" defaultMessage="Copied!" />
</Tooltip>
}>
<Button variant="secondary" disabled>
<Icon icon="clipboard-check" gapRight fixedWidth />
<FormattedMessage id="app.generateTokenForm.copyToClipboard" defaultMessage="Copy to Clipboard" />
</Button>
</OverlayTrigger>
) : (
<CopyToClipboard text={lastToken} onCopy={() => setCopied(true)}>
<Button variant="info">
<CopyIcon gapRight fixedWidth />
<FormattedMessage id="app.generateTokenForm.copyToClipboard" defaultMessage="Copy to Clipboard" />
</Button>
</CopyToClipboard>
))}

<SubmitButton
id="generateToken"
handleSubmit={handleSubmit}
submitting={submitting}
hasSucceeded={submitSucceeded}
hasFailed={submitFailed}
invalid={invalid}
messages={{
submit: <FormattedMessage id="app.generateTokenForm.generate" defaultMessage="Generate" />,
submitting: <FormattedMessage id="app.generateTokenForm.generating" defaultMessage="Generating..." />,
success: <FormattedMessage id="app.generateTokenForm.generated" defaultMessage="Generated" />,
}}
/>
</TheButtonGroup>
</div>
}>
<InsetPanel>
<FormattedMessage
id="app.generateTokenForm.info"
defaultMessage="This form should help advanced users to access API directly. It may be used to generate security tokens which are used for authentication and authorization of API operations. The scopes may restrict the set of operations authorized by the token beyond the limitations of the user role."
/>
</div>
}>
<InsetPanel>
<FormattedMessage
id="app.generateTokenForm.info"
defaultMessage="This form should help advanced users to access API directly. It may be used to generate security tokens which are used for authentication and authorization of API operations. The scopes may restrict the set of operations authorized by the token beyond the limitations of the user role."
/>
</InsetPanel>
</InsetPanel>

<h4 className="em-margin-bottom">
<FormattedMessage id="app.generateTokenForm.scopes" defaultMessage="Scopes:" />
</h4>
<Container fluid>
<Row>
{Object.values(
objectMap(availableScopes, (message, scope) => (
<Col sm={6} key={scope}>
<Field
name={`scopes.${scope}`}
component={CheckboxField}
onOff
label={formatMessage(message)}
ignoreDirty
<Col md={12} lg={6} xl={4}>
<Field
name="scope"
component={SelectField}
options={scopes}
label={<FormattedMessage id="app.generateTokenForm.scope" defaultMessage="Scope:" />}
ignoreDirty
/>
</Col>
<Col md={12} lg={6} xl={4}>
<Field
name="expiration"
component={SelectField}
options={Object.values(
objectMap(messages, (value, key) => {
return { name: formatMessage(value), key };
})
)}
label={<FormattedMessage id="app.generateTokenForm.expiration" defaultMessage="Expires In:" />}
ignoreDirty
/>
</Col>
<Col lg={12} xl={4} className="align-self-end">
<Field
name="refresh"
component={CheckboxField}
onOff
label={
<FormattedMessage
id="app.generateTokenForm.scope.refresh"
defaultMessage="Allow refreshing indefinitely"
/>
</Col>
))
)}
}
ignoreDirty
/>
</Col>
</Row>
</Container>

{warning && <Callout variant="warning">{warning}</Callout>}
{error && <Callout variant="danger">{error}</Callout>}

{error && <Callout variant="danger">{error}</Callout>}
{submitFailed && (
<Callout variant="danger">
<FormattedMessage
id="app.generateTokenForm.failed"
defaultMessage="The process of token creation has failed. Please try again later."
/>
</Callout>
)}

{submitFailed && (
<Callout variant="danger">
<FormattedMessage
id="app.generateTokenForm.failed"
defaultMessage="The process of token creation has failed. Please try again later."
/>
</Callout>
)}

<hr />

<Field
name="expiration"
component={SelectField}
options={Object.values(
objectMap(messages, (value, key) => {
return { name: formatMessage(value), key };
})
{lastToken !== '' && (
<div>
<hr />
<h4>
<FormattedMessage id="app.generateTokenForm.lastToken" defaultMessage="Last Token:" />
</h4>
<InsetPanel className="tokenWell">
<code>{lastToken}</code>
</InsetPanel>
</div>
)}
label={<FormattedMessage id="app.generateTokenForm.expiration" defaultMessage="Expires In:" />}
ignoreDirty
/>

{lastToken !== '' && (
<div>
<hr />
<h4>
<FormattedMessage id="app.generateTokenForm.lastToken" defaultMessage="Last Token:" />
</h4>
<InsetPanel className="tokenWell">
<code>{lastToken}</code>
</InsetPanel>
</div>
)}
</FormBox>
);
</FormBox>
);
};

GenerateTokenForm.propTypes = {
warning: PropTypes.any,
error: PropTypes.any,
handleSubmit: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
Expand All @@ -192,27 +213,17 @@ GenerateTokenForm.propTypes = {
intl: PropTypes.object.isRequired,
};

const validate = ({ scopes, expiration }) => {
const validate = ({ scope, expiration }) => {
const errors = {};
if (!scopes || !expiration) {
if (!scope || !expiration) {
return errors;
}

if (!scopes['read-all'] && !scopes.master) {
errors._error = (
<FormattedMessage
id="app.generateTokenForm.validate.noScopes"
defaultMessage="At least one main scope (<i>read-only</i> or <i>read-write</i>) has to be selected."
values={{
i: text => <i>{text}</i>,
}}
/>
);
} else if (scopes.master && expiration > WEEK_SEC) {
if (scope === 'master' && expiration > WEEK_SEC) {
errors._error = (
<FormattedMessage
id="app.generateTokenForm.validate.expirationTooLong"
defaultMessage="The <i>read-write</i> scope has restricted maximal expiration time to one week at the moment."
defaultMessage="The <i>Master</i> scope has restricted maximal expiration time to one week at the moment."
values={{
i: text => <i>{text}</i>,
}}
Expand All @@ -222,28 +233,11 @@ const validate = ({ scopes, expiration }) => {
return errors;
};

const warn = ({ scopes }) => {
const warnings = {};
if (scopes && scopes['read-all'] && scopes.master) {
warnings._warning = (
<FormattedMessage
id="app.generateTokenForm.warnBothMasterAndReadAll"
defaultMessage="The <i>read-only</i> scope will have no effect whilst <i>read-write</i> scope is set since <i>read-write</i> implicitly contains <i>read-only</i>."
values={{
i: text => <i>{text}</i>,
}}
/>
);
}
return warnings;
};

export default injectIntl(
reduxForm({
form: 'generate-token',
enableReinitialize: true,
keepDirtyOnReinitialize: false,
validate,
warn,
})(GenerateTokenForm)
);
3 changes: 2 additions & 1 deletion src/components/forms/GenerateTokenForm/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import GenerateTokenForm from './GenerateTokenForm';
import GenerateTokenForm, { initialValues } from './GenerateTokenForm';
export default GenerateTokenForm;
export { initialValues };
10 changes: 5 additions & 5 deletions src/locales/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -989,14 +989,14 @@
"app.generateTokenForm.info": "Tento formulář slouží pokročilým uživatelům pro snazší přímé používání API. S jeho pomocí je možné generovat bezpečnostní tokeny, které se používají pro autentizaci a autorizaci operací API. Rozsah platnosti může omezit povolené operace nad rámec restrikcí, které jsou dány uživatelskou rolí.",
"app.generateTokenForm.lastToken": "Poslední token:",
"app.generateTokenForm.month": "1 Měsíc",
"app.generateTokenForm.scope.master": "Čtení a zápis",
"app.generateTokenForm.scope": "Rozsah platnosti:",
"app.generateTokenForm.scope.master": "Master (všechny operace, výchozí)",
"app.generateTokenForm.scope.plagiarism": "Detekce podobných řešení",
"app.generateTokenForm.scope.readAll": "Pouze čtení",
"app.generateTokenForm.scope.ref-solutions": "Referenční řešení",
"app.generateTokenForm.scope.refresh": "Povolit nekonečné prodlužování",
"app.generateTokenForm.scopes": "Rozsahy platnosti:",
"app.generateTokenForm.title": "Generování aplikačních tokenů",
"app.generateTokenForm.validate.expirationTooLong": "Povolený rozsah <i>pro čtení a zápis</i> má v tuto chvíli omezenou maximální životnost na jeden týden.",
"app.generateTokenForm.validate.noScopes": "Alespoň jeden hlavní povolený rozsah (<i>pouze pro čtení</i> nebo <i>pro čtení a zápis</i>) musí být vybrán.",
"app.generateTokenForm.warnBothMasterAndReadAll": "Povolený rozsah <i>pouze pro čtení</i> nebude mít žádný efekt, neboť byl zároveň vybrán rozsah <i>čtení a zápis</i>, který v sobě <i>čtení</i> obsahuje implicitně.",
"app.generateTokenForm.validate.expirationTooLong": "Povolený rozsah <i>Master</i> má v tuto chvíli omezenou maximální životnost na jeden týden.",
"app.generateTokenForm.week": "1 Týden",
"app.generateTokenForm.year": "1 Rok",
"app.group.adminsView.addSupervisor": "Přidat vedoucího",
Expand Down
Loading

0 comments on commit 5e28e53

Please sign in to comment.