You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ass/backend/routers/api.ts

143 lines
4.6 KiB

import { AssUser, AssUserNewReq } from 'ass';
import * as bcrypt from 'bcrypt'
import { Router, json as BodyParserJson, RequestHandler } from 'express';
import * as data from '../data.js';
import { log } from '../log.js';
import { nanoid } from '../generators.js';
import { UserConfig } from '../UserConfig.js';
import { rateLimiterMiddleware, setRateLimiter } from '../ratelimit.js';
import { DBManager } from '../sql/database.js';
import { JSONDatabase } from '../sql/json.js';
import { MySQLDatabase } from '../sql/mysql.js';
import { PostgreSQLDatabase } from '../sql/postgres.js';
import { MongoDBDatabase } from '../sql/mongodb.js';
const router = Router({ caseSensitive: true });
// Setup route
router.post('/setup', BodyParserJson(), async (req, res) => {
if (UserConfig.ready)
return res.status(409).json({ success: false, message: 'User config already exists' });
log.info('Setup', 'initiated');
try {
// Parse body
new UserConfig(req.body);
// Save config
await UserConfig.saveConfigFile();
// set up new databases
if (UserConfig.config.database) {
switch (UserConfig.config.database.kind) {
case 'json':
await DBManager.use(new JSONDatabase());
break;
case 'mysql':
await DBManager.use(new MySQLDatabase());
break;
case 'postgres':
await DBManager.use(new PostgreSQLDatabase());
break;
case 'mongodb':
await DBManager.use(new MongoDBDatabase());
break;
}
}
// set rate limits
if (UserConfig.config.rateLimit?.api) setRateLimiter('api', UserConfig.config.rateLimit.api);
if (UserConfig.config.rateLimit?.login) setRateLimiter('login', UserConfig.config.rateLimit.login);
if (UserConfig.config.rateLimit?.upload) setRateLimiter('upload', UserConfig.config.rateLimit.upload);
log.success('Setup', 'completed');
return res.json({ success: true });
} catch (err: any) {
return res.status(400).json({ success: false, message: err.message });
}
});
// User login
router.post('/login', rateLimiterMiddleware('login', UserConfig.config?.rateLimit?.login), BodyParserJson(), (req, res) => {
const { username, password } = req.body;
data.getAll('users')
.then((users) => {
if (!users) throw new Error('Missing users data');
else return Object.entries(users as { [key: string]: AssUser })
.filter(([_uid, user]: [string, AssUser]) => user.username === username)[0][1]; // [0] is the first item in the filter results, [1] is is AssUser
})
.then((user) => Promise.all([bcrypt.compare(password, user.password), user]))
.then(([success, user]) => {
success ? log.success('User logged in', user.username)
: log.warn('User failed to log in', user.username);
// Set up the session information
if (success) req.session.ass!.auth = {
uid: user.id,
token: ''
};
// Respond
res.json({ success, message: `User [${user.username}] ${success ? 'logged' : 'failed to log'} in`, meta: { redirectTo: req.session.ass?.preLoginPath ?? '/user' } });
// Delete the pre-login path after successful login
if (success) delete req.session.ass?.preLoginPath;
})
.catch((err) => res.status(400).json({ success: false, message: err.message }));
});
// todo: authenticate API endpoints
router.post('/user', rateLimiterMiddleware('api', UserConfig.config?.rateLimit?.api), BodyParserJson(), async (req, res) => {
if (!UserConfig.ready)
return res.status(409).json({ success: false, message: 'User config not ready' });
const newUser = req.body as AssUserNewReq;
// Run input validation
let issue: false | string = false;
let user: AssUser;
try {
// Username check
if (!newUser.username) issue = 'Missing username';
newUser.username.replaceAll(/[^A-z0-9_-]/g, '');
if (newUser.username === '') issue = 'Invalid username';
// Password check
if (!newUser.password) issue = 'Missing password';
if (newUser.password === '') issue = 'Invalid password';
newUser.password = newUser.password.substring(0, 128);
// todo: figure out how to check admin:boolean and meta:{}
// Create new AssUser objet
user = {
id: nanoid(32),
username: newUser.username,
password: await bcrypt.hash(newUser.password, 10),
admin: newUser.admin ?? false,
meta: newUser.meta ?? {},
tokens: [],
files: []
};
log.debug(`Creating ${user.admin ? 'admin' : 'regular'} user`, user.username, user.id);
// todo: also check duplicate usernames
await data.put('users', user.id, user);
} catch (err: any) { issue = `Error: ${err.message}`; }
if (issue) return res.status(400).json({ success: false, messsage: issue });
log.debug(`User created`, user!.username);
res.json(({ success: true, message: `User ${user!.username} created` }));
});
export { router };