Skip to content

Commit 1afc82c

Browse files
authored
feat: Add account ban handling and UI updates (#11)
- Add ban status and reason fields to account configuration - Add account ban status and details handling in API refresh account function. - Add logic to handle account suspension and authentication errors, updating ban status accordingly. - Add and style badge classes for different account statuses and modify account status display logic.
1 parent 306f49f commit 1afc82c

File tree

4 files changed

+142
-7
lines changed

4 files changed

+142
-7
lines changed

config/config.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ type Account struct {
5151
MachineId string `json:"machineId,omitempty"` // UUID machine identifier for request tracking
5252

5353
// Account status
54-
Enabled bool `json:"enabled"` // Whether account is active in the pool
54+
Enabled bool `json:"enabled"` // Whether account is active in the pool
55+
BanStatus string `json:"banStatus,omitempty"` // Ban status: "ACTIVE", "BANNED", "SUSPENDED"
56+
BanReason string `json:"banReason,omitempty"` // Reason for ban/suspension
57+
BanTime int64 `json:"banTime,omitempty"` // Timestamp when ban was detected
5558

5659
// Subscription information
5760
SubscriptionType string `json:"subscriptionType,omitempty"` // Tier: FREE, PRO, PRO_PLUS, or POWER

proxy/handler.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,9 @@ func (h *Handler) apiGetAccounts(w http.ResponseWriter, r *http.Request) {
14011401
"provider": a.Provider,
14021402
"region": a.Region,
14031403
"enabled": a.Enabled,
1404+
"banStatus": a.BanStatus,
1405+
"banReason": a.BanReason,
1406+
"banTime": a.BanTime,
14041407
"expiresAt": a.ExpiresAt,
14051408
"hasToken": a.AccessToken != "",
14061409
"machineId": a.MachineId,
@@ -1993,14 +1996,36 @@ func (h *Handler) apiRefreshAccount(w http.ResponseWriter, r *http.Request, id s
19931996
// 获取账户信息
19941997
info, err := RefreshAccountInfo(account)
19951998
if err != nil {
1996-
// 如果是 403/401,说明 token 无效,尝试刷新后重试
1999+
// 检查是否为封禁相关错误
19972000
errMsg := err.Error()
2001+
if strings.Contains(errMsg, "TEMPORARILY_SUSPENDED") || strings.Contains(errMsg, "Account suspended") {
2002+
// 封禁状态已在 RefreshAccountInfo 中处理,静默返回成功
2003+
json.NewEncoder(w).Encode(map[string]interface{}{
2004+
"success": true,
2005+
"message": "Account status updated",
2006+
})
2007+
return
2008+
}
2009+
2010+
// 如果是 403/401,说明 token 无效,尝试刷新后重试
19982011
if strings.Contains(errMsg, "403") || strings.Contains(errMsg, "401") || strings.Contains(errMsg, "invalid") || strings.Contains(errMsg, "expired") {
19992012
if refreshErr := refreshTokenIfNeeded(); refreshErr == nil {
20002013
// 重试
20012014
info, err = RefreshAccountInfo(account)
2015+
if err != nil {
2016+
// 重试后仍然失败,检查是否为封禁状态
2017+
if strings.Contains(err.Error(), "TEMPORARILY_SUSPENDED") || strings.Contains(err.Error(), "Account suspended") {
2018+
json.NewEncoder(w).Encode(map[string]interface{}{
2019+
"success": true,
2020+
"message": "Account status updated",
2021+
})
2022+
return
2023+
}
2024+
}
20022025
}
20032026
}
2027+
2028+
// 其他错误才显示错误信息
20042029
if err != nil {
20052030
w.WriteHeader(500)
20062031
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})

proxy/kiro_api.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,61 @@ func RefreshAccountInfo(account *config.Account) (*config.AccountInfo, error) {
136136
// 获取使用量和订阅信息
137137
usage, err := GetUsageLimits(account)
138138
if err != nil {
139+
// 检测封禁状态
140+
errMsg := err.Error()
141+
if strings.Contains(errMsg, "TEMPORARILY_SUSPENDED") {
142+
// 账户被暂时封禁,自动禁用并标记封禁状态
143+
fmt.Printf("[RefreshAccountInfo] Account %s is temporarily suspended: %v\n", account.Email, err)
144+
145+
// 更新账户封禁状态并自动禁用
146+
updatedAccount := *account
147+
updatedAccount.Enabled = false
148+
updatedAccount.BanStatus = "BANNED"
149+
updatedAccount.BanReason = "AWS temporarily suspended - unusual user activity detected"
150+
updatedAccount.BanTime = time.Now().Unix()
151+
152+
// 保存更新后的账户状态
153+
if updateErr := config.UpdateAccount(account.ID, updatedAccount); updateErr != nil {
154+
fmt.Printf("[RefreshAccountInfo] Failed to update account ban status: %v\n", updateErr)
155+
}
156+
157+
return nil, fmt.Errorf("Account suspended: %w", err)
158+
} else if strings.Contains(errMsg, "403") || strings.Contains(errMsg, "401") ||
159+
strings.Contains(errMsg, "invalid") || strings.Contains(errMsg, "expired") {
160+
// Token 相关错误,可能需要重新认证
161+
fmt.Printf("[RefreshAccountInfo] Authentication error for %s: %v\n", account.Email, err)
162+
163+
// 更新账户封禁状态为认证失败并自动禁用
164+
updatedAccount := *account
165+
updatedAccount.Enabled = false
166+
updatedAccount.BanStatus = "BANNED"
167+
updatedAccount.BanReason = "Authentication failed - token invalid or expired"
168+
updatedAccount.BanTime = time.Now().Unix()
169+
170+
// 保存更新后的账户状态
171+
if updateErr := config.UpdateAccount(account.ID, updatedAccount); updateErr != nil {
172+
fmt.Printf("[RefreshAccountInfo] Failed to update account ban status: %v\n", updateErr)
173+
}
174+
}
175+
139176
return nil, fmt.Errorf("GetUsageLimits: %w", err)
140177
}
141178

179+
// 如果成功获取信息,清除封禁状态(如果之前被标记)
180+
if account.BanStatus != "" && account.BanStatus != "ACTIVE" {
181+
fmt.Printf("[RefreshAccountInfo] Account %s is now active, clearing ban status\n", account.Email)
182+
183+
updatedAccount := *account
184+
updatedAccount.BanStatus = "ACTIVE"
185+
updatedAccount.BanReason = ""
186+
updatedAccount.BanTime = 0
187+
188+
// 保存更新后的账户状态
189+
if updateErr := config.UpdateAccount(account.ID, updatedAccount); updateErr != nil {
190+
fmt.Printf("[RefreshAccountInfo] Failed to clear account ban status: %v\n", updateErr)
191+
}
192+
}
193+
142194
// 解析用户信息
143195
if usage.UserInfo != nil {
144196
info.Email = usage.UserInfo.Email

web/index.html

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,21 @@
241241
color: white;
242242
}
243243

244+
.badge-trial {
245+
background: #10b981;
246+
color: white;
247+
}
248+
249+
.badge-banned {
250+
background: #dc2626;
251+
color: white;
252+
}
253+
254+
.badge-suspended {
255+
background: #f59e0b;
256+
color: white;
257+
}
258+
244259
.modal {
245260
display: none;
246261
position: fixed;
@@ -1062,6 +1077,9 @@ <h1 class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stro
10621077
'accounts.expired': '已过期',
10631078
'accounts.disabled': '已禁用',
10641079
'accounts.normal': '正常',
1080+
'accounts.enabled': '已启用',
1081+
'accounts.banned': '已封禁',
1082+
'accounts.suspended': '已暂停',
10651083
'accounts.refreshFailed': '刷新失败',
10661084
'accounts.confirmDelete': '确定删除?',
10671085
'accounts.mainQuota': '主配额',
@@ -1251,6 +1269,9 @@ <h1 class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stro
12511269
'accounts.expired': 'Expired',
12521270
'accounts.disabled': 'Disabled',
12531271
'accounts.normal': 'Active',
1272+
'accounts.enabled': 'Enabled',
1273+
'accounts.banned': 'Banned',
1274+
'accounts.suspended': 'Suspended',
12541275
'accounts.refreshFailed': 'Refresh failed',
12551276
'accounts.confirmDelete': 'Confirm delete?',
12561277
'time.expired': 'Expired',
@@ -1596,7 +1617,9 @@ <h1 class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stro
15961617
'<div class="account-actions">' +
15971618
'<button class="btn btn-sm btn-icon btn-secondary" onclick="refreshAccount(\'' + a.id + '\')" title="' + t('accounts.refresh') + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg></button>' +
15981619
'<button class="btn btn-sm btn-icon btn-secondary" onclick="showDetail(\'' + a.id + '\')" title="' + t('accounts.detail') + '"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>' +
1599-
'<button class="btn btn-sm ' + (a.enabled ? 'btn-secondary' : 'btn-primary') + '" onclick="toggleAccount(\'' + a.id + '\',' + !a.enabled + ')">' + (a.enabled ? t('accounts.disable') : t('accounts.enable')) + '</button>' +
1620+
// 封禁账户不显示启用/禁用按钮
1621+
(a.banStatus && a.banStatus !== 'ACTIVE' ? '' :
1622+
'<button class="btn btn-sm ' + (a.enabled ? 'btn-secondary' : 'btn-primary') + '" onclick="toggleAccount(\'' + a.id + '\',' + !a.enabled + ')">' + (a.enabled ? t('accounts.disable') : t('accounts.enable')) + '</button>') +
16001623
'<button class="btn btn-sm btn-danger" onclick="deleteAccount(\'' + a.id + '\')">' + t('accounts.delete') + '</button>' +
16011624
'</div>' +
16021625
'</div>' +
@@ -1644,10 +1667,42 @@ <h1 class="logo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stro
16441667
return method;
16451668
}
16461669
function getStatusBadge(a) {
1647-
if (!a.hasToken) return '<span class="badge badge-error">' + t('accounts.noToken') + '</span>';
1648-
if (a.expiresAt && a.expiresAt < Date.now() / 1000) return '<span class="badge badge-warning">' + t('accounts.expired') + '</span>';
1649-
if (!a.enabled) return '<span class="badge badge-warning">' + t('accounts.disabled') + '</span>';
1650-
return '<span class="badge badge-success">' + t('accounts.normal') + '</span>';
1670+
let badges = [];
1671+
1672+
// 检查是否为封禁状态
1673+
const isBanned = a.banStatus && a.banStatus !== 'ACTIVE';
1674+
1675+
if (isBanned) {
1676+
// 封禁账号:显示"封禁 + 禁用"
1677+
if (a.banStatus === 'BANNED') {
1678+
badges.push('<span class="badge badge-banned">' + t('accounts.banned') + '</span>');
1679+
} else if (a.banStatus === 'SUSPENDED') {
1680+
badges.push('<span class="badge badge-suspended">' + t('accounts.suspended') + '</span>');
1681+
}
1682+
// 封禁账号必定显示禁用状态
1683+
badges.push('<span class="badge badge-warning">' + t('accounts.disabled') + '</span>');
1684+
} else {
1685+
// 正常账号:显示"正常 + 启用/禁用"
1686+
1687+
// 检查Token状态
1688+
if (!a.hasToken) {
1689+
badges.push('<span class="badge badge-error">' + t('accounts.noToken') + '</span>');
1690+
} else if (a.expiresAt && a.expiresAt < Date.now() / 1000) {
1691+
badges.push('<span class="badge badge-warning">' + t('accounts.expired') + '</span>');
1692+
} else {
1693+
// 有效Token的正常账号显示"正常"
1694+
badges.push('<span class="badge badge-success">' + t('accounts.normal') + '</span>');
1695+
}
1696+
1697+
// 显示启用/禁用状态
1698+
if (a.enabled) {
1699+
badges.push('<span class="badge badge-info">' + t('accounts.enabled') + '</span>');
1700+
} else {
1701+
badges.push('<span class="badge badge-warning">' + t('accounts.disabled') + '</span>');
1702+
}
1703+
}
1704+
1705+
return badges.join('');
16511706
}
16521707
function formatTokenExpiry(ts) {
16531708
if (!ts) return '-';

0 commit comments

Comments
 (0)