Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheodoreKrypton committed Jan 5, 2024
1 parent 49b45e1 commit b415a0c
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 19 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"input": "^1.0.1",
"ip": "^1.1.8",
"js-yaml": "^4.1.0",
"json-web-token": "^3.2.0",
"telegraf": "^4.15.0",
"telegram": "^2.17.10",
"uuid": "^9.0.0",
Expand Down
61 changes: 60 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,48 @@ import yaml from 'js-yaml';
import os from 'os';
import path from 'path';

export const config: any = {};
type Config = {
telegram: {
api_id: number;
api_hash: string;
bot_token: string;
private_file_channel: string;
public_file_channel: string;
session_file: string;
};
tgfs: {
users: {
[key: string]: {
password: string;
};
};
download: {
progress: boolean;
chunksize: number;
};
jwt: {
secret: string;
algorithm: string;
expiration: number;
};
};
webdav: {
host: string;
port: number;
path: string;
};
manager: {
host: string;
port: number;
path: string;
bot: {
token: string;
chat_id: number;
};
};
};

export const config = {} as Config;

export const loadConfig = (configPath: string) => {
const file = fs.readFileSync(configPath, 'utf8');
Expand Down Expand Up @@ -34,6 +75,11 @@ export const loadConfig = (configPath: string) => {
chunksize: cfg['tgfs']['download']['chunk_size_kb'] ?? 1024,
progress: cfg['tgfs']['download']['progress'] === 'true',
},
jwt: {
secret: cfg['tgfs']['jwt']['secret'],
algorithm: cfg['tgfs']['jwt']['algorithm'] ?? 'HS256',
expiration: cfg['tgfs']['jwt']['expiration'],
},
};

config.webdav = {
Expand Down Expand Up @@ -110,6 +156,19 @@ export const createConfig = async () => {
progress: 'true',
chunk_size_kb: 1024,
},
jwt: {
secret: (() => {
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let secret = '';
for (let i = 0; i < 64; i++) {
secret += chars.charAt(Math.floor(Math.random() * chars.length));
}
return secret;
})(),
algorithm: 'HS256',
expiration: 3600 * 24 * 7,
},
};

config.webdav = {
Expand Down
42 changes: 42 additions & 0 deletions src/errors/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import HTTPErrors from 'http-errors';

import { BusinessError } from './base';

export class BadAuthentication extends BusinessError {
constructor(
public readonly message: string,
public readonly cause?: string,
) {
super(message, 'BAD_AUTHENTICATION', cause, HTTPErrors.Unauthorized);
}
}

export class MissingAuthenticationHeaders extends BadAuthentication {
constructor() {
super('Missing authentication headers', 'Missing authentication headers');
}
}

export class InvalidCredentials extends BadAuthentication {
constructor(public readonly cause: string) {
super('Bad authentication', cause);
}
}

export class UserNotFound extends InvalidCredentials {
constructor(public readonly username: string) {
super(`User ${username} not found`);
}
}

export class IncorrectPassword extends InvalidCredentials {
constructor(public readonly username: string) {
super(`Password for ${username} does not match`);
}
}

export class JWTInvalid extends InvalidCredentials {
constructor() {
super('JWT token invalid');
}
}
10 changes: 9 additions & 1 deletion src/errors/base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import HTTPErrors from 'http-errors';

import { ErrorCodes } from './error-codes';

export class TechnicalError extends Error {
constructor(
public readonly message: string,
public readonly cause?: any,
public readonly httpError: {
new (message: string): HTTPErrors.HttpError;
} = HTTPErrors.InternalServerError,
) {
super(message);
}
Expand All @@ -14,7 +19,10 @@ export class BusinessError extends TechnicalError {
public readonly message: string,
public readonly code: ErrorCodes,
public readonly cause?: any,
public readonly httpError: {
new (message: string): HTTPErrors.HttpError;
} = HTTPErrors.InternalServerError,
) {
super(message, cause);
super(message, cause, httpError);
}
}
3 changes: 2 additions & 1 deletion src/errors/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export type ErrorCodes =
| 'INVALID_NAME'
| 'RELATIVE_PATH'
| 'UNKNOWN_COMMAND'
| 'DIR_IS_NOT_EMPTY';
| 'DIR_IS_NOT_EMPTY'
| 'BAD_AUTHENTICATION';
1 change: 1 addition & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './cmd';
export * from './path';
export * from './authentication';
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const { argv }: any = yargs(hideBin(process.argv))
try {
loadConfig(configPath);
} catch (err) {
console.log(err);
configPath = await createConfig();
loadConfig(configPath);
}
Expand Down
48 changes: 48 additions & 0 deletions src/server/manager/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import jwt from 'json-web-token';

import { config } from 'src/config';
import { IncorrectPassword, JWTInvalid, UserNotFound } from 'src/errors';

const sha256 = async (s: string) => {
const msgBuffer = new TextEncoder().encode(s);
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return hashHex;
};

type JWTPayload = {
username: string;
exp: number;
};

export const generateToken = async (username: string, password: string) => {
if (config.tgfs.users[username] === undefined) {
throw new UserNotFound(username);
}
if ((await sha256(config.tgfs.users[username].password)) !== password) {
// the password sent in is sha256 hashed
throw new IncorrectPassword(username);
}
return jwt.encode;
};

export const verifyToken = (token: string): Promise<JWTPayload> => {
return new Promise((resolve, reject) => {
const payload = jwt.decode(
config.tgfs.jwt.secret,
token,
(error, payload) => {
if (error) {
reject(new JWTInvalid());
}
},
);
if (payload.exp < Date.now()) {
reject(new JWTInvalid());
}
resolve(payload as JWTPayload);
});
};
40 changes: 38 additions & 2 deletions src/server/manager/http-server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,54 @@
import express from 'express';
import express, { Request, Response } from 'express';

import { BadAuthentication } from 'src/errors';
import { TechnicalError } from 'src/errors/base';

import { generateToken, verifyToken } from './auth';
import { db } from './db';

const app = express();

app.use(async (req, res, next) => {
if (req.path === '/login') {
next();
}

const token = req.headers['authorization'];
if (token === undefined) {
res.redirect('/');
}

try {
await verifyToken(token);
} catch (err) {
res.redirect('/');
}
});

// set cors headers
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});

app.post('/login', (req, res) => {
const token = generateToken(req.body.username, req.body.password);
res.setHeader('Set-Cookie', `token=${token}; HttpOnly`);
res.end();
});

app.get('/tasks', (req, res) => {
console.log(db.getTasks());
console.log(req.headers);
res.send(db.getTasks());
});

app.use((err: Error, req: Request, res: Response, next: () => any) => {
if (err instanceof TechnicalError) {
const error = new err.httpError(err.message);
res.status(error.statusCode).send(error.message);
} else {
res.status(500).send('Internal Server Error');
}
});

export const managerServer = app;
2 changes: 1 addition & 1 deletion src/server/webdav/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const webdavServer = (
options?: webdav.WebDAVServerOptions,
) => {
const userManager = new webdav.SimpleUserManager();
Object.keys(config.tgfs.users).forEach((user: string) => {
Object.keys(config.tgfs.users).forEach((user) => {
userManager.addUser(user, config.tgfs.users[user].password, true);
});

Expand Down
11 changes: 0 additions & 11 deletions src/sync/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/sync/sync-from-local-only.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/sync/sync-from-remote-only.ts

This file was deleted.

30 changes: 30 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,11 @@ base64-js@^1.3.1:
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==

base64-url@^2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-2.3.3.tgz#645b71455c75109511f27d98450327e455f488ec"
integrity sha512-dLMhIsK7OplcDauDH/tZLvK7JmUZK3A7KiQpjNzsBrM6Etw7hzNI1tLEywqJk9NnwkgWuFKSlx/IUO7vF6Mo8Q==

big-integer@^1.6.48:
version "1.6.51"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz"
Expand Down Expand Up @@ -2165,6 +2170,11 @@ is-typedarray@^1.0.0:
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==

is.object@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is.object/-/is.object-1.0.0.tgz#e4f4117e9f083b35c8df5cf817ea3efb0452fdfa"
integrity sha512-BdDP6tLXkf0nrCnksLobALJxkt2hmrVL6ge1oRuzGU4Lb9NpreEbhhuCcY6HMzx/qo3Dff9DJ3jf0x9+U0bNMQ==

isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
Expand Down Expand Up @@ -2606,6 +2616,21 @@ json-parse-even-better-errors@^2.3.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==

json-parse-safe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/json-parse-safe/-/json-parse-safe-2.0.0.tgz#27b4fddd31114936136b77d9bbe13f7ef5c897de"
integrity sha512-aK7Ccg46n9JkRil/M5s8ytNP9wk0Tc0TsTDVMOYxB2cGsL7XFJ1vfWsurkVZy6zR55KZ+2SWHEuzQeZK4PH6lg==

json-web-token@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/json-web-token/-/json-web-token-3.2.0.tgz#06cbc8ab039f474f0f3fabc2e073e7fb6903b8d8"
integrity sha512-y+zDkON17Zk7X3Hf0Rv04k8N7cP1GDRonLLL7WkdnlHl1ur2hwta4MYzxG8nWhQWRBNlIp1erszhmo/65RSTIw==
dependencies:
base64-url "^2.3.2"
is.object "^1.0.0"
json-parse-safe "^2.0.0"
xtend "^4.0.2"

json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
Expand Down Expand Up @@ -3749,6 +3774,11 @@ xml-js@^1.6.2:
dependencies:
sax "^1.2.4"

xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==

y18n@^5.0.5:
version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
Expand Down

0 comments on commit b415a0c

Please sign in to comment.