diff --git a/package.json b/package.json index a917c8d..dbbad98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "void", - "version": "0.2.2", + "version": "0.2.3", "private": true, "engines": { "node": ">=14" diff --git a/prisma/migrations/20211002062936_url_password/migration.sql b/prisma/migrations/20211002062936_url_password/migration.sql new file mode 100644 index 0000000..3ab99b4 --- /dev/null +++ b/prisma/migrations/20211002062936_url_password/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Url" ADD COLUMN "password" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1bac1d2..6307f9c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -27,6 +27,7 @@ model Url { destination String short String createdAt DateTime @default(now()) + password String? views Int @default(0) user User @relation(fields: [userId], references: [id]) userId Int diff --git a/server/index.js b/server/index.js index 1d9ba2d..a38f32f 100644 --- a/server/index.js +++ b/server/index.js @@ -19,7 +19,7 @@ const dev = process.env.NODE_ENV === 'development'; try { const config = await validateConfig(configReader()); const data = await prismaRun(config.core.database_url, ['migrate', 'status'], true); - if (data.includes('Following migration have not yet been applied')) { + if (data.match(/Following migration[s]? have not yet been applied/)) { info('DB', 'Some migrations are not applied, applying them now...'); await deployDb(config); info('DB', 'Finished applying migrations'); diff --git a/src/components/pages/Upload.tsx b/src/components/pages/Upload.tsx index 83f79f8..8343045 100644 --- a/src/components/pages/Upload.tsx +++ b/src/components/pages/Upload.tsx @@ -1,4 +1,4 @@ -import { Button, Center, Checkbox, Heading, HStack, Select, Text, useColorModeValue, useToast, VStack } from '@chakra-ui/react'; +import { Button, Center, Checkbox, Heading, HStack, Select, Input, Text, useColorModeValue, useToast, VStack } from '@chakra-ui/react'; import copy from 'copy-to-clipboard'; import { useStoreSelector } from 'lib/redux/store'; import React, { useState } from 'react'; @@ -51,25 +51,22 @@ export default function Upload() { setBusy(false); } }; - const fg = useColorModeValue('gray.800', 'white'); - const bg = useColorModeValue('gray.100', 'gray.700'); - const shadow = useColorModeValue('outline', 'dark-lg'); return (
+ shadow={useColorModeValue('outline', 'dark-lg')}> Upload a file + +
+ ); +} + +function Preview({ file, embed, username, content = undefined, misc }) { const handleDownload = () => { const a = document.createElement('a'); a.download = file.origFileName; @@ -79,6 +121,7 @@ export const getServerSideProps: GetServerSideProps = async context => { }, select: { id: true, + password: true, destination: true } }); @@ -93,9 +136,20 @@ export const getServerSideProps: GetServerSideProps = async context => { } } }); + const { destination, password } = url; + if (url.password) { + return { + props: { + type: 'url', + data: { + id: url.id + } + } + }; + } return { redirect: { - destination: url.destination, + destination }, props: undefined, }; @@ -173,15 +227,18 @@ export const getServerSideProps: GetServerSideProps = async context => { delete file.uploadedAt; return { props: { - file, - embed, - username, - misc: { - ext, - type, - language: isCode ? ext : 'text' - }, - content + type: 'file', + data: { + file, + embed, + username, + misc: { + ext, + type, + language: isCode ? ext : 'text' + }, + content + } } }; }; @@ -190,13 +247,16 @@ export const getServerSideProps: GetServerSideProps = async context => { delete file.uploadedAt; return { props: { - file, - embed, - username, - misc: { - ext, - type, - src + type: 'file', + data: { + file, + embed, + username, + misc: { + ext, + type, + src + } } } }; diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts index caa6578..c2d279c 100644 --- a/src/pages/api/auth/login.ts +++ b/src/pages/api/auth/login.ts @@ -1,6 +1,6 @@ import { info } from 'lib/logger'; import prisma from 'lib/prisma'; -import { checkPassword, createToken, hashPassword } from 'lib/utils'; +import { verifyPassword, createToken, hashPassword } from 'lib/utils'; import { NextApiReq, NextApiRes, withVoid } from 'middleware/withVoid'; async function handler(req: NextApiReq, res: NextApiRes) { @@ -24,7 +24,7 @@ async function handler(req: NextApiReq, res: NextApiRes) { } }); if (!user) return res.status(404).end(JSON.stringify({ error: 'User not found' })); - const valid = await checkPassword(password, user.password); + const valid = await verifyPassword(password, user.password); if (!valid) return res.forbid('Wrong password'); res.setCookie('user', user.id, { sameSite: true, maxAge: 604800, path: '/' }); info('AUTH', `User ${user.username} (${user.id}) logged in`); diff --git a/src/pages/api/shorten.ts b/src/pages/api/shorten.ts index c465967..52c5790 100644 --- a/src/pages/api/shorten.ts +++ b/src/pages/api/shorten.ts @@ -3,6 +3,7 @@ import generate from 'lib/generators'; import { info } from 'lib/logger'; import { NextApiReq, NextApiRes, withVoid } from 'lib/middleware/withVoid'; import prisma from 'lib/prisma'; +import { hashPassword } from 'lib/utils'; async function handler(req: NextApiReq, res: NextApiRes) { if (req.method !== 'POST') return res.forbid('Invalid method'); @@ -25,12 +26,14 @@ async function handler(req: NextApiReq, res: NextApiRes) { if (existing) return res.error('Vanity is already taken'); } const rand = generate(cfg.shortener.length); + if (req.body.password) var password = await hashPassword(req.body.password); const url = await prisma.url.create({ data: { short: req.body.vanity ? req.body.vanity : rand, destination: req.body.destination, userId: user.id, - }, + password + } }); info('URL', `User ${user.username} (${user.id}) shortened a URL: ${url.destination} (${url.id})`); return res.json({ diff --git a/src/pages/api/user/index.ts b/src/pages/api/user/index.ts index f00c3b3..694ad25 100644 --- a/src/pages/api/user/index.ts +++ b/src/pages/api/user/index.ts @@ -50,7 +50,7 @@ async function handler(req: NextApiReq, res: NextApiRes) { }); const newUser = await prisma.user.findFirst({ where: { - id: Number(user.id) + id: +user.id }, select: { isAdmin: true, diff --git a/src/pages/api/validate.ts b/src/pages/api/validate.ts new file mode 100644 index 0000000..6593ad0 --- /dev/null +++ b/src/pages/api/validate.ts @@ -0,0 +1,29 @@ +import { default as cfg, default as config } from 'lib/config'; +import generate from 'lib/generators'; +import { info } from 'lib/logger'; +import { NextApiReq, NextApiRes, withVoid } from 'lib/middleware/withVoid'; +import prisma from 'lib/prisma'; +import { verifyPassword } from 'lib/utils'; + +async function handler(req: NextApiReq, res: NextApiRes) { + if (req.method !== 'POST') return res.forbid('Invalid method'); + if (!req.body) return res.forbid('No body'); + if (!(req.body.password || !req.body.id)) return res.forbid('No password or ID'); + const url = await prisma.url.findFirst({ + where: { + id: +req.body.id + }, + select: { + password: true, + destination: true + } + }); + const valid = await verifyPassword(req.body.password, url.password); + if (!valid) return res.error('Wrong password'); + return res.json({ + success: true, + destination: url.destination + }); +} + +export default withVoid(handler); \ No newline at end of file