faet: 修改README.md,提升安全级别意识.
This commit is contained in:
1 parent
06b418ec51
commit
face59cdd7
5 files changed
+67
-13
No files matched your search
@@ -71,6 +71,7 @@ export type EnvBindings = {
|
||||
OAUTH_TELEGRAM_BOT_NAME?: string;
|
||||
OAUTH_TELEGRAM_BOT_TOKEN?: string;
|
||||
OAUTH_ALLOWED_USERS: string; // 允许登录的 Email 或 Username 白名单 (必填)
|
||||
OAUTH_ALLOW_ALL?: string; // 是否允许所有用户登录 (仅推荐用于测试演示环境)
|
||||
JWT_SECRET: string;
|
||||
ENCRYPTION_KEY: string;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Hono } from 'hono';
|
||||
import { setCookie, deleteCookie } from 'hono/cookie';
|
||||
import { setCookie, getCookie, deleteCookie } from 'hono/cookie';
|
||||
import { EnvBindings, AppError } from '@/app/config';
|
||||
import { authMiddleware } from '@/shared/middleware/auth';
|
||||
import { getAvailableProviders } from '@/features/auth/providers/index';
|
||||
@@ -33,6 +33,16 @@ auth.get('/authorize/:provider', async (c) => {
|
||||
// 生成包含唯一 state 防御 CSRF 的跳转地址
|
||||
const authData = await service.generateAuthorizeUrl(providerName);
|
||||
|
||||
// 将 state 存入服务端的 HttpOnly Cookie,有效期 10 分钟
|
||||
const stateCookieName = `oauth_state_${providerName}`;
|
||||
setCookie(c, stateCookieName, authData.state, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'Lax', // 允许从第三方回调跳回时携带
|
||||
maxAge: 10 * 60, // 10分钟有效期
|
||||
path: '/',
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
authUrl: authData.url,
|
||||
@@ -45,6 +55,20 @@ auth.get('/authorize/:provider', async (c) => {
|
||||
auth.post('/callback/:provider', async (c) => {
|
||||
const providerName = c.req.param('provider');
|
||||
const body = await c.req.json();
|
||||
|
||||
// --- State 闭环校验开始 ---
|
||||
const stateCookieName = `oauth_state_${providerName}`;
|
||||
const serverState = getCookie(c, stateCookieName);
|
||||
const clientState = body.state; // 前端传回的回调 state 参数
|
||||
|
||||
if (!serverState || !clientState || serverState !== clientState) {
|
||||
throw new AppError('Invalid or missing OAuth state. Security validation failed.', 403);
|
||||
}
|
||||
|
||||
// 校验通过,清理一次性 State Cookie
|
||||
deleteCookie(c, stateCookieName, { path: '/', secure: true, sameSite: 'Lax' });
|
||||
// --- State 闭环校验结束 ---
|
||||
|
||||
const service = getService(c);
|
||||
|
||||
// 调用 Service 层处理登录
|
||||
|
||||
@@ -80,14 +80,19 @@ export class AuthService {
|
||||
* 白名单校验
|
||||
*/
|
||||
private verifyWhitelist(userInfo: OAuthUserInfo, whitelistFields: string[]) {
|
||||
// 如果 explicitly allowed all, 则放行
|
||||
const allowAllStr = String(this.env.OAUTH_ALLOW_ALL || '').toLowerCase();
|
||||
if (allowAllStr === 'true' || allowAllStr === '1') {
|
||||
return;
|
||||
}
|
||||
|
||||
const allowedUsersStr = this.env.OAUTH_ALLOWED_USERS || '';
|
||||
const allowedIdentities = allowedUsersStr.split(',').map((e: string) => e.trim().toLowerCase()).filter(Boolean);
|
||||
|
||||
const userEmail = (userInfo.email || '').toLowerCase();
|
||||
const userName = (userInfo.username || '').toLowerCase();
|
||||
|
||||
// 仅在配置了白名单且 OAUTH_ALLOWED_USERS 开发用的特定值不同时校验
|
||||
if (allowedIdentities.length > 0 && this.env.OAUTH_ALLOWED_USERS !== this.env.JWT_SECRET) {
|
||||
// 如果配置了白名单,严格执行
|
||||
if (allowedIdentities.length > 0) {
|
||||
const userEmail = (userInfo.email || '').toLowerCase();
|
||||
const userName = (userInfo.username || '').toLowerCase();
|
||||
let isAllowed = false;
|
||||
|
||||
if (whitelistFields.includes('email') && userEmail && allowedIdentities.includes(userEmail)) {
|
||||
@@ -101,6 +106,9 @@ export class AuthService {
|
||||
if (!isAllowed) {
|
||||
throw new AppError(`Unauthorized user. Email: ${userEmail}, Username: ${userName}`, 403);
|
||||
}
|
||||
} else {
|
||||
// 默认安全策略:如果未配置白名单,未开启 ALLOW_ALL,则拒绝所有人(或者也可以记录警告,但为了安全通常应拒绝)
|
||||
throw new AppError(`No authorization whitelist configured. Login denied.`, 403);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ export class VaultService {
|
||||
constructor(env: EnvBindings, repository: VaultRepository) {
|
||||
this.env = env;
|
||||
this.repository = repository;
|
||||
this.encryptionKey = env.ENCRYPTION_KEY || env.JWT_SECRET;
|
||||
|
||||
if (!env.ENCRYPTION_KEY) {
|
||||
throw new AppError('FATAL ERROR: ENCRYPTION_KEY is not configured.', 500);
|
||||
}
|
||||
|
||||
this.encryptionKey = env.ENCRYPTION_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user