wip
This commit is contained in:
parent
b924fbd314
commit
7114830582
|
@ -1,17 +1,11 @@
|
|||
import type { FastifyRequest, FastifyReply, HookHandlerDoneFunction } from 'fastify';
|
||||
import type { FastifyReply, HookHandlerDoneFunction } from 'fastify';
|
||||
import type { RequestWithUser } from '../structures/interfaces';
|
||||
import JWT from 'jsonwebtoken';
|
||||
import prisma from '../structures/database';
|
||||
|
||||
interface Decoded {
|
||||
sub: number;
|
||||
}
|
||||
export interface RequestWithUser extends FastifyRequest {
|
||||
user: {
|
||||
id: number;
|
||||
username: string | null;
|
||||
isAdmin: boolean | null;
|
||||
};
|
||||
}
|
||||
|
||||
export default (req: RequestWithUser, res: FastifyReply, next: HookHandlerDoneFunction) => {
|
||||
if (!req.headers.authorization) return res.status(401).send({ message: 'No authorization header provided' });
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
ip: string;
|
||||
}
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
// const { username, password }: { username: string; password: string } = req.body;
|
||||
const { ip } = req.body as body;
|
||||
if (!ip) return res.status(400).send({ message: 'No ip provided' });
|
||||
|
||||
await prisma.bans.create({
|
||||
data: {
|
||||
ip
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully banned the ip'
|
||||
});
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class banIP extends Route {
|
||||
constructor() {
|
||||
super('/admin/ban/ip', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { ip } = req.body;
|
||||
if (!ip) return res.status(400).json({ message: 'No ip provided' });
|
||||
|
||||
try {
|
||||
await db.table('bans').insert({ ip });
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully banned the ip'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = banIP;
|
|
@ -0,0 +1,56 @@
|
|||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import prisma from '../../../../structures/database';
|
||||
import { constructFilePublicLink } from '../../../../utils/Util';
|
||||
import type { User } from '../../../../structures/interfaces';
|
||||
interface params {
|
||||
id: number;
|
||||
}
|
||||
interface UserWithFileCount extends User {
|
||||
fileCount?: number;
|
||||
}
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
||||
const { id } = req.params as params;
|
||||
if (!id) return res.status(400).send({ message: 'Invalid file ID supplied' });
|
||||
|
||||
const file = await prisma.files.findUnique({
|
||||
where: {
|
||||
id
|
||||
}
|
||||
});
|
||||
|
||||
if (!file) return res.status(404).send({ message: 'File doesn\'t exist' });
|
||||
|
||||
let user;
|
||||
if (file.userId) {
|
||||
user = await prisma.users.findUnique({
|
||||
where: {
|
||||
id: file.userId
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
enabled: true,
|
||||
createdAt: true,
|
||||
editedAt: true,
|
||||
isAdmin: true
|
||||
}
|
||||
});
|
||||
|
||||
if (user) {
|
||||
// TODO: ???
|
||||
(user as unknown as UserWithFileCount).fileCount = await prisma.files.count({
|
||||
where: {
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
const extendedFile = constructFilePublicLink(req, file);
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully retrieved file',
|
||||
file: extendedFile,
|
||||
user
|
||||
});
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
const Util = require('../../utils/Util');
|
||||
|
||||
class filesGET extends Route {
|
||||
constructor() {
|
||||
super('/admin/file/:id', 'get', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db) {
|
||||
const { id } = req.params;
|
||||
if (!id) return res.status(400).json({ message: 'Invalid file ID supplied' });
|
||||
|
||||
let file = await db.table('files').where({ id }).first();
|
||||
const user = await db.table('users')
|
||||
.select('id', 'username', 'enabled', 'createdAt', 'editedAt', 'apiKeyEditedAt', 'isAdmin')
|
||||
.where({ id: file.userId })
|
||||
.first();
|
||||
file = Util.constructFilePublicLink(req, file);
|
||||
|
||||
// Additional relevant data
|
||||
const filesFromUser = await db.table('files').where({ userId: user.id }).select('id');
|
||||
user.fileCount = filesFromUser.length;
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully retrieved file',
|
||||
file,
|
||||
user
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = filesGET;
|
|
@ -1,27 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class unBanIP extends Route {
|
||||
constructor() {
|
||||
super('/admin/unban/ip', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { ip } = req.body;
|
||||
if (!ip) return res.status(400).json({ message: 'No ip provided' });
|
||||
|
||||
try {
|
||||
await db.table('bans')
|
||||
.where({ ip })
|
||||
.delete();
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully unbanned the ip'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = unBanIP;
|
|
@ -0,0 +1,31 @@
|
|||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
ip: string;
|
||||
}
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
// const { username, password }: { username: string; password: string } = req.body;
|
||||
const { ip } = req.body as body;
|
||||
if (!ip) return res.status(400).send({ message: 'No ip provided' });
|
||||
|
||||
const record = await prisma.bans.findFirst({
|
||||
where: {
|
||||
ip
|
||||
}
|
||||
});
|
||||
|
||||
if (record) {
|
||||
await prisma.bans.delete({
|
||||
where: {
|
||||
id: record.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully unbanned the ip'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { id } = req.body as body;
|
||||
if (!id) return res.status(400).send({ message: 'No id provided' });
|
||||
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id
|
||||
},
|
||||
data: {
|
||||
isAdmin: false
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully demoted user'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { id } = req.body as body;
|
||||
if (!id) return res.status(400).send({ message: 'No id provided' });
|
||||
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id
|
||||
},
|
||||
data: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully disabled user'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { id } = req.body as body;
|
||||
if (!id) return res.status(400).send({ message: 'No id provided' });
|
||||
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id
|
||||
},
|
||||
data: {
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully enabled user'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
|
||||
interface body {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { id } = req.body as body;
|
||||
if (!id) return res.status(400).send({ message: 'No id provided' });
|
||||
if (id === req.user.id) return res.status(400).send({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id
|
||||
},
|
||||
data: {
|
||||
isAdmin: true
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully promoted user'
|
||||
});
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { deleteAllFilesFromUser } from '../../../../utils/Util';
|
||||
|
||||
interface body {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { id } = req.body as body;
|
||||
if (!id) return res.status(400).send({ message: 'No id provided' });
|
||||
|
||||
await deleteAllFilesFromUser(id);
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully demoted user'
|
||||
});
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class userDemote extends Route {
|
||||
constructor() {
|
||||
super('/admin/users/demote', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { id } = req.body;
|
||||
if (!id) return res.status(400).json({ message: 'No id provided' });
|
||||
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
try {
|
||||
await db.table('users')
|
||||
.where({ id })
|
||||
.update({ isAdmin: false })
|
||||
.wasMutated();
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully demoted user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = userDemote;
|
|
@ -1,29 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class userDisable extends Route {
|
||||
constructor() {
|
||||
super('/admin/users/disable', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { id } = req.body;
|
||||
if (!id) return res.status(400).json({ message: 'No id provided' });
|
||||
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
try {
|
||||
await db.table('users')
|
||||
.where({ id })
|
||||
.update({ enabled: false })
|
||||
.wasMutated();
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully disabled user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = userDisable;
|
|
@ -1,29 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class userEnable extends Route {
|
||||
constructor() {
|
||||
super('/admin/users/enable', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { id } = req.body;
|
||||
if (!id) return res.status(400).json({ message: 'No id provided' });
|
||||
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
try {
|
||||
await db.table('users')
|
||||
.where({ id })
|
||||
.update({ enabled: true })
|
||||
.wasMutated();
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully enabled user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = userEnable;
|
|
@ -1,28 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class userPromote extends Route {
|
||||
constructor() {
|
||||
super('/admin/users/promote', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { id } = req.body;
|
||||
if (!id) return res.status(400).json({ message: 'No id provided' });
|
||||
if (id === user.id) return res.status(400).json({ message: 'You can\'t apply this action to yourself' });
|
||||
|
||||
try {
|
||||
await db.table('users')
|
||||
.where({ id })
|
||||
.update({ isAdmin: true });
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully promoted user'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = userPromote;
|
|
@ -1,26 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
const Util = require('../../utils/Util');
|
||||
|
||||
class userDemote extends Route {
|
||||
constructor() {
|
||||
super('/admin/users/purge', 'post', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { id } = req.body;
|
||||
if (!id) return res.status(400).json({ message: 'No id provided' });
|
||||
|
||||
try {
|
||||
await Util.deleteAllFilesFromUser(id);
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully deleted the user\'s files'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = userDemote;
|
|
@ -0,0 +1,21 @@
|
|||
import type { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import prisma from '../../../structures/database';
|
||||
|
||||
export const middlewares = ['auth', 'admin'];
|
||||
|
||||
export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
||||
const users = await prisma.users.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
enabled: true,
|
||||
isAdmin: true,
|
||||
createdAt: true
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully retrieved users',
|
||||
users
|
||||
});
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class usersGET extends Route {
|
||||
constructor() {
|
||||
super('/admin/users', 'get', { adminOnly: true });
|
||||
}
|
||||
|
||||
async run(req, res, db) {
|
||||
try {
|
||||
const users = await db.table('users')
|
||||
.select('id', 'username', 'enabled', 'isAdmin', 'createdAt');
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully retrieved users',
|
||||
users
|
||||
});
|
||||
} catch (error) {
|
||||
return super.error(res, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = usersGET;
|
|
@ -22,7 +22,7 @@ export const run = async (req: FastifyRequest, res: FastifyReply) => {
|
|||
|
||||
if (!user) return res.status(401).send({ message: 'User doesn\'t exist' });
|
||||
|
||||
const comparePassword = await bcrypt.compare(password, user.password ?? '');
|
||||
const comparePassword = await bcrypt.compare(password, user.password);
|
||||
if (!comparePassword) return res.status(401).send({ message: 'Invalid authorization.' });
|
||||
|
||||
const jwt = JWT.sign({
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
const randomstring = require('randomstring');
|
||||
const moment = require('moment');
|
||||
const { dump } = require('dumper.js');
|
||||
const Route = require('../../structures/Route');
|
||||
|
||||
class apiKeyPOST extends Route {
|
||||
constructor() {
|
||||
super('/user/apikey/change', 'post');
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
const now = moment.utc().toDate();
|
||||
const apiKey = randomstring.generate(64);
|
||||
|
||||
try {
|
||||
await db.table('users')
|
||||
.where({ id: user.id })
|
||||
.update({
|
||||
apiKey,
|
||||
apiKeyEditedAt: now
|
||||
});
|
||||
} catch (error) {
|
||||
dump(error);
|
||||
return res.status(401).json({ message: 'There was a problem processing your account' });
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Successfully created new api key',
|
||||
apiKey
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = apiKeyPOST;
|
|
@ -0,0 +1,26 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
import { utc } from 'moment';
|
||||
import randomstring from 'randomstring';
|
||||
export const middlewares = ['auth'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
const now = utc().toDate();
|
||||
const apiKey = randomstring.generate(64);
|
||||
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id: req.user.id
|
||||
},
|
||||
data: {
|
||||
apiKey,
|
||||
apiKeyEditedAt: now
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({
|
||||
message: 'Successfully created new api key',
|
||||
apiKey
|
||||
});
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
const Route = require('../../structures/Route');
|
||||
const log = require('../../utils/Log');
|
||||
|
||||
class changePasswordPOST extends Route {
|
||||
constructor() {
|
||||
super('/user/password/change', 'post');
|
||||
}
|
||||
|
||||
async run(req, res, db, user) {
|
||||
if (!req.body) return res.status(400).json({ message: 'No body provided' });
|
||||
const { password, newPassword } = req.body;
|
||||
if (!password || !newPassword) return res.status(401).json({ message: 'Invalid body provided' });
|
||||
if (password === newPassword) return res.status(400).json({ message: 'Passwords have to be different' });
|
||||
|
||||
/*
|
||||
Checks if the password is right
|
||||
*/
|
||||
const comparePassword = await bcrypt.compare(password, user.password);
|
||||
if (!comparePassword) return res.status(401).json({ message: 'Current password is incorrect' });
|
||||
|
||||
if (newPassword.length < 6 || newPassword.length > 64) {
|
||||
return res.status(400).json({ message: 'Password must have 6-64 characters' });
|
||||
}
|
||||
|
||||
let hash;
|
||||
try {
|
||||
hash = await bcrypt.hash(newPassword, 10);
|
||||
} catch (error) {
|
||||
log.error('Error generating password hash');
|
||||
log.error(error);
|
||||
return res.status(401).json({ message: 'There was a problem processing your account' });
|
||||
}
|
||||
|
||||
const now = moment.utc().toDate();
|
||||
await db.table('users').where('id', user.id).update({
|
||||
password: hash,
|
||||
passwordEditedAt: now
|
||||
});
|
||||
|
||||
return res.json({ message: 'The password was changed successfully' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = changePasswordPOST;
|
|
@ -0,0 +1,11 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../structures/interfaces';
|
||||
|
||||
export const middlewares = ['auth'];
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => res.send({
|
||||
message: 'Successfully retrieved user',
|
||||
user: {
|
||||
...req.user
|
||||
}
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import type { RequestWithUser } from '../../../../structures/interfaces';
|
||||
import prisma from '../../../../structures/database';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { utc } from 'moment';
|
||||
|
||||
interface body {
|
||||
password: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export const run = async (req: RequestWithUser, res: FastifyReply) => {
|
||||
if (!req.body) return res.status(400).send({ message: 'No body provided' });
|
||||
const { password, newPassword } = req.body as body;
|
||||
if (!password || !newPassword) return res.status(400).send({ message: 'Invalid body provided' });
|
||||
if (password === newPassword) return res.status(400).send({ message: 'Passwords have to be different' });
|
||||
|
||||
const user = await prisma.users.findUnique({
|
||||
where: {
|
||||
id: req.user.id
|
||||
},
|
||||
select: {
|
||||
password: true
|
||||
}
|
||||
});
|
||||
|
||||
const comparePassword = await bcrypt.compare(password, user?.password ?? '');
|
||||
if (!comparePassword) return res.status(401).send({ message: 'Current password is incorrect' });
|
||||
|
||||
if (newPassword.length < 6 || newPassword.length > 64) {
|
||||
return res.status(400).send({ message: 'Password must have 6-64 characters' });
|
||||
}
|
||||
|
||||
let hash;
|
||||
try {
|
||||
hash = await bcrypt.hash(newPassword, 10);
|
||||
} catch (err) {
|
||||
req.log.error(err);
|
||||
return res.status(401).send({ message: 'There was a problem processing your account' });
|
||||
}
|
||||
|
||||
const now = utc().toDate();
|
||||
await prisma.users.update({
|
||||
where: {
|
||||
id: req.user.id
|
||||
},
|
||||
data: {
|
||||
password: hash,
|
||||
passwordEditedAt: now
|
||||
}
|
||||
});
|
||||
|
||||
return res.send({ message: 'TThe password was changed successfully' });
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
const Route = require('../../structures/Route');
|
||||
|
||||
class usersGET extends Route {
|
||||
constructor() {
|
||||
super('/users/me', 'get');
|
||||
}
|
||||
|
||||
run(req, res, db, user) {
|
||||
return res.json({
|
||||
message: 'Successfully retrieved user',
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
isAdmin: user.isAdmin,
|
||||
apiKey: user.apiKey
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = usersGET;
|
|
@ -1,5 +1,5 @@
|
|||
import type { FastifyReply } from 'fastify';
|
||||
import { RequestWithUser } from '../../middlewares/auth';
|
||||
import { RequestWithUser } from '../../structures/interfaces';
|
||||
|
||||
export const middlewares = ['auth'];
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
require('dotenv').config();
|
||||
|
||||
const Util = require('../utils/Util');
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await Util.writeConfigToDb(Util.getEnvironmentDefaults());
|
||||
console.log('Configuration overwriten, you can now start chibisafe');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
|
@ -0,0 +1,23 @@
|
|||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import { writeConfigToDb, getEnvironmentDefaults } from '../utils/Util';
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
const defaults = getEnvironmentDefaults();
|
||||
const keys = Object.keys(defaults);
|
||||
for (const item of keys) {
|
||||
await writeConfigToDb({
|
||||
key: item,
|
||||
value: defaults[item]
|
||||
});
|
||||
}
|
||||
console.log('Configuration overwriten, you can now start chibisafe');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
void start();
|
|
@ -1,77 +0,0 @@
|
|||
const JWT = require('jsonwebtoken');
|
||||
const db = require('./Database');
|
||||
const moment = require('moment');
|
||||
const log = require('../utils/Log');
|
||||
const Util = require('../utils/Util');
|
||||
|
||||
class Route {
|
||||
constructor(path, method, options) {
|
||||
if (!path) throw new Error('Every route needs a URL associated with it.');
|
||||
if (!method) throw new Error('Every route needs its method specified.');
|
||||
|
||||
this.path = path;
|
||||
this.method = method;
|
||||
this.options = options || {};
|
||||
}
|
||||
|
||||
async authorize(req, res) {
|
||||
const banned = await db
|
||||
.table('bans')
|
||||
.where({ ip: req.ip })
|
||||
.first();
|
||||
if (banned) return res.status(401).json({ message: 'This IP has been banned from using the service.' });
|
||||
|
||||
if (this.options.bypassAuth) return this.run(req, res, db);
|
||||
// The only reason I call it token here and not Api Key is to be backwards compatible
|
||||
// with the uploader and sharex
|
||||
// Small price to pay.
|
||||
if (req.headers.token) return this.authorizeApiKey(req, res, req.headers.token);
|
||||
if (!req.headers.authorization) return res.status(401).json({ message: 'No authorization header provided' });
|
||||
|
||||
const token = req.headers.authorization.split(' ')[1];
|
||||
if (!token) return res.status(401).json({ message: 'No authorization header provided' });
|
||||
|
||||
return JWT.verify(token, Util.config.secret, async (error, decoded) => {
|
||||
if (error) {
|
||||
log.error(error);
|
||||
return res.status(401).json({ message: 'Invalid token' });
|
||||
}
|
||||
const id = decoded ? decoded.sub : '';
|
||||
const iat = decoded ? decoded.iat : '';
|
||||
|
||||
const user = await db
|
||||
.table('users')
|
||||
.where({ id })
|
||||
.first();
|
||||
if (!user) return res.status(401).json({ message: 'Invalid authorization' });
|
||||
if (iat && iat < moment(user.passwordEditedAt).format('x')) {
|
||||
return res.status(401).json({ message: 'Token expired' });
|
||||
}
|
||||
if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' });
|
||||
if (this.options.adminOnly && !user.isAdmin) { return res.status(401).json({ message: 'Invalid authorization' }); }
|
||||
|
||||
return this.run(req, res, db, user);
|
||||
});
|
||||
}
|
||||
|
||||
async authorizeApiKey(req, res, apiKey) {
|
||||
if (!this.options.canApiKey) return res.status(401).json({ message: 'Api Key not allowed for this resource' });
|
||||
const user = await db
|
||||
.table('users')
|
||||
.where({ apiKey })
|
||||
.first();
|
||||
if (!user) return res.status(401).json({ message: 'Invalid authorization' });
|
||||
if (!user.enabled) return res.status(401).json({ message: 'This account has been disabled' });
|
||||
|
||||
return this.run(req, res, db, user);
|
||||
}
|
||||
|
||||
run() {}
|
||||
|
||||
error(res, error) {
|
||||
log.error(error);
|
||||
return res.status(500).json({ message: 'There was a problem parsing the request' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Route;
|
|
@ -1,123 +0,0 @@
|
|||
require('dotenv').config();
|
||||
|
||||
if (!process.env.SERVER_PORT) {
|
||||
console.log('Run the setup script first or fill the .env file manually before starting');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!process.env.DOMAIN) {
|
||||
console.log('You failed to provide a domain for your instance. Edit the .env file manually and fix it.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const { loadNuxt, build } = require('nuxt');
|
||||
const express = require('express');
|
||||
const helmet = require('helmet');
|
||||
const cors = require('cors');
|
||||
const RateLimit = require('express-rate-limit');
|
||||
const bodyParser = require('body-parser');
|
||||
const jetpack = require('fs-jetpack');
|
||||
const path = require('path');
|
||||
const morgan = require('morgan');
|
||||
const rfs = require('rotating-file-stream');
|
||||
const CronJob = require('cron').CronJob;
|
||||
const log = require('../utils/Log');
|
||||
|
||||
const Util = require('../utils/Util');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const rateLimiter = new RateLimit({
|
||||
windowMs: parseInt(Util.config.rateLimitWindow, 10),
|
||||
max: parseInt(Util.config.rateLimitMax, 10),
|
||||
delayMs: 0
|
||||
});
|
||||
|
||||
class Server {
|
||||
constructor() {
|
||||
this.port = parseInt(process.env.SERVER_PORT, 10);
|
||||
this.server = express();
|
||||
this.server.set('trust proxy', 1);
|
||||
this.server.use(helmet());
|
||||
this.server.use(cors({ allowedHeaders: ['Accept', 'Authorization', 'Cache-Control', 'X-Requested-With', 'Content-Type', 'albumId', 'finishedChunks'] }));
|
||||
this.server.use((req, res, next) => {
|
||||
// This bypasses the headers.accept for album download, since it's accesed directly through the browser.
|
||||
if ((req.url.includes('/api/album/') || req.url.includes('/zip')) && req.method === 'GET') return next();
|
||||
// This bypasses the headers.accept if we are accessing the frontend
|
||||
if (!req.url.includes('/api/') && req.method === 'GET') return next();
|
||||
if (req.headers.accept && req.headers.accept.includes('application/vnd.chibisafe.json')) return next();
|
||||
return res.status(405).json({ message: 'Incorrect `Accept` header provided' });
|
||||
});
|
||||
this.server.use(bodyParser.urlencoded({ extended: true }));
|
||||
this.server.use(bodyParser.json());
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const accessLogStream = rfs.createStream('access.log', {
|
||||
interval: '1d', // rotate daily
|
||||
path: path.join(__dirname, '../../../logs', 'log')
|
||||
});
|
||||
this.server.use(morgan('combined', { stream: accessLogStream }));
|
||||
}
|
||||
|
||||
// Apply rate limiting to the api only
|
||||
this.server.use('/api/', rateLimiter);
|
||||
|
||||
// Serve the uploads
|
||||
this.server.use(express.static(path.join(__dirname, '../../../uploads')));
|
||||
this.routesFolder = path.join(__dirname, '../routes');
|
||||
|
||||
// Save the cron job instances in case we want to stop them later
|
||||
this.jobs = {};
|
||||
}
|
||||
|
||||
registerAllTheRoutes() {
|
||||
jetpack.find(this.routesFolder, { matching: '*.js' }).forEach(routeFile => {
|
||||
const RouteClass = require(path.join('../../../', routeFile));
|
||||
let routes = [RouteClass];
|
||||
if (Array.isArray(RouteClass)) routes = RouteClass;
|
||||
for (const File of routes) {
|
||||
try {
|
||||
const route = new File();
|
||||
this.server[route.method](`/api${route.path}`, route.authorize.bind(route));
|
||||
log.info(`Found route ${route.method.toUpperCase()} /api${route.path}`);
|
||||
} catch (e) {
|
||||
log.error(`Failed loading route from file ${routeFile} with error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async serveNuxt() {
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const nuxt = await loadNuxt(isProd ? 'start' : 'dev');
|
||||
this.server.use(nuxt.render);
|
||||
if (!isProd) {
|
||||
build(nuxt);
|
||||
}
|
||||
}
|
||||
|
||||
createJobs() {
|
||||
// TODO: move into the database config. (we can just show the crontab line for start, later on we can add dropdowns and stuff)
|
||||
this.jobs.stats = new CronJob('0 0 * * * *', Util.saveStatsToDb, null, true);
|
||||
}
|
||||
|
||||
start() {
|
||||
jetpack.dir('uploads/chunks');
|
||||
jetpack.dir('uploads/thumbs/square');
|
||||
jetpack.dir('uploads/thumbs/preview');
|
||||
this.registerAllTheRoutes();
|
||||
this.serveNuxt();
|
||||
const server = this.server.listen(this.port, () => {
|
||||
log.success(`Backend ready and listening on port ${this.port}`);
|
||||
});
|
||||
server.setTimeout(600000);
|
||||
|
||||
this.createJobs();
|
||||
}
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
const conf = await Util.config;
|
||||
new Server().start();
|
||||
};
|
||||
|
||||
start();
|
||||
|
|
@ -1,3 +1,14 @@
|
|||
import type { FastifyRequest } from 'fastify';
|
||||
|
||||
export interface RequestWithUser extends FastifyRequest {
|
||||
user: {
|
||||
id: number;
|
||||
username: string;
|
||||
isAdmin: boolean;
|
||||
apiKey?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
|
|
|
@ -2,7 +2,7 @@ import jetpack from 'fs-jetpack';
|
|||
import randomstring from 'randomstring';
|
||||
import path from 'path';
|
||||
import prisma from '../structures/database';
|
||||
import moment from 'moment';
|
||||
import { utc } from 'moment';
|
||||
import Zip from 'adm-zip';
|
||||
import { generateThumbnails, getFileThumbnail, removeThumbs } from './ThumbUtil';
|
||||
import { getStats } from './StatsGenerator';
|
||||
|
@ -273,7 +273,7 @@ export const storeFileToDb = async (req: FastifyRequest, res: FastifyReply, user
|
|||
return;
|
||||
}
|
||||
|
||||
const now = moment.utc().toDate();
|
||||
const now = utc().toDate();
|
||||
const data = {
|
||||
userId: user.id ? user.id : undefined,
|
||||
name: file.data.filename,
|
||||
|
@ -300,7 +300,7 @@ export const storeFileToDb = async (req: FastifyRequest, res: FastifyReply, user
|
|||
export const saveFileToAlbum = async (albumId: number, insertedId: number) => {
|
||||
if (!albumId) return;
|
||||
|
||||
const now = moment.utc().toDate();
|
||||
const now = utc().toDate();
|
||||
try {
|
||||
await prisma.albumsFiles.create({
|
||||
data: {
|
||||
|
@ -367,7 +367,7 @@ export const saveStatsToDb = async (force: boolean) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const now = moment.utc().toDate();
|
||||
const now = utc().toDate();
|
||||
const stats = await getStats(db);
|
||||
|
||||
let batchId = 1;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable no-console */
|
||||
const jetpack = require('fs-jetpack');
|
||||
const qoa = require('qoa');
|
||||
import jetpack from 'fs-jetpack';
|
||||
// @ts-ignore no typings for qoa
|
||||
import qoa from 'qoa';
|
||||
|
||||
qoa.config({
|
||||
prefix: '>',
|
||||
|
@ -86,7 +87,8 @@ async function start() {
|
|||
console.log('== MAKE SURE TO CHANGE IT AFTER YOUR FIRST LOGIN ==');
|
||||
console.log('=====================================================');
|
||||
console.log();
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
setTimeout(() => {}, 1000);
|
||||
}
|
||||
|
||||
start();
|
||||
void start();
|
||||
|
|
Loading…
Reference in New Issue