用户变动撤销所有刷新token,立即失效accesstoken

This commit is contained in:
mtvpls
2026-06-17 11:35:46 +08:00
parent 84afb97f9f
commit d7727455dc
9 changed files with 118 additions and 4 deletions
+2
View File
@@ -2,6 +2,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { invalidateDeviceAccessToken } from '@/lib/access-token-invalidation';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db, getStorage } from '@/lib/db';
@@ -156,6 +157,7 @@ export async function DELETE(request: NextRequest) {
);
}
invalidateDeviceAccessToken(targetUsername, tokenId);
await revokeRefreshToken(targetUsername, tokenId);
const storage = getStorage();
await storage.deletePushSubscriptionsByTokenId?.(targetUsername, tokenId);
+21 -2
View File
@@ -2,14 +2,29 @@
import { NextRequest, NextResponse } from 'next/server';
import { invalidateUserAccessTokens } from '@/lib/access-token-invalidation';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getConfig } from '@/lib/config';
import { db } from '@/lib/db';
import { db, getStorage } from '@/lib/db';
import { sanitizeFeaturePermissions } from '@/lib/feature-permissions';
import { revokeAllRefreshTokens } from '@/lib/refresh-token';
export const runtime = 'nodejs';
// 支持的操作类型
async function terminateUserSessions(username: string, reason: string): Promise<void> {
try {
invalidateUserAccessTokens(username);
await revokeAllRefreshTokens(username);
const storage = getStorage();
await storage.deleteAllPushSubscriptions?.(username);
console.log(`Terminated all sessions for ${username}: ${reason}`);
} catch (error) {
console.error(`Failed to terminate sessions for ${username}:`, error);
}
}
const ACTIONS = [
'add',
'ban',
@@ -194,6 +209,7 @@ export async function POST(request: NextRequest) {
// 只更新V2存储
await db.updateUserInfoV2(targetUsername!, { banned: true });
await terminateUserSessions(targetUsername!, 'user banned');
break;
}
case 'unban': {
@@ -262,6 +278,7 @@ export async function POST(request: NextRequest) {
// 只更新V2存储
await db.updateUserInfoV2(targetUsername!, { role: 'user' });
await terminateUserSessions(targetUsername!, 'admin role revoked');
break;
}
case 'changePassword': {
@@ -296,6 +313,7 @@ export async function POST(request: NextRequest) {
// 使用新版本修改密码(SHA256加密)
await db.changePasswordV2(targetUsername!, targetPassword);
await terminateUserSessions(targetUsername!, 'password changed by admin');
break;
}
case 'deleteUser': {
@@ -321,7 +339,8 @@ export async function POST(request: NextRequest) {
);
}
// 删除V2存储中的用户
// 先终止目标用户所有会话,再删除V2存储中的用户
await terminateUserSessions(targetUsername!, 'user deleted');
await db.deleteUserV2(targetUsername!);
break;
+3
View File
@@ -2,6 +2,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { invalidateDeviceAccessToken, invalidateUserAccessTokens } from '@/lib/access-token-invalidation';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
import {
@@ -51,6 +52,7 @@ export async function DELETE(request: NextRequest) {
return NextResponse.json({ error: 'Token ID required' }, { status: 400 });
}
invalidateDeviceAccessToken(authInfo.username, tokenId);
await revokeRefreshToken(authInfo.username, tokenId);
const storage = getStorage();
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, tokenId);
@@ -71,6 +73,7 @@ export async function POST(request: NextRequest) {
}
try {
invalidateUserAccessTokens(authInfo.username);
await revokeAllRefreshTokens(authInfo.username);
const storage = getStorage();
await storage.deleteAllPushSubscriptions?.(authInfo.username);
+21
View File
@@ -2,6 +2,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAuthInfoFromCookie, parseAuthInfo } from '@/lib/auth';
import { db } from '@/lib/db';
import { refreshAccessToken } from '@/lib/middleware-auth';
import { TOKEN_CONFIG } from '@/lib/refresh-token';
@@ -73,6 +74,26 @@ export async function POST(request: NextRequest) {
const now = Date.now();
if (authInfo.username === process.env.USERNAME) {
if (authInfo.role !== 'owner') {
return NextResponse.json({ error: 'Role changed' }, { status: 401 });
}
} else {
const userInfo = await db.getUserInfoV2(authInfo.username);
if (!userInfo) {
return NextResponse.json({ error: 'User not found' }, { status: 401 });
}
if (userInfo.banned) {
return NextResponse.json({ error: 'User banned' }, { status: 403 });
}
if (userInfo.role !== authInfo.role) {
return NextResponse.json({ error: 'Role changed' }, { status: 401 });
}
}
// 只检查 Refresh Token 是否过期
if (now >= authInfo.refreshExpires) {
return NextResponse.json(
+2
View File
@@ -2,6 +2,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { invalidateDeviceAccessToken } from '@/lib/access-token-invalidation';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
import { db } from '@/lib/db';
@@ -59,6 +60,7 @@ export async function POST(request: NextRequest) {
// 撤销所有非当前设备的 token
for (const device of devices) {
if (device.tokenId !== currentTokenId) {
invalidateDeviceAccessToken(username, device.tokenId);
await revokeRefreshToken(username, device.tokenId);
await storage.deletePushSubscriptionsByTokenId?.(username, device.tokenId);
console.log(`Revoked token ${device.tokenId} for ${username} after password change`);
+2
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { invalidateDeviceAccessToken } from '@/lib/access-token-invalidation';
import { getAuthInfoFromCookie } from '@/lib/auth';
import { getStorage } from '@/lib/db';
import { revokeRefreshToken } from '@/lib/refresh-token';
@@ -12,6 +13,7 @@ export async function POST(request: NextRequest) {
// 撤销当前设备的 Refresh Token
if (authInfo && authInfo.username && authInfo.tokenId) {
try {
invalidateDeviceAccessToken(authInfo.username, authInfo.tokenId);
await revokeRefreshToken(authInfo.username, authInfo.tokenId);
const storage = getStorage();
await storage.deletePushSubscriptionsByTokenId?.(authInfo.username, authInfo.tokenId);