Skip to content

Commit 5b032f4

Browse files
committed
fix(accounts): accept numeric sub2api expiry values
1 parent 44f3358 commit 5b032f4

3 files changed

Lines changed: 97 additions & 23 deletions

File tree

admin/handler.go

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,17 +1063,17 @@ type importToken struct {
10631063

10641064
// jsonAccountEntry CLIProxyAPI 凭证 JSON 条目
10651065
type jsonAccountEntry struct {
1066-
RefreshToken string `json:"refresh_token"`
1067-
SessionToken string `json:"session_token"`
1068-
SessionTokenCamel string `json:"sessionToken"`
1069-
AccessToken string `json:"access_token"`
1070-
IDToken string `json:"id_token"`
1071-
AccountID string `json:"account_id"`
1072-
Email string `json:"email"`
1073-
Name string `json:"name"`
1074-
PlanType string `json:"plan_type"`
1075-
Expired string `json:"expired"`
1076-
ExpiresAt string `json:"expires_at"`
1066+
RefreshToken string `json:"refresh_token"`
1067+
SessionToken string `json:"session_token"`
1068+
SessionTokenCamel string `json:"sessionToken"`
1069+
AccessToken string `json:"access_token"`
1070+
IDToken string `json:"id_token"`
1071+
AccountID string `json:"account_id"`
1072+
Email string `json:"email"`
1073+
Name string `json:"name"`
1074+
PlanType string `json:"plan_type"`
1075+
Expired importJSONScalarString `json:"expired"`
1076+
ExpiresAt importJSONScalarString `json:"expires_at"`
10771077
}
10781078

10791079
type sub2apiImportPayload struct {
@@ -1086,16 +1086,45 @@ type sub2apiAccountEntry struct {
10861086
}
10871087

10881088
type sub2apiAccountCredentials struct {
1089-
RefreshToken string `json:"refresh_token"`
1090-
SessionToken string `json:"session_token"`
1091-
SessionTokenCamel string `json:"sessionToken"`
1092-
AccessToken string `json:"access_token"`
1093-
IDToken string `json:"id_token"`
1094-
AccountID string `json:"account_id"`
1095-
Email string `json:"email"`
1096-
PlanType string `json:"plan_type"`
1097-
ExpiresAt string `json:"expires_at"`
1098-
Expired string `json:"expired"`
1089+
RefreshToken string `json:"refresh_token"`
1090+
SessionToken string `json:"session_token"`
1091+
SessionTokenCamel string `json:"sessionToken"`
1092+
AccessToken string `json:"access_token"`
1093+
IDToken string `json:"id_token"`
1094+
AccountID string `json:"account_id"`
1095+
Email string `json:"email"`
1096+
PlanType string `json:"plan_type"`
1097+
ExpiresAt importJSONScalarString `json:"expires_at"`
1098+
Expired importJSONScalarString `json:"expired"`
1099+
}
1100+
1101+
type importJSONScalarString string
1102+
1103+
func (v *importJSONScalarString) UnmarshalJSON(data []byte) error {
1104+
decoder := json.NewDecoder(bytes.NewReader(data))
1105+
decoder.UseNumber()
1106+
1107+
var raw interface{}
1108+
if err := decoder.Decode(&raw); err != nil {
1109+
return err
1110+
}
1111+
1112+
switch value := raw.(type) {
1113+
case string:
1114+
*v = importJSONScalarString(strings.TrimSpace(value))
1115+
case json.Number:
1116+
*v = importJSONScalarString(value.String())
1117+
case bool:
1118+
*v = importJSONScalarString(strconv.FormatBool(value))
1119+
default:
1120+
*v = ""
1121+
}
1122+
1123+
return nil
1124+
}
1125+
1126+
func (v importJSONScalarString) String() string {
1127+
return strings.TrimSpace(string(v))
10991128
}
11001129

11011130
var utf8BOM = []byte{0xef, 0xbb, 0xbf}
@@ -1155,7 +1184,7 @@ func jsonAccountEntriesToTokens(entries []jsonAccountEntry) []importToken {
11551184
idToken: strings.TrimSpace(entry.IDToken),
11561185
accountID: strings.TrimSpace(entry.AccountID),
11571186
planType: strings.TrimSpace(entry.PlanType),
1158-
expiresAt: firstNonEmpty(entry.ExpiresAt, entry.Expired),
1187+
expiresAt: firstNonEmpty(entry.ExpiresAt.String(), entry.Expired.String()),
11591188
})
11601189
}
11611190
}
@@ -1190,7 +1219,7 @@ func parseSub2APIJSONImportTokens(data []byte) []importToken {
11901219
idToken: strings.TrimSpace(account.Credentials.IDToken),
11911220
accountID: strings.TrimSpace(account.Credentials.AccountID),
11921221
planType: strings.TrimSpace(account.Credentials.PlanType),
1193-
expiresAt: firstNonEmpty(account.Credentials.ExpiresAt, account.Credentials.Expired),
1222+
expiresAt: firstNonEmpty(account.Credentials.ExpiresAt.String(), account.Credentials.Expired.String()),
11941223
})
11951224
}
11961225
}

admin/import_json_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"testing"
10+
"time"
1011

1112
"github.com/gin-gonic/gin"
1213
)
@@ -119,6 +120,41 @@ func TestParseImportJSONTokensSupportsSub2API(t *testing.T) {
119120
}
120121
}
121122

123+
func TestParseImportJSONTokensSupportsSub2APINumericExpiresAt(t *testing.T) {
124+
data := []byte(`{
125+
"accounts": [
126+
{
127+
"name": "Numeric Expiry",
128+
"credentials": {
129+
"refresh_token": "rt-numeric",
130+
"access_token": "at-numeric",
131+
"expires_at": 1779071020
132+
}
133+
}
134+
]
135+
}`)
136+
137+
tokens, err := parseImportJSONTokens(data)
138+
if err != nil {
139+
t.Fatalf("parseImportJSONTokens returned error: %v", err)
140+
}
141+
142+
if len(tokens) != 1 {
143+
t.Fatalf("tokens len = %d, want 1", len(tokens))
144+
}
145+
if tokens[0].expiresAt != "1779071020" {
146+
t.Fatalf("expiresAt = %q, want numeric value preserved", tokens[0].expiresAt)
147+
}
148+
}
149+
150+
func TestParseCredentialExpiresAtSupportsUnixSeconds(t *testing.T) {
151+
got := parseCredentialExpiresAt("1779071020").UTC()
152+
want := time.Unix(1779071020, 0).UTC()
153+
if !got.Equal(want) {
154+
t.Fatalf("parseCredentialExpiresAt = %s, want %s", got, want)
155+
}
156+
}
157+
122158
func TestParseImportJSONTokensPreservesCPAFields(t *testing.T) {
123159
data := []byte(`{
124160
"type": "codex",

admin/token_credentials.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package admin
22

33
import (
4+
"strconv"
45
"strings"
56
"time"
67

@@ -130,6 +131,14 @@ func parseCredentialExpiresAt(raw string) time.Time {
130131
if raw == "" {
131132
return time.Time{}
132133
}
134+
135+
if unixSeconds, err := strconv.ParseFloat(raw, 64); err == nil && unixSeconds > 0 {
136+
if unixSeconds >= 1e12 {
137+
return time.UnixMilli(int64(unixSeconds))
138+
}
139+
return time.Unix(int64(unixSeconds), 0)
140+
}
141+
133142
for _, layout := range []string{
134143
time.RFC3339Nano,
135144
time.RFC3339,

0 commit comments

Comments
 (0)