work on the

This commit is contained in:
May 2023-01-27 15:01:28 -08:00
parent 74e18d3d9c
commit c911896f6d
18 changed files with 1153 additions and 84 deletions

4
.vscode/tasks.json vendored
View file

@ -3,7 +3,7 @@
"tasks": [
{
"type": "shell",
"command":"npx tsc",
"command":"tsc\nsass src/style:out/style\nrollup -c",
"group": {
"kind": "build",
"isDefault": true
@ -12,7 +12,7 @@
},
{
"type": "shell",
"command":"npx tsc\nnode ./out/index.js\ndel ./out/* -Recurse",
"command":"tsc\nsass src/style:out/style\nrollup -c\nnpx tsc\nnode ./out/index.js\ndel ./out/* -Recurse",
"group": {
"kind": "build",
"isDefault": true

View file

@ -22,7 +22,8 @@ TOKEN=KILL-YOURSELF.NOW
- [X] 1.2.2 clean up this shitty code
- [X] 1.2.3 bugfixes
- [ ] 1.3.0 new ui; accounts
- [ ] 1.3.1 disable cloning of local ips
- [ ] 1.3.1 add utility endpoints: `/embed/:fileId` for discord, `/:fileId` for quick access
- [ ] 1.3.3 disable cloning of local ips
- [ ] 1.4.0 admin panel
- [ ] 2.0.0 rewrite using theUnfunny's code as a base/rewrite using monofile-core

747
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,5 +24,12 @@
"express": "^4.18.1",
"multer": "^1.4.5-lts.1",
"typescript": "^4.8.3"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup": "^3.11.0",
"rollup-plugin-svelte": "^7.1.0",
"sass": "^1.57.1",
"svelte": "^3.55.1"
}
}

View file

@ -1,72 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="og:site_name" content="monofile $Version">
<meta name="application-name" content="$ErrorCode">
<meta name="description" content="$ErrorMessage">
<title>monofile</title>
<style>
<head>
* {
font-family: sans-serif;
}
h1,h2 {
text-align:center;
}
#content {
position:fixed;
left:50%;
top:50%;
transform:translate(-50%,-50%);
background-color:white;
width:450px;
/*height:100%;*/
}
body {
background-color: darkgray;
}
.note {
padding:5px;
position:relative;
width:calc( 100% - 60px );
left:25px;
border:1px solid #AAAAAAFF;
border-radius: 8px;
background-color: #AAAAAA66;
}
<link
rel="stylesheet"
href="/static/style/error.css"
>
@media only screen and (max-width: 450px) {
#content {
position:fixed;
left:0%;
top:0%;
transform:translate(0%,0%);
background-color:white;
width:100%;
height:100%;
}
}
<link
rel="icon"
type="image/png"
href="/static/assets/monofile-circ.png"
>
</style>
</head>
<body>
<div id="content">
<h1>
monofile<span style="font-style:italic;font-weight:bold;font-size:16px;color:#999999"> $Version</span>
</h1>
<h2><em>Discord-based file sharing</em></h2>
<div class="note" style="border-color:#FFAAAAFF;background-color:#FFAAAA66">
<h1>$ErrorCode</h1>
<p style="width:calc( 100% - 50px );left:25px;position:relative;">$ErrorMessage</p>
</div>
<div style="width:100%;height:25px"></div>
</div>
</body>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=0"
>
<title>$code</title>
</head>
<body>
<p class="error">
<span class="code">$code</span>
&nbsp;$text
</p>
</body>
</html>

15
rollup.config.mjs Normal file
View file

@ -0,0 +1,15 @@
import svelte from 'rollup-plugin-svelte'
import resolve from "@rollup/plugin-node-resolve"
export default {
input: "src/script/client/main.js",
output: {
file: 'out/script/client/bundle.js',
format: 'esm',
sourcemap:true
},
plugins: [
resolve(),
svelte({})
]
}

0
src/client/main.js Normal file
View file

View file

@ -8,6 +8,7 @@ import Discord, { IntentsBitField, Client } from "discord.js"
import express from "express"
import fs from "fs"
import axios, { AxiosResponse } from "axios"
import ServeError from "./lib/errors"
import Files from "./lib/files"
require("dotenv").config()
@ -21,14 +22,6 @@ app.use("/static",express.static("assets"))
app.use(bodyParser.text({limit:(config.maxDiscordFileSize*config.maxDiscordFiles)+1048576,type:["application/json","text/plain"]}))
// funcs
function ThrowError(response:express.Response,code:number,errorMessage:string) {
fs.readFile(__dirname+"/../pages/error.html",(err,buf) => {
if (err) {response.sendStatus(500);console.log(err);return}
response.status(code)
response.send(buf.toString().replace(/\$ErrorCode/g,code.toString()).replace(/\$ErrorMessage/g,errorMessage).replace(/\$Version/g,pkg.version))
})
}
// init data
if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/")
@ -87,7 +80,10 @@ app.post("/upload",multerSetup.single('file'),async (req,res) => {
try {
files.uploadFile({name:req.file.originalname,mime:req.file.mimetype,uploadId:req.header("monofile-upload-id")},req.file.buffer)
.then((uID) => res.send(uID))
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
.catch((stat) => {
res.status(stat.status);
res.send(`[err] ${stat.message}`)
})
} catch {
res.status(400)
res.send("[err] bad request")
@ -108,7 +104,10 @@ app.post("/clone",(req,res) => {
axios.get(j.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => {
files.uploadFile({name:j.url.split("/")[req.body.split("/").length-1] || "generic",mime:data.headers["content-type"],uploadId:j.uploadId},Buffer.from(data.data))
.then((uID) => res.send(uID))
.catch((stat) => {res.status(stat.status);res.send(`[err] ${stat.message}`)})
.catch((stat) => {
res.status(stat.status);
res.send(`[err] ${stat.message}`)
})
}).catch((err) => {
console.log(err)
res.status(400)
@ -141,7 +140,7 @@ app.get("/download/:fileId",(req,res) => {
)
})
} else {
ThrowError(res,404,"File not found.")
ServeError(res,404,"File not found.")
}
})
@ -151,12 +150,12 @@ app.get("/file/:fileId",async (req,res) => {
res.status(200)
f.dataStream.pipe(res)
}).catch((err) => {
ThrowError(res,err.status,err.message)
ServeError(res,err.status,err.message)
})
})
app.get("*",(req,res) => {
ThrowError(res,404,"Page not found.")
ServeError(res,404,"Page not found.")
})
app.get("/server",(req,res) => {

131
src/server/lib/accounts.ts Normal file
View file

@ -0,0 +1,131 @@
import crypto from "crypto"
import * as auth from "./auth";
import { readFile, writeFile } from "fs/promises"
// this is probably horrible
// but i don't even care anymore
export let Accounts: Account[] = []
export interface Account {
id : string
username: string
password: {
hash: string
salt: string
}
accounts: string[]
admin : boolean
}
export function create(username:string,pwd:string,admin:boolean=false) {
let accId = crypto.randomBytes(12).toString("hex")
Accounts.push(
{
id: accId,
username: username,
password: password.hash(pwd),
accounts: [],
admin: admin
}
)
save()
return accId
}
export function getFromUsername(username:string) {
return Accounts.find(e => e.username == username)
}
export function getFromId(id:string) {
return Accounts.find(e => e.id == id)
}
export function getFromToken(token:string) {
let accId = auth.validate(token)
if (!accId) return
return getFromId(accId)
}
export function deleteAccount(id:string) {
Accounts.splice(Accounts.findIndex(e => e.id == id),1)
save()
}
export namespace password {
export function hash(password:string,_salt?:string) {
let salt = _salt || crypto.randomBytes(12).toString('base64')
let hash = crypto.createHash('sha256').update(`${salt}${password}`).digest('hex')
return {
salt:salt,
hash:hash
}
}
export function set(id:string,password:string) {
let acc = Accounts.find(e => e.id == id)
if (!acc) return
acc.password = hash(password)
save()
}
export function check(id:string,password:string) {
let acc = Accounts.find(e => e.id == id)
if (!acc) return
return acc.password.hash == hash(password,acc.password.salt).hash
}
}
export namespace rbxaccounts {
export function add(id:string,name:string) {
let acc = getFromId(id)
if (!acc) return
/* check for account that already has name */
let idx = acc.accounts.findIndex(e=>e==name)
if (idx > -1) return
acc.accounts = [...acc.accounts,name]
save()
return
}
export function remove(id:string,name:string) {
let acc = getFromId(id)
if (!acc) return
let idx = acc.accounts.findIndex(e=>e==name)
if (idx < 0) return
acc.accounts.splice(idx,1)
save()
return
}
export function clear(id:string) {
let acc = getFromId(id)
if (!acc) return
acc.accounts = []
save()
return
}
}
export function save() {
writeFile(`${process.cwd()}/.data/accounts.json`,JSON.stringify(Accounts))
.catch((err) => console.error(err))
}
readFile(`${process.cwd()}/.data/accounts.json`)
.then((buf) => {
Accounts = JSON.parse(buf.toString())
}).catch(err => console.error(err))
.finally(() => {
if (!Accounts.find(e => e.admin)) {
create("admin","admin",true)
}
})

58
src/server/lib/auth.ts Normal file
View file

@ -0,0 +1,58 @@
import crypto from "crypto"
import { readFile, writeFile } from "fs/promises"
export let AuthTokens: AuthToken[] = []
export let AuthTokenTO:{[key:string]:NodeJS.Timeout} = {}
export interface AuthToken {
account: string,
token: string,
expire: number
}
export function create(id:string,expire:number=(24*60*60*1000)) {
let token = {
account:id,
token:crypto.randomBytes(12).toString('hex'),
expire:Date.now()+expire
}
AuthTokens.push(token)
tokenTimer(token)
save()
return token.token
}
export function validate(token:string) {
return AuthTokens.find(e => e.token == token && Date.now() < e.expire)?.account
}
export function tokenTimer(token:AuthToken) {
if (Date.now() >= token.expire) {
invalidate(token.token)
return
}
AuthTokenTO[token.token] = setTimeout(() => invalidate(token.token),token.expire-Date.now())
}
export function invalidate(token:string) {
if (AuthTokenTO[token]) {
clearTimeout(AuthTokenTO[token])
}
AuthTokens.splice(AuthTokens.findIndex(e => e.token == token),1)
save()
}
export function save() {
writeFile(`${process.cwd()}/.data/tokens.json`,JSON.stringify(AuthTokens))
.catch((err) => console.error(err))
}
readFile(`${process.cwd()}/.data/tokens.json`)
.then((buf) => {
AuthTokens = JSON.parse(buf.toString())
AuthTokens.forEach(e => tokenTimer(e))
}).catch(err => console.error(err))

35
src/server/lib/errors.ts Normal file
View file

@ -0,0 +1,35 @@
import { Response } from "express";
import { readFile } from "fs/promises"
let errorPage:string
export default async function ServeError(
res:Response,
code:number,
reason:string
) {
// fetch error page if not cached
if (!errorPage) {
errorPage =
(
await readFile(`${process.cwd()}/src/pages/error.html`)
.catch(() => {res.header("Content-Type","text/plain")})
|| "<pre>$code $text</pre>"
)
.toString()
}
// serve error
res.status(code)
res.send(
errorPage
.replace(/\$code/g,code.toString())
.replace(/\$text/g,reason)
)
}
export function Redirect(res:Response,url:string) {
res.status(302)
res.header("Location",url)
res.send()
}

50
src/style/_base.scss Normal file
View file

@ -0,0 +1,50 @@
/*
could probably replace this with fonts served directly
from the server but it's fine for now
*/
@import url('https://fonts.googleapis.com/css2?family=Fira+Code&family=Inconsolata&family=Source+Sans+Pro:wght@300;400;600;700;900&display=swap');
$FallbackFonts:
-apple-system,
system-ui,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
sans-serif;
%normal {
font-family: "Source Sans Pro", $FallbackFonts
}
/*
everything that's not a span
and/or has the normal class
(it's just in case)
*/
*:not(span), .normal { @extend %normal; }
/*
for code blocks / terminal
*/
.monospace {
font-family: "Fira Code", monospace
}
/*
colors
*/
$Background: #252525;
/* hsl(210,12.9,24.3) */
$darkish: rgb(54, 62, 70);
/*
then other stuff
*/
body {
background-color: $Background
}

34
src/style/app/topbar.scss Normal file
View file

@ -0,0 +1,34 @@
@use "../base";
#topbar {
position:absolute;
left:0px;
top:0px;
width:100%;
height:40px;
/* hsl(210,9.1,12.9) */
background-color: rgb(30, 33, 36);
display:flex;
flex-direction: row;
justify-content: center;
align-items: center;
column-gap:5px;
.monofile_ico {
width:26px;
height:26px;
border:2px solid base.$darkish;
border-radius:100%;
transition-duration: 100ms;
&:hover {
border:2px solid #999999;
transition-duration: 100ms;
}
}
}

20
src/style/error.scss Normal file
View file

@ -0,0 +1,20 @@
@use "_base";
.error {
font-size:20px;
color: lightslategray;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
text-align:center;
.code {
font-size:25px;
font-family: "Inconsolata", monospace;
color: white;
}
}

0
src/svelte/App.svelte Normal file
View file

View file

@ -0,0 +1,16 @@
<script>
</script>
<div id="topbar">
<button on:click={() => {}} class="menuBtn">my account</button>
<button on:click={() => {}} class="menuBtn">files</button>
<!-- divider -->
<div style="height:calc( 100% - 10px ); top:5px; width:2px; background-color:#333333; margin:0 5px 0 5px;" />
<button on:click={() => {}} class="menuBtn">upload</button>
<button on:click={() => {}} class="menuBtn">clone</button>
<div />
</div>

View file

@ -1,5 +1,5 @@
{
"include":["src/**/*"],
"include":["src/server/**/*"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */