mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
feat: add generate token endpoint and ui for generating tokens for users
This commit is contained in:
@@ -18,5 +18,6 @@ export const SYSTEM_STATUS_ENDPOINT = ENDPOINT_ROOT + "systemStatus";
|
||||
export const SIGN_IN_ENDPOINT = ENDPOINT_ROOT + "signIn";
|
||||
export const VERIFY_AUTHORIZATION_ENDPOINT = ENDPOINT_ROOT + "verifyAuthorization";
|
||||
export const SECURITY_SETTINGS_ENDPOINT = ENDPOINT_ROOT + "securitySettings";
|
||||
export const GENERATE_TOKEN_ENDPOINT = ENDPOINT_ROOT + "generateToken";
|
||||
export const RESTART_ENDPOINT = ENDPOINT_ROOT + "restart";
|
||||
export const FACTORY_RESET_ENDPOINT = ENDPOINT_ROOT + "factoryReset";
|
||||
|
||||
77
interface/src/security/GenerateToken.tsx
Normal file
77
interface/src/security/GenerateToken.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Box, LinearProgress, Typography, TextField } from '@material-ui/core';
|
||||
|
||||
import { FormButton } from '../components';
|
||||
import { redirectingAuthorizedFetch } from '../authentication';
|
||||
import { GENERATE_TOKEN_ENDPOINT } from '../api';
|
||||
import { withSnackbar, WithSnackbarProps } from 'notistack';
|
||||
|
||||
interface GenerateTokenProps extends WithSnackbarProps {
|
||||
username: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface GenerateTokenState {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
class GenerateToken extends React.Component<GenerateTokenProps, GenerateTokenState> {
|
||||
|
||||
state: GenerateTokenState = {};
|
||||
|
||||
componentDidMount() {
|
||||
const { username } = this.props;
|
||||
redirectingAuthorizedFetch(GENERATE_TOKEN_ENDPOINT + "?" + new URLSearchParams({ username }), { method: 'GET' })
|
||||
.then(response => {
|
||||
if (response.status === 200) {
|
||||
return response.json();
|
||||
} else {
|
||||
throw Error("Error generating token: " + response.status);
|
||||
}
|
||||
}).then(generatedToken => {
|
||||
console.log(generatedToken);
|
||||
this.setState({ token: generatedToken.token });
|
||||
})
|
||||
.catch(error => {
|
||||
this.props.enqueueSnackbar(error.message || "Problem generating token", { variant: 'error' });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onClose, username } = this.props;
|
||||
const { token } = this.state;
|
||||
return (
|
||||
<Dialog onClose={onClose} aria-labelledby="generate-token-dialog-title" open fullWidth maxWidth="sm">
|
||||
<DialogTitle id="generate-token-dialog-title">Token for: {username}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{token ?
|
||||
<Fragment>
|
||||
<Box bgcolor="primary.main" color="primary.contrastText" p={2} mt={2} mb={2}>
|
||||
<Typography variant="body1">
|
||||
The token below may be used to access the secured APIs. This may be used for bearer authentication with the "Authorization" header or using the "access_token" query paramater.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box mt={2} mb={2}>
|
||||
<TextField label="Token" multiline value={token} fullWidth contentEditable={false} />
|
||||
</Box>
|
||||
</Fragment>
|
||||
:
|
||||
<Box m={4} textAlign="center">
|
||||
<LinearProgress />
|
||||
<Typography variant="h6">
|
||||
Generating token…
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<FormButton variant="contained" color="primary" type="submit" onClick={onClose}>
|
||||
Close
|
||||
</FormButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withSnackbar(GenerateToken);
|
||||
@@ -11,12 +11,14 @@ import CheckIcon from '@material-ui/icons/Check';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import VpnKeyIcon from '@material-ui/icons/VpnKey';
|
||||
|
||||
import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
|
||||
import { RestFormProps, FormActions, FormButton, extractEventValue } from '../components';
|
||||
|
||||
import UserForm from './UserForm';
|
||||
import { SecuritySettings, User } from './types';
|
||||
import GenerateToken from './GenerateToken';
|
||||
|
||||
function compareUsers(a: User, b: User) {
|
||||
if (a.username < b.username) {
|
||||
@@ -33,6 +35,7 @@ type ManageUsersFormProps = RestFormProps<SecuritySettings> & AuthenticatedConte
|
||||
type ManageUsersFormState = {
|
||||
creating: boolean;
|
||||
user?: User;
|
||||
generateTokenFor?: string;
|
||||
}
|
||||
|
||||
class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersFormState> {
|
||||
@@ -66,6 +69,18 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
this.props.setData({ ...data, users });
|
||||
}
|
||||
|
||||
closeGenerateToken = () => {
|
||||
this.setState({
|
||||
generateTokenFor: undefined
|
||||
});
|
||||
}
|
||||
|
||||
generateToken = (user: User) => {
|
||||
this.setState({
|
||||
generateTokenFor: user.username
|
||||
});
|
||||
}
|
||||
|
||||
startEditingUser = (user: User) => {
|
||||
this.setState({
|
||||
creating: false,
|
||||
@@ -103,7 +118,7 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
|
||||
render() {
|
||||
const { width, data } = this.props;
|
||||
const { user, creating } = this.state;
|
||||
const { user, creating, generateTokenFor } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<ValidatorForm onSubmit={this.onSubmit}>
|
||||
@@ -125,6 +140,9 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
{user.admin ? <CheckIcon /> : <CloseIcon />}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<IconButton size="small" aria-label="Generate Token" onClick={() => this.generateToken(user)}>
|
||||
<VpnKeyIcon />
|
||||
</IconButton>
|
||||
<IconButton size="small" aria-label="Delete" onClick={() => this.removeUser(user)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
@@ -162,6 +180,9 @@ class ManageUsersForm extends React.Component<ManageUsersFormProps, ManageUsersF
|
||||
</FormButton>
|
||||
</FormActions>
|
||||
</ValidatorForm>
|
||||
{
|
||||
generateTokenFor && <GenerateToken username={generateTokenFor} onClose={this.closeGenerateToken} />
|
||||
}
|
||||
{
|
||||
user &&
|
||||
<UserForm
|
||||
|
||||
@@ -32,7 +32,7 @@ class UserForm extends React.Component<UserFormProps> {
|
||||
const { user, creating, handleValueChange, onDoneEditing, onCancelEditing } = this.props;
|
||||
return (
|
||||
<ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
|
||||
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open>
|
||||
<Dialog onClose={onCancelEditing} aria-labelledby="user-form-dialog-title" open fullWidth maxWidth="sm">
|
||||
<DialogTitle id="user-form-dialog-title">{creating ? 'Add' : 'Modify'} User</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<TextValidator
|
||||
|
||||
@@ -9,3 +9,6 @@ export interface SecuritySettings {
|
||||
jwt_secret: string;
|
||||
}
|
||||
|
||||
export interface GeneratedToken {
|
||||
token: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user