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 = {
|
export const authProvider: AuthProvider = {
|
||||||
login: ({ username, password }) => {
|
login: ({ username, password }) => {
|
||||||
if (username !== 'tushan' || password !== 'tushan') {
|
const request = new Request('/admin/api/login', {
|
||||||
return Promise.reject();
|
method: 'POST',
|
||||||
}
|
body: JSON.stringify({ username, password }),
|
||||||
|
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||||
|
});
|
||||||
|
|
||||||
localStorage.setItem('username', username);
|
return fetch(request)
|
||||||
return Promise.resolve();
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((auth) => {
|
||||||
|
console.log(auth);
|
||||||
|
localStorage.setItem(authStorageKey, JSON.stringify(auth));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Login Failed');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
logout: () => {
|
logout: () => {
|
||||||
localStorage.removeItem('username');
|
localStorage.removeItem(authStorageKey);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
checkAuth: () =>
|
checkAuth: () => {
|
||||||
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
|
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) => {
|
checkError: (error) => {
|
||||||
const status = error.status;
|
const status = error.status;
|
||||||
if (status === 401 || status === 403) {
|
if (status === 401 || status === 403) {
|
||||||
localStorage.removeItem('username');
|
localStorage.removeItem(authStorageKey);
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// other error code (404, 500, etc): no need to log out
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
getIdentity: () =>
|
getIdentity: () => {
|
||||||
Promise.resolve({
|
const { username } = JSON.parse(
|
||||||
id: '0',
|
localStorage.getItem(authStorageKey) ?? '{}'
|
||||||
fullName: 'Admin',
|
);
|
||||||
}),
|
if (!username) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
id: username,
|
||||||
|
fullName: username,
|
||||||
|
});
|
||||||
|
},
|
||||||
getPermissions: () => Promise.resolve(''),
|
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 express from 'express';
|
||||||
import ViteExpress from 'vite-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 app = express();
|
||||||
|
|
||||||
const port = Number(process.env.PORT || 13000);
|
const port = Number(process.env.ADMIN_PORT || 13000);
|
||||||
|
|
||||||
app.get('/hello', (_, res) => {
|
if (!process.env.MONGO_URL) {
|
||||||
res.send('Hello Vite + React + TypeScript!');
|
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, () =>
|
ViteExpress.listen(app, port, () =>
|
||||||
console.log(
|
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