mirror of https://github.com/msgbyte/tailchat
refactor: move backend logic to admin-next
parent
6640397b7a
commit
9e6912fa91
@ -1,33 +1,66 @@
|
||||
import { AuthProvider } from 'tushan';
|
||||
import type { AuthProvider } from 'tushan';
|
||||
|
||||
export const authStorageKey = 'tailchat:admin:auth';
|
||||
|
||||
export const authProvider: AuthProvider = {
|
||||
login: ({ username, password }) => {
|
||||
if (username !== 'tushan' || password !== 'tushan') {
|
||||
return Promise.reject();
|
||||
}
|
||||
const request = new Request('/admin/api/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
});
|
||||
|
||||
localStorage.setItem('username', username);
|
||||
return Promise.resolve();
|
||||
return fetch(request)
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((auth) => {
|
||||
console.log(auth);
|
||||
localStorage.setItem(authStorageKey, JSON.stringify(auth));
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error('Login Failed');
|
||||
});
|
||||
},
|
||||
logout: () => {
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem(authStorageKey);
|
||||
return Promise.resolve();
|
||||
},
|
||||
checkAuth: () =>
|
||||
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
|
||||
checkAuth: () => {
|
||||
const auth = localStorage.getItem(authStorageKey);
|
||||
if (auth) {
|
||||
try {
|
||||
const obj = JSON.parse(auth);
|
||||
if (obj.expiredAt && Date.now() < obj.expiredAt) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
},
|
||||
checkError: (error) => {
|
||||
const status = error.status;
|
||||
if (status === 401 || status === 403) {
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem(authStorageKey);
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
// other error code (404, 500, etc): no need to log out
|
||||
return Promise.resolve();
|
||||
},
|
||||
getIdentity: () =>
|
||||
Promise.resolve({
|
||||
id: '0',
|
||||
fullName: 'Admin',
|
||||
}),
|
||||
getIdentity: () => {
|
||||
const { username } = JSON.parse(
|
||||
localStorage.getItem(authStorageKey) ?? '{}'
|
||||
);
|
||||
if (!username) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
id: username,
|
||||
fullName: username,
|
||||
});
|
||||
},
|
||||
getPermissions: () => Promise.resolve(''),
|
||||
};
|
||||
|
@ -0,0 +1,28 @@
|
||||
import { TcBroker, SYSTEM_USERID } from 'tailchat-server-sdk';
|
||||
import brokerConfig from '../../../moleculer.config';
|
||||
|
||||
const transporter = process.env.TRANSPORTER;
|
||||
export const broker = new TcBroker({
|
||||
...brokerConfig,
|
||||
metrics: false,
|
||||
logger: false,
|
||||
transporter,
|
||||
});
|
||||
|
||||
broker.start().then(() => {
|
||||
console.log('Linked to Tailchat network, TRANSPORTER: ', transporter);
|
||||
});
|
||||
|
||||
export function callBrokerAction<T>(
|
||||
actionName: string,
|
||||
params: any,
|
||||
opts?: Record<string, any>
|
||||
): Promise<T> {
|
||||
return broker.call(actionName, params, {
|
||||
...opts,
|
||||
meta: {
|
||||
...opts?.meta,
|
||||
userId: SYSTEM_USERID,
|
||||
},
|
||||
});
|
||||
}
|
@ -1,16 +1,58 @@
|
||||
import express from 'express';
|
||||
import ViteExpress from 'vite-express';
|
||||
import mongoose from 'mongoose';
|
||||
import compression from 'compression';
|
||||
import morgan from 'morgan';
|
||||
import bodyParser from 'body-parser';
|
||||
import path from 'path';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });
|
||||
import { apiRouter } from './router/api';
|
||||
|
||||
const app = express();
|
||||
|
||||
const port = Number(process.env.PORT || 13000);
|
||||
const port = Number(process.env.ADMIN_PORT || 13000);
|
||||
|
||||
app.get('/hello', (_, res) => {
|
||||
res.send('Hello Vite + React + TypeScript!');
|
||||
if (!process.env.MONGO_URL) {
|
||||
console.error('Require env: MONGO_URL');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 链接数据库
|
||||
mongoose.connect(process.env.MONGO_URL, (error: any) => {
|
||||
if (!error) {
|
||||
return console.info('Datebase connected');
|
||||
}
|
||||
console.error('Datebase connect error', error);
|
||||
});
|
||||
|
||||
app.use(compression());
|
||||
app.use(bodyParser());
|
||||
|
||||
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
|
||||
app.disable('x-powered-by');
|
||||
|
||||
// Remix fingerprints its assets so we can cache forever.
|
||||
app.use(
|
||||
'/build',
|
||||
express.static('public/build', { immutable: true, maxAge: '1y' })
|
||||
);
|
||||
|
||||
// Everything else (like favicon.ico) is cached for an hour. You may want to be
|
||||
// more aggressive with this caching.
|
||||
app.use(express.static('public', { maxAge: '1h' }));
|
||||
|
||||
app.use(morgan('tiny'));
|
||||
|
||||
app.use('/admin/api', apiRouter);
|
||||
|
||||
app.use((err: any, req: any, res: any, next: any) => {
|
||||
res.status(500);
|
||||
res.json({ error: err.message });
|
||||
});
|
||||
|
||||
ViteExpress.listen(app, port, () =>
|
||||
console.log(
|
||||
`Server is listening on port ${port}, visit with: http://localhost:${port}`
|
||||
`Server is listening on port ${port}, visit with: http://localhost:${port}/admin/`
|
||||
)
|
||||
);
|
||||
|
@ -0,0 +1,39 @@
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import md5 from 'md5';
|
||||
|
||||
export const adminAuth = {
|
||||
username: process.env.ADMIN_USER,
|
||||
password: process.env.ADMIN_PASS,
|
||||
};
|
||||
|
||||
export const authSecret =
|
||||
(process.env.SECRET || 'tailchat') + md5(JSON.stringify(adminAuth)); // 增加一个md5的盐值确保SECRET没有设置的情况下只修改了用户名密码也不会被人伪造token秘钥
|
||||
|
||||
export function auth() {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const authorization = req.headers.authorization;
|
||||
if (!authorization) {
|
||||
res.status(401).end('not found authorization in headers');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = authorization.slice('Bearer '.length);
|
||||
|
||||
const payload = jwt.verify(token, authSecret);
|
||||
if (typeof payload === 'string') {
|
||||
res.status(401).end('payload type error');
|
||||
return;
|
||||
}
|
||||
if (payload.platform !== 'admin') {
|
||||
res.status(401).end('Payload invalid');
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).end(String(err));
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Network 相关接口
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { broker } from '../broker';
|
||||
import { auth } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/client', auth(), async (req, res, next) => {
|
||||
try {
|
||||
const config = await broker.call('config.client');
|
||||
|
||||
res.json({
|
||||
config,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.patch('/client', auth(), async (req, res, next) => {
|
||||
try {
|
||||
await broker.call('config.setClientConfig', {
|
||||
key: req.body.key,
|
||||
value: req.body.value,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
export { router as configRouter };
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Network 相关接口
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { callBrokerAction } from '../broker';
|
||||
import { auth } from '../middleware/auth';
|
||||
import Busboy from '@fastify/busboy';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.put('/upload', auth(), async (req, res) => {
|
||||
const busboy = new Busboy({ headers: req.headers as any });
|
||||
|
||||
const promises: Promise<any>[] = [];
|
||||
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
|
||||
promises.push(
|
||||
callBrokerAction('file.save', file, {
|
||||
filename: filename,
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
return data;
|
||||
})
|
||||
.catch((err) => {
|
||||
file.resume(); // Drain file stream to continue processing form
|
||||
busboy.emit('error', err);
|
||||
return err;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
busboy.on('finish', async () => {
|
||||
/* istanbul ignore next */
|
||||
if (promises.length == 0) {
|
||||
res.status(500).json('File missing in the request');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = await Promise.all(promises);
|
||||
|
||||
res.json({ files });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json(String(err));
|
||||
}
|
||||
});
|
||||
|
||||
busboy.on('error', (err) => {
|
||||
console.error(err);
|
||||
req.unpipe(busboy);
|
||||
req.resume();
|
||||
res.status(500).json({ err });
|
||||
});
|
||||
|
||||
req.pipe(busboy);
|
||||
});
|
||||
|
||||
export { router as fileRouter };
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Network 相关接口
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { broker } from '../broker';
|
||||
import { auth } from '../middleware/auth';
|
||||
import _ from 'lodash';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/all', auth(), async (req, res) => {
|
||||
res.json({
|
||||
nodes: Array.from(new Map(broker.registry.nodes.nodes).values()).map(
|
||||
(item) =>
|
||||
_.pick(item, [
|
||||
'id',
|
||||
'available',
|
||||
'local',
|
||||
'ipList',
|
||||
'hostname',
|
||||
'cpu',
|
||||
'client',
|
||||
])
|
||||
),
|
||||
events: broker.registry.events.events.map((item: any) => item.name),
|
||||
services: broker.registry.services.services.map((item: any) => item.name),
|
||||
actions: Array.from(new Map(broker.registry.actions.actions).keys()),
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/ping', auth(), async (req, res) => {
|
||||
const pong = await broker.ping();
|
||||
res.json(pong);
|
||||
});
|
||||
|
||||
export { router as networkRouter };
|
Loading…
Reference in New Issue