All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m20s
v0.9.5 — 账号安全管控 + 内容资产页: - 首次登录强制改密(must_change_password + ForceChangePasswordModal) - 并发会话限制(ActiveSession + SessionJWT认证,可配置桌面/移动端会话数) - Token生命周期缩短(access 30min, refresh 1天) - 登录IP记录(LoginRecord模型,为异常检测打基础) - 内容资产页(超管三级折叠/团队管两级折叠,按需懒加载) v0.9.6 — UI修缮: - 侧栏导航排序(内容资产移到用户管理下方) - 视频网格高度调整(440px,3行+暗示可滚动) - 秒数单位统一(不再换算为分钟/小时) - 提示词标签溢出修复 + 弹窗方向自适应 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
31 lines
1.1 KiB
Python
31 lines
1.1 KiB
Python
"""Custom JWT authentication — validates session_id against ActiveSession table."""
|
|
|
|
from rest_framework_simplejwt.authentication import JWTAuthentication
|
|
from rest_framework_simplejwt.exceptions import InvalidToken
|
|
|
|
|
|
class SessionJWTAuthentication(JWTAuthentication):
|
|
"""
|
|
Extends JWTAuthentication to check that the session_id in the token
|
|
still exists in the ActiveSession table.
|
|
|
|
Legacy tokens (without session_id) are allowed through for backward compatibility.
|
|
"""
|
|
|
|
def get_user(self, validated_token):
|
|
user = super().get_user(validated_token)
|
|
|
|
session_id = validated_token.get('session_id')
|
|
if session_id is None:
|
|
# Legacy token without session_id — allow through
|
|
return user
|
|
|
|
from .models import ActiveSession
|
|
if not ActiveSession.objects.filter(user=user, session_id=session_id).exists():
|
|
raise InvalidToken({
|
|
'detail': '您的账号已在其他设备登录',
|
|
'code': 'session_expired_other_device',
|
|
})
|
|
|
|
return user
|