mirror of https://github.com/msgbyte/tailchat
feat: 增加admin登录鉴权逻辑
parent
867fbd3223
commit
2c1aa02428
@ -1,33 +1,53 @@
|
|||||||
import { AuthProvider } from 'react-admin';
|
import { AuthProvider } from 'react-admin';
|
||||||
|
|
||||||
|
export const authStorageKey = 'tailchat:admin:auth';
|
||||||
|
|
||||||
export const authProvider: AuthProvider = {
|
export const authProvider: AuthProvider = {
|
||||||
login: ({ username, password }) => {
|
login: ({ username, password }) => {
|
||||||
// TODO
|
const request = new Request('/admin/api/login', {
|
||||||
if (username !== 'admin' || password !== 'admin') {
|
method: 'POST',
|
||||||
return Promise.reject();
|
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) => {
|
||||||
|
localStorage.setItem(authStorageKey, JSON.stringify(auth));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('登录失败');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
logout: () => {
|
logout: () => {
|
||||||
localStorage.removeItem('username');
|
localStorage.removeItem(authStorageKey);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
checkAuth: () =>
|
checkAuth: () =>
|
||||||
localStorage.getItem('username') ? Promise.resolve() : Promise.reject(),
|
localStorage.getItem(authStorageKey) ? Promise.resolve() : 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
|
// 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: 'user',
|
localStorage.getItem(authStorageKey) ?? '{}'
|
||||||
fullName: 'Admin',
|
);
|
||||||
}),
|
if (!username) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
id: username,
|
||||||
|
fullName: username,
|
||||||
|
});
|
||||||
|
},
|
||||||
getPermissions: () => Promise.resolve(''),
|
getPermissions: () => Promise.resolve(''),
|
||||||
};
|
};
|
||||||
|
@ -1,32 +1,78 @@
|
|||||||
import { Router } from 'express';
|
import path from 'path';
|
||||||
import raExpressMongoose from 'express-mongoose-ra-json-server';
|
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(
|
const BUILD_DIR = path.join(process.cwd(), 'build');
|
||||||
'/users',
|
|
||||||
raExpressMongoose(require('../../../models/user/user').default, {
|
const app = express();
|
||||||
q: ['nickname', 'email'],
|
|
||||||
})
|
app.use(compression());
|
||||||
);
|
app.use(bodyParser());
|
||||||
router.use(
|
|
||||||
'/messages',
|
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
|
||||||
raExpressMongoose(require('../../../models/chat/message').default, {
|
app.disable('x-powered-by');
|
||||||
q: ['content'],
|
|
||||||
allowedRegexFields: ['content'],
|
// Remix fingerprints its assets so we can cache forever.
|
||||||
})
|
app.use(
|
||||||
);
|
'/build',
|
||||||
router.use(
|
express.static('public/build', { immutable: true, maxAge: '1y' })
|
||||||
'/groups',
|
|
||||||
raExpressMongoose(require('../../../models/group/group').default, {
|
|
||||||
q: ['name'],
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
router.use(
|
|
||||||
'/file',
|
// Everything else (like favicon.ico) is cached for an hour. You may want to be
|
||||||
raExpressMongoose(require('../../../models/file').default, {
|
// more aggressive with this caching.
|
||||||
q: ['objectName'],
|
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 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 dotenv from 'dotenv';
|
||||||
import { router } from './app/server/index';
|
|
||||||
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
||||||
|
|
||||||
// 链接数据库
|
import('./app/server');
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue