From b7a64fd5c07cbaddfab199adfaf3c1dcb33d454b Mon Sep 17 00:00:00 2001 From: chaos-zhu Date: Sat, 27 Jun 2026 12:52:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=9C=89=E6=95=88=E6=9C=9F=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- native/lib/core/utils/jwt_expiry.dart | 32 +++-------------- .../lib/features/auth/login_controller.dart | 6 ++-- native/lib/features/auth/login_page.dart | 4 +-- native/lib/l10n/strings_en.dart | 3 +- native/lib/l10n/strings_zh.dart | 7 ++-- native/test/core/utils/jwt_expiry_test.dart | 15 ++------ server/app/controller/user.js | 14 ++++++-- web/src/store/index.js | 9 ++--- web/src/utils/index.js | 19 +---------- web/src/views/login/index.vue | 34 ++++++++----------- 10 files changed, 44 insertions(+), 99 deletions(-) diff --git a/native/lib/core/utils/jwt_expiry.dart b/native/lib/core/utils/jwt_expiry.dart index 767a198..2cf6e3e 100644 --- a/native/lib/core/utils/jwt_expiry.dart +++ b/native/lib/core/utils/jwt_expiry.dart @@ -1,36 +1,12 @@ -enum LoginExpiry { temporary, currentDay, threeDays, sevenDays } +enum LoginExpiry { threeDays, sevenDays, thirtyDays } -String jwtExpiresFor(LoginExpiry expiry, {DateTime? now}) { +String jwtExpiresFor(LoginExpiry expiry) { switch (expiry) { - case LoginExpiry.temporary: - return '1h'; - case LoginExpiry.currentDay: - final current = now ?? DateTime.now(); - final tomorrow = DateTime(current.year, current.month, current.day + 1); - final seconds = tomorrow.difference(current).inSeconds; - return '${seconds}s'; case LoginExpiry.threeDays: return '3d'; case LoginExpiry.sevenDays: return '7d'; + case LoginExpiry.thirtyDays: + return '30d'; } } - -int jwtExpireAtFor(LoginExpiry expiry, {DateTime? now}) { - final current = now ?? DateTime.now(); - final expires = jwtExpiresFor(expiry, now: current); - final match = RegExp(r'^(\d+)([smhd])$').firstMatch(expires); - if (match == null) { - throw const FormatException('invalid jwt expiry'); - } - final count = int.parse(match.group(1)!); - final unit = match.group(2)!; - final multiplier = switch (unit) { - 's' => 1000, - 'm' => 60 * 1000, - 'h' => 60 * 60 * 1000, - 'd' => 24 * 60 * 60 * 1000, - _ => 1000, - }; - return current.millisecondsSinceEpoch + count * multiplier; -} diff --git a/native/lib/features/auth/login_controller.dart b/native/lib/features/auth/login_controller.dart index a372c14..3c57a1b 100644 --- a/native/lib/features/auth/login_controller.dart +++ b/native/lib/features/auth/login_controller.dart @@ -63,8 +63,7 @@ class LoginController { required String mfa2Token, required bool httpRiskAccepted, required bool savePassword, - LoginExpiry expiry = LoginExpiry.temporary, - DateTime? now, + LoginExpiry expiry = LoginExpiry.threeDays, }) async { final String normalized; try { @@ -99,8 +98,7 @@ class LoginController { final response = await api.postJson('/login', { 'loginName': username, 'ciphertext': ciphertext, - 'jwtExpires': jwtExpiresFor(expiry, now: now), - 'jwtExpireAt': jwtExpireAtFor(expiry, now: now), + 'jwtExpires': jwtExpiresFor(expiry), if (mfa2Token.isNotEmpty) 'mfa2Token': mfa2Token, }); diff --git a/native/lib/features/auth/login_page.dart b/native/lib/features/auth/login_page.dart index 7485fe9..9235f17 100644 --- a/native/lib/features/auth/login_page.dart +++ b/native/lib/features/auth/login_page.dart @@ -41,7 +41,7 @@ class _LoginPageState extends State { final FocusNode _userFocus = FocusNode(); final FocusNode _passwordFocus = FocusNode(); - LoginExpiry _expiry = LoginExpiry.currentDay; + LoginExpiry _expiry = LoginExpiry.threeDays; bool _savePassword = false; bool _httpRiskAccepted = false; bool _submitting = false; @@ -564,9 +564,9 @@ class _ExpiryPicker extends StatelessWidget { final ValueChanged onChanged; static const _optionKeys = { - LoginExpiry.currentDay: 'login.expiry.currentDay', LoginExpiry.threeDays: 'login.expiry.threeDays', LoginExpiry.sevenDays: 'login.expiry.sevenDays', + LoginExpiry.thirtyDays: 'login.expiry.thirtyDays', }; @override diff --git a/native/lib/l10n/strings_en.dart b/native/lib/l10n/strings_en.dart index 5110061..2609bf4 100644 --- a/native/lib/l10n/strings_en.dart +++ b/native/lib/l10n/strings_en.dart @@ -49,10 +49,9 @@ const Map stringsEn = { 'login.errMissingFields': 'Server login response is missing required fields', // Login expiry options - 'login.expiry.temporary': 'Temporary, 1 hour', - 'login.expiry.currentDay': 'Today', 'login.expiry.threeDays': '3 days', 'login.expiry.sevenDays': '7 days', + 'login.expiry.thirtyDays': '30 days', // Main shell tabs 'tabs.servers': 'Servers', diff --git a/native/lib/l10n/strings_zh.dart b/native/lib/l10n/strings_zh.dart index ea729c6..dfa9428 100644 --- a/native/lib/l10n/strings_zh.dart +++ b/native/lib/l10n/strings_zh.dart @@ -46,10 +46,9 @@ const Map stringsZh = { 'login.errSchemeUnsupported': '服务端地址仅支持 http 或 https', 'login.errLoginGeneric': '登录失败', 'login.errMissingFields': '服务端登录响应缺少必要字段', - 'login.expiry.temporary': '临时会话,1 小时', - 'login.expiry.currentDay': '今天', - 'login.expiry.threeDays': '3 天', - 'login.expiry.sevenDays': '7 天', + 'login.expiry.threeDays': '3天', + 'login.expiry.sevenDays': '7天', + 'login.expiry.thirtyDays': '30天', // Tabs 'tabs.servers': '服务器', diff --git a/native/test/core/utils/jwt_expiry_test.dart b/native/test/core/utils/jwt_expiry_test.dart index 5b4e75f..1956a6f 100644 --- a/native/test/core/utils/jwt_expiry_test.dart +++ b/native/test/core/utils/jwt_expiry_test.dart @@ -2,23 +2,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:easynode_native/core/utils/jwt_expiry.dart'; void main() { - test('maps temporary expiry to one hour', () { - expect(jwtExpiresFor(LoginExpiry.temporary), '1h'); - }); - test('maps three days and seven days', () { expect(jwtExpiresFor(LoginExpiry.threeDays), '3d'); expect(jwtExpiresFor(LoginExpiry.sevenDays), '7d'); }); - test('maps current day to remaining seconds until midnight', () { - final fixed = DateTime(2026, 5, 16, 23, 59, 30); - expect(jwtExpiresFor(LoginExpiry.currentDay, now: fixed), '30s'); - }); - - test('jwtExpireAtFor returns milliseconds advanced by the duration', () { - final fixed = DateTime.utc(2026, 5, 16, 0, 0, 0); - final expectedMs = fixed.millisecondsSinceEpoch + 60 * 60 * 1000; - expect(jwtExpireAtFor(LoginExpiry.temporary, now: fixed), expectedMs); + test('maps thirty days', () { + expect(jwtExpiresFor(LoginExpiry.thirtyDays), '30d'); }); } diff --git a/server/app/controller/user.js b/server/app/controller/user.js index 5ec703a..b68df3f 100644 --- a/server/app/controller/user.js +++ b/server/app/controller/user.js @@ -17,6 +17,13 @@ const sessionDB = new SessionDB().getInstance() const plusDB = new PlusDB().getInstance() const runtimeState = new RuntimeState().getInstance() +// 仅允许三种登录有效期:3天 / 7天 / 30天 +const ALLOWED_JWT_EXPIRES = { + '3d': 3 * 24 * 60 * 60 * 1000, + '7d': 7 * 24 * 60 * 60 * 1000, + '30d': 30 * 24 * 60 * 60 * 1000 +} + const getpublicKey = async ({ res }) => { let { publicKey: data } = await keyDB.findOneAsync({}) if (!data) return res.fail({ msg: 'publicKey not found, Try to restart the server', status: 500 }) @@ -46,8 +53,11 @@ let forbidLogin = false const login = async (ctx) => { const { res, request } = ctx - let { body: { loginName, ciphertext, jwtExpires, jwtExpireAt, mfa2Token }, ip: clientIp, header } = request - if (!loginName || !ciphertext || !jwtExpires || !jwtExpireAt || !header) return res.fail({ msg: '请求非法!' }) + let { body: { loginName, ciphertext, jwtExpires, mfa2Token }, ip: clientIp, header } = request + if (!loginName || !ciphertext || !jwtExpires || !header) return res.fail({ msg: '请求非法!' }) + const jwtExpiresDuration = ALLOWED_JWT_EXPIRES[jwtExpires] + if (typeof jwtExpiresDuration !== 'number') return res.fail({ msg: '请求非法!' }) + const jwtExpireAt = Date.now() + jwtExpiresDuration if (forbidLogin) return res.fail({ msg: `禁止登录! 倒计时[${ loginCountDown }s]后尝试登录或重启面板服务` }) loginErrCount++ loginErrTotal++ diff --git a/web/src/store/index.js b/web/src/store/index.js index ba25760..b40637e 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -14,7 +14,7 @@ const useStore = defineStore('global', { localScriptList: [], suspendedSessions: [], // 挂起的会话列表 user: localStorage.getItem('user') || null, - token: localStorage.getItem('token') || sessionStorage.getItem('token') || null, + token: localStorage.getItem('token') || null, deviceId: localStorage.getItem('deviceId') || null, title: '', isDark: false, @@ -82,9 +82,8 @@ const useStore = defineStore('global', { chatHistory: [] }), actions: { - async setJwtToken(token, isSession = true) { - if (isSession) sessionStorage.setItem('token', token) - else localStorage.setItem('token', token) + async setJwtToken(token) { + localStorage.setItem('token', token) this.$patch({ token }) }, async setUser(username, deviceId) { @@ -104,8 +103,6 @@ const useStore = defineStore('global', { console.error('注销登录凭证失败: ', err.message) } finally { localStorage.removeItem('token') - sessionStorage.removeItem('token') - sessionStorage.removeItem('uid') localStorage.removeItem('uid') localStorage.removeItem('user') localStorage.removeItem('deviceId') diff --git a/web/src/utils/index.js b/web/src/utils/index.js index 5977e8e..10655a1 100644 --- a/web/src/utils/index.js +++ b/web/src/utils/index.js @@ -37,23 +37,6 @@ export const randomStr = (e) =>{ return res } -export const jwtExpireToTimestamp = (str) => { - const match = /^(\d+)([smhd])$/.exec(str) - if (!match) throw new Error('jwt过期格式错误,必须是 [xx]s / [xx]h / [xx]d') - - const n = Number(match[1]) - const unit = match[2] - - const map = { - s: 1000, - m: 60 * 1000, - h: 60 * 60 * 1000, - d: 24 * 60 * 60 * 1000 - } - - return n * map[unit] + Date.now() -} - // rsa公钥加密 export const RSAEncrypt = (text) => { const publicKey = localStorage.getItem('publicKey') @@ -210,4 +193,4 @@ export const generateSocketInstance = (path, config = { forceNew: false, reconne }, ...config }) -} \ No newline at end of file +} diff --git a/web/src/views/login/index.vue b/web/src/views/login/index.vue index 8dad7c6..12007a2 100644 --- a/web/src/views/login/index.vue +++ b/web/src/views/login/index.vue @@ -58,10 +58,9 @@ @@ -83,19 +82,18 @@