mirror of
https://github.com/diced/zipline.git
synced 2025-12-30 22:51:15 -08:00
the tokening
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -2571,6 +2571,14 @@
|
||||
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
|
||||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
|
||||
},
|
||||
"copy-to-clipboard": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz",
|
||||
"integrity": "sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==",
|
||||
"requires": {
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz",
|
||||
@@ -6836,6 +6844,11 @@
|
||||
"repeat-string": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"toggle-selection": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
||||
},
|
||||
"toml": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"@material-ui/lab": "^4.0.0-alpha.56",
|
||||
"bcrypt": "^5.0.0",
|
||||
"clsx": "^1.1.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"fastify": "^3.5.0",
|
||||
"fastify-cookie": "^4.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FastifyReply, FastifyRequest, FastifyInstance } from 'fastify';
|
||||
import { Controller, GET, POST, FastifyInstanceToken, Inject, Hook } from 'fastify-decorators';
|
||||
import { UserNotFoundError, MissingBodyData, LoginError, UserExistsError, NotAdministratorError } from '../lib/api/APIErrors';
|
||||
import { User } from '../lib/Data';
|
||||
import { checkPassword, decrypt, encrypt, encryptPassword } from '../lib/Encryption';
|
||||
import { checkPassword, createToken, encryptPassword } from '../lib/Encryption';
|
||||
|
||||
@Controller('/api/user')
|
||||
export class UserController {
|
||||
@@ -41,6 +41,18 @@ export class UserController {
|
||||
.send(user);
|
||||
}
|
||||
|
||||
@POST('/reset-token')
|
||||
async resetToken(req: FastifyRequest, reply: FastifyReply) {
|
||||
if (!req.cookies.zipline) throw new LoginError(`Not logged in.`);
|
||||
|
||||
const users = this.instance.mongo.db.collection('zipline_users');
|
||||
const user: User = await users.findOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) });
|
||||
if (!user) throw new UserNotFoundError(`User was not found.`);
|
||||
|
||||
users.updateOne({ _id: new this.instance.mongo.ObjectId(req.cookies.zipline) }, { $set: { token: createToken() } });
|
||||
return reply.send({ updated: true });
|
||||
}
|
||||
|
||||
@POST('/create')
|
||||
async create(req: FastifyRequest<{ Body: { username: string, password: string, administrator: boolean } }>, reply: FastifyReply) {
|
||||
if (!req.body.username) throw new MissingBodyData(`Missing username.`);
|
||||
@@ -54,6 +66,7 @@ export class UserController {
|
||||
const newUser: User = {
|
||||
username: req.body.username,
|
||||
password: encryptPassword(req.body.password),
|
||||
token: createToken(),
|
||||
administrator: req.body.administrator
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { AddressInfo } from 'net';
|
||||
import { ConsoleFormatter } from './lib/ConsoleFormatter';
|
||||
import { bold, green, reset } from '@dicedtomato/colors';
|
||||
import { Config, Configuration } from './lib/Config';
|
||||
import { decrypt, encrypt } from './lib/Encryption';
|
||||
|
||||
Console.setFormatter(new ConsoleFormatter());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface User {
|
||||
username: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
administrator: boolean;
|
||||
_id?: any;
|
||||
}
|
||||
@@ -5,12 +5,8 @@ import { Configuration } from './Config';
|
||||
const config = Configuration.readConfig();
|
||||
if (!config) process.exit(0);
|
||||
|
||||
export function encrypt(data: any) {
|
||||
return aes.encrypt(JSON.stringify(data), config.core.secret).toString();
|
||||
}
|
||||
|
||||
export function decrypt(data: string) {
|
||||
return aes.decrypt(data, config.core.secret).toString();
|
||||
export function createToken() {
|
||||
return aes.encrypt(Math.random().toString(36).substr(2) + Date.now(), config.core.secret).toString();
|
||||
}
|
||||
|
||||
export function encryptPassword(pass: string) {
|
||||
|
||||
@@ -8,6 +8,14 @@ import TextField from '@material-ui/core/TextField';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import DialogActions from '@material-ui/core/DialogActions';
|
||||
import DialogContent from '@material-ui/core/DialogContent';
|
||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { store } from '../lib/store';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -25,24 +33,102 @@ const useStyles = makeStyles({
|
||||
export default function IndexPage() {
|
||||
const classes = useStyles();
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const state = store.getState();
|
||||
const [alertMessage, setAlertMessage] = useState('Copied token!');
|
||||
const [tokenOpen, setTokenOpen] = useState(false);
|
||||
const [resetToken, setResetToken] = useState(false);
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
|
||||
const handleCopyTokenThenClose = async () => {
|
||||
const data = await (await fetch('/api/user/current')).json();
|
||||
if (!data.error) {
|
||||
copy(data.token);
|
||||
setAlertMessage('Copied token!');
|
||||
setTokenOpen(false);
|
||||
setAlertOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetTokenThenClose = async () => {
|
||||
const data = await (await fetch('/api/user/reset-token', { method: 'POST' })).json();
|
||||
if (!data.error && data.updated) {
|
||||
setAlertMessage('Reset token!');
|
||||
setResetToken(false);
|
||||
setAlertOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (typeof window !== 'undefined' && !state.loggedIn) router.push('/login');
|
||||
else {
|
||||
return (
|
||||
<UI>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
open={alertOpen}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setAlertOpen(false)}
|
||||
>
|
||||
<Alert severity="success" variant="filled">
|
||||
{alertMessage}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Paper elevation={3} className={classes.padding}>
|
||||
<Typography variant="h5">Welcome back, {state.user.username}</Typography>
|
||||
<Typography color="textSecondary">You have <b>2</b> images</Typography>
|
||||
<div className={classes.margin}>
|
||||
<Typography variant="h5">Token</Typography>
|
||||
<Button variant="contained" color="primary" className={classes.margin}>
|
||||
<Button variant="contained" color="primary" className={classes.margin} onClick={() => setTokenOpen(true)}>
|
||||
Copy
|
||||
</Button>
|
||||
<Button variant="contained" className={classes.margin} style={{ backgroundColor: "#d6381c", color: "white" }}>
|
||||
</Button>
|
||||
<Dialog
|
||||
open={tokenOpen}
|
||||
onClose={() => setTokenOpen(true)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
This token is used to upload images to Zipline, and should not be shared!
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTokenOpen(true)} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleCopyTokenThenClose} color="primary" autoFocus>
|
||||
Yes, copy!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Button variant="contained" className={classes.margin} onClick={() => setResetToken(true)} style={{ backgroundColor: "#d6381c", color: "white" }}>
|
||||
Reset
|
||||
</Button>
|
||||
<Dialog
|
||||
open={resetToken}
|
||||
onClose={() => setResetToken(true)}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
This token is used to upload images to Zipline, resetting your token will cause any uploading actions to not work until you update them your self.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setResetToken(true)} color="primary">
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleResetTokenThenClose} color="primary" autoFocus>
|
||||
Yes, reset!
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={classes.margin}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import Button from '@material-ui/core/Button';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -12,7 +13,7 @@ import Router from 'next/router';
|
||||
import { store, persistor } from '../lib/store';
|
||||
import { UPDATE_USER, LOGIN } from '../lib/reducer';
|
||||
import UIPlaceholder from '../components/UIPlaceholder';
|
||||
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
const useStyles = makeStyles({
|
||||
field: {
|
||||
width: '100%'
|
||||
@@ -28,6 +29,16 @@ export default function Index() {
|
||||
const state = store.getState();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = (event, reason) => {
|
||||
if (reason === 'clickaway') return;
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleLogin = async () => {
|
||||
const d = await (await fetch('/api/user/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) })).json()
|
||||
@@ -35,18 +46,33 @@ export default function Index() {
|
||||
dispatch({ type: UPDATE_USER, payload: d });
|
||||
dispatch({ type: LOGIN })
|
||||
Router.push('/');
|
||||
} else {
|
||||
setOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.loggedIn) Router.push('/');
|
||||
else return (
|
||||
<div>
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
open={open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Alert severity="error" variant="filled">
|
||||
Could not login!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<Grid container spacing={0} direction="column" alignItems="center" justify="center" style={{ minHeight: '100vh' }}>
|
||||
<Grid item xs={6}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography color="textSecondary" variant="h3" gutterBottom>
|
||||
Login
|
||||
Login
|
||||
</Typography>
|
||||
<TextField label="Username" className={classes.field} onChange={(e) => setUsername(e.target.value)} />
|
||||
<TextField label="Password" className={classes.field} onChange={(e) => setPassword(e.target.value)} />
|
||||
|
||||
Reference in New Issue
Block a user