mirror of https://github.com/msgbyte/tailchat
feat: 增加admin登录鉴权逻辑
parent
867fbd3223
commit
2c1aa02428
@ -1,33 +1,53 @@
|
||||
import { AuthProvider } from 'react-admin';
|
||||
|
||||
export const authStorageKey = 'tailchat:admin:auth';
|
||||
|
||||
export const authProvider: AuthProvider = {
|
||||
login: ({ username, password }) => {
|
||||
// TODO
|
||||
if (username !== 'admin' || password !== 'admin') {
|
||||
return Promise.reject();
|
||||
}
|
||||
localStorage.setItem('username', username);
|
||||
return Promise.resolve();
|
||||
const request = new Request('/admin/api/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: new Headers({ 'Content-Type': 'application/json' }),
|
||||
});
|
||||
return fetch(request)
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((auth) => {
|
||||
localStorage.setItem(authStorageKey, JSON.stringify(auth));
|
||||
})
|
||||
.catch(() => {
|
||||
throw new Error('登录失败');
|
||||
});
|
||||
},
|
||||
logout: () => {
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem(authStorageKey);
|
||||
return Promise.resolve();
|
||||
},
|
||||
checkAuth: () =>
|
||||
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
|
||||
localStorage.getItem(authStorageKey) ? Promise.resolve() : 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: 'user',
|
||||
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(''),
|
||||
};
|
||||
|
@ -1,32 +1,78 @@
|
||||
import { Router } from 'express';
|
||||
import raExpressMongoose from 'express-mongoose-ra-json-server';
|
||||
import path from 'path';
|
||||
import express from 'express';
|
||||
import compression from 'compression';
|
||||
import morgan from 'morgan';
|
||||
import { createRequestHandler } from '@remix-run/express';
|
||||
import mongoose from 'mongoose';
|
||||
import bodyParser from 'body-parser';
|
||||
import { router } from './api';
|
||||
|
||||
const router = Router();
|
||||
// 链接数据库
|
||||
mongoose.connect(process.env.MONGO_URL!, (error: any) => {
|
||||
if (!error) {
|
||||
return console.info('Mongo connected');
|
||||
}
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
router.use(
|
||||
'/users',
|
||||
raExpressMongoose(require('../../../models/user/user').default, {
|
||||
q: ['nickname', 'email'],
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
'/messages',
|
||||
raExpressMongoose(require('../../../models/chat/message').default, {
|
||||
q: ['content'],
|
||||
allowedRegexFields: ['content'],
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
'/groups',
|
||||
raExpressMongoose(require('../../../models/group/group').default, {
|
||||
q: ['name'],
|
||||
})
|
||||
const BUILD_DIR = path.join(process.cwd(), 'build');
|
||||
|
||||
const app = express();
|
||||
|
||||
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' })
|
||||
);
|
||||
router.use(
|
||||
'/file',
|
||||
raExpressMongoose(require('../../../models/file').default, {
|
||||
q: ['objectName'],
|
||||
})
|
||||
|
||||
// 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', router);
|
||||
|
||||
app.all(
|
||||
'/admin/*',
|
||||
process.env.NODE_ENV === 'development'
|
||||
? (req, res, next) => {
|
||||
purgeRequireCache();
|
||||
|
||||
return createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: process.env.NODE_ENV,
|
||||
})(req, res, next);
|
||||
}
|
||||
: createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: process.env.NODE_ENV,
|
||||
})
|
||||
);
|
||||
|
||||
export { router };
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(
|
||||
`Express server listening on port ${port}, visit with: http://localhost:${port}/admin/`
|
||||
);
|
||||
});
|
||||
|
||||
function purgeRequireCache() {
|
||||
// purge require cache on requests for "server side HMR" this won't let
|
||||
// you have in-memory objects between requests in development,
|
||||
// alternatively you can set up nodemon/pm2-dev to restart the server on
|
||||
// file changes, but then you'll have to reconnect to databases/etc on each
|
||||
// change. We prefer the DX of this, so we've included it for you by default
|
||||
for (const key in require.cache) {
|
||||
if (key.startsWith(BUILD_DIR)) {
|
||||
delete require.cache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(500).end(String(err));
|
||||
}
|
||||
};
|
||||
}
|
@ -1,78 +1,5 @@
|
||||
import path from 'path';
|
||||
import express from 'express';
|
||||
import compression from 'compression';
|
||||
import morgan from 'morgan';
|
||||
import { createRequestHandler } from '@remix-run/express';
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
import { router } from './app/server/index';
|
||||
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||
|
||||
// 链接数据库
|
||||
mongoose.connect(process.env.MONGO_URL!, (error: any) => {
|
||||
if (!error) {
|
||||
return console.info('Mongo connected');
|
||||
}
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
const BUILD_DIR = path.join(process.cwd(), 'build');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(compression());
|
||||
|
||||
// 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', router);
|
||||
|
||||
app.all(
|
||||
'/admin/*',
|
||||
process.env.NODE_ENV === 'development'
|
||||
? (req, res, next) => {
|
||||
purgeRequireCache();
|
||||
|
||||
return createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: process.env.NODE_ENV,
|
||||
})(req, res, next);
|
||||
}
|
||||
: createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: process.env.NODE_ENV,
|
||||
})
|
||||
);
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(
|
||||
`Express server listening on port ${port}, visit with: http://localhost:${port}/admin/`
|
||||
);
|
||||
});
|
||||
|
||||
function purgeRequireCache() {
|
||||
// purge require cache on requests for "server side HMR" this won't let
|
||||
// you have in-memory objects between requests in development,
|
||||
// alternatively you can set up nodemon/pm2-dev to restart the server on
|
||||
// file changes, but then you'll have to reconnect to databases/etc on each
|
||||
// change. We prefer the DX of this, so we've included it for you by default
|
||||
for (const key in require.cache) {
|
||||
if (key.startsWith(BUILD_DIR)) {
|
||||
delete require.cache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
import('./app/server');
|
||||
|
Loading…
Reference in New Issue