mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 03:39:49 +08:00
Merge commit 'refs/pull/563/head' of github-fawney19:fawney19/Aether into review/pr-562
This commit is contained in:
@@ -76,6 +76,26 @@ fn coerce_admin_provider_oauth_import_str(value: Option<&serde_json::Value>) ->
|
|||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn json_import_expiry_value(value: Option<&serde_json::Value>) -> Option<u64> {
|
||||||
|
let value = value?;
|
||||||
|
json_u64_value(Some(value)).or_else(|| {
|
||||||
|
value
|
||||||
|
.as_str()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.and_then(|value| chrono::DateTime::parse_from_rfc3339(value).ok())
|
||||||
|
.and_then(|value| u64::try_from(value.timestamp()).ok())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_import_expiry_from_keys(
|
||||||
|
object: &serde_json::Map<String, serde_json::Value>,
|
||||||
|
keys: &[&str],
|
||||||
|
) -> Option<u64> {
|
||||||
|
keys.iter()
|
||||||
|
.find_map(|key| json_import_expiry_value(object.get(*key)))
|
||||||
|
}
|
||||||
|
|
||||||
fn grok_cookie_value(raw: &str, name: &str) -> Option<String> {
|
fn grok_cookie_value(raw: &str, name: &str) -> Option<String> {
|
||||||
raw.trim()
|
raw.trim()
|
||||||
.strip_prefix("Cookie:")
|
.strip_prefix("Cookie:")
|
||||||
@@ -188,6 +208,8 @@ fn extract_admin_provider_oauth_batch_import_entry(
|
|||||||
object
|
object
|
||||||
.get("sso_token")
|
.get("sso_token")
|
||||||
.or_else(|| object.get("ssoToken"))
|
.or_else(|| object.get("ssoToken"))
|
||||||
|
.or_else(|| object.get("session_token"))
|
||||||
|
.or_else(|| object.get("sessionToken"))
|
||||||
.or(grok_token_alias),
|
.or(grok_token_alias),
|
||||||
)
|
)
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
@@ -238,7 +260,7 @@ fn extract_admin_provider_oauth_batch_import_entry(
|
|||||||
refresh_token
|
refresh_token
|
||||||
};
|
};
|
||||||
let expires_at =
|
let expires_at =
|
||||||
json_u64_value(object.get("expires_at").or_else(|| object.get("expiresAt")));
|
json_import_expiry_from_keys(object, &["expires_at", "expiresAt", "expired"]);
|
||||||
let account_id = coerce_admin_provider_oauth_import_str(
|
let account_id = coerce_admin_provider_oauth_import_str(
|
||||||
object
|
object
|
||||||
.get("account_id")
|
.get("account_id")
|
||||||
@@ -619,6 +641,21 @@ mod tests {
|
|||||||
assert_eq!(entries[0].email.as_deref(), Some("[email protected]"));
|
assert_eq!(entries[0].email.as_deref(), Some("[email protected]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_common_chatgpt_web_json_aliases() {
|
||||||
|
let entries = parse_admin_provider_oauth_batch_import_entries(
|
||||||
|
"chatgpt_web",
|
||||||
|
r#"[{"session_token":"session-1","expired":"2030-01-01T00:00:00Z","chatgpt_account_id":"acc-1","chatgpt_plan_type":"plus"}]"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(entries.len(), 1);
|
||||||
|
assert_eq!(entries[0].refresh_token, None);
|
||||||
|
assert_eq!(entries[0].access_token.as_deref(), Some("session-1"));
|
||||||
|
assert_eq!(entries[0].expires_at, Some(1_893_456_000));
|
||||||
|
assert_eq!(entries[0].account_id.as_deref(), Some("acc-1"));
|
||||||
|
assert_eq!(entries[0].plan_type.as_deref(), Some("plus"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_plain_jwt_line_as_access_token() {
|
fn parses_plain_jwt_line_as_access_token() {
|
||||||
let token = unsigned_jwt(json!({
|
let token = unsigned_jwt(json!({
|
||||||
|
|||||||
@@ -106,12 +106,21 @@ fn import_payload_string_any(
|
|||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_payload_u64(
|
fn import_payload_u64_any(
|
||||||
payload: &serde_json::Map<String, serde_json::Value>,
|
payload: &serde_json::Map<String, serde_json::Value>,
|
||||||
snake_case: &str,
|
keys: &[&str],
|
||||||
camel_case: &str,
|
|
||||||
) -> Option<u64> {
|
) -> Option<u64> {
|
||||||
json_u64_value(payload.get(snake_case).or_else(|| payload.get(camel_case)))
|
keys.iter().find_map(|key| {
|
||||||
|
let value = payload.get(*key)?;
|
||||||
|
json_u64_value(Some(value)).or_else(|| {
|
||||||
|
value
|
||||||
|
.as_str()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.and_then(|value| chrono::DateTime::parse_from_rfc3339(value).ok())
|
||||||
|
.and_then(|value| u64::try_from(value.timestamp()).ok())
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_single_import_hints(
|
fn apply_single_import_hints(
|
||||||
@@ -409,9 +418,17 @@ pub(super) async fn handle_admin_provider_oauth_import_refresh_token(
|
|||||||
let refresh_token_input = import_payload_string(&raw_payload, "refresh_token", "refreshToken");
|
let refresh_token_input = import_payload_string(&raw_payload, "refresh_token", "refreshToken");
|
||||||
let access_token_input = import_payload_string_any(
|
let access_token_input = import_payload_string_any(
|
||||||
&raw_payload,
|
&raw_payload,
|
||||||
&["access_token", "accessToken", "sso_token", "ssoToken"],
|
&[
|
||||||
|
"access_token",
|
||||||
|
"accessToken",
|
||||||
|
"sso_token",
|
||||||
|
"ssoToken",
|
||||||
|
"session_token",
|
||||||
|
"sessionToken",
|
||||||
|
],
|
||||||
);
|
);
|
||||||
let imported_expires_at = import_payload_u64(&raw_payload, "expires_at", "expiresAt");
|
let imported_expires_at =
|
||||||
|
import_payload_u64_any(&raw_payload, &["expires_at", "expiresAt", "expired"]);
|
||||||
let name = raw_payload
|
let name = raw_payload
|
||||||
.get("name")
|
.get("name")
|
||||||
.and_then(serde_json::Value::as_str)
|
.and_then(serde_json::Value::as_str)
|
||||||
@@ -622,8 +639,39 @@ pub(super) async fn handle_admin_provider_oauth_import_refresh_token(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::sanitize_windsurf_import_error;
|
use super::{import_payload_string_any, import_payload_u64_any, sanitize_windsurf_import_error};
|
||||||
use aether_oauth::core::OAuthError;
|
use aether_oauth::core::OAuthError;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_import_accepts_session_token_alias() {
|
||||||
|
let payload = json!({
|
||||||
|
"session_token": "session-1",
|
||||||
|
})
|
||||||
|
.as_object()
|
||||||
|
.cloned()
|
||||||
|
.expect("payload should be an object");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
import_payload_string_any(&payload, &["access_token", "session_token"]).as_deref(),
|
||||||
|
Some("session-1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_import_accepts_iso_expired_alias() {
|
||||||
|
let payload = json!({
|
||||||
|
"expired": "2030-01-01T00:00:00Z",
|
||||||
|
})
|
||||||
|
.as_object()
|
||||||
|
.cloned()
|
||||||
|
.expect("payload should be an object");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
import_payload_u64_any(&payload, &["expires_at", "expiresAt", "expired"]),
|
||||||
|
Some(1_893_456_000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn windsurf_import_error_redacts_http_body() {
|
fn windsurf_import_error_redacts_http_body() {
|
||||||
|
|||||||
@@ -1408,6 +1408,8 @@ function parseImportText(text: string): {
|
|||||||
const refreshTokenCamel = obj.refreshToken
|
const refreshTokenCamel = obj.refreshToken
|
||||||
const accessToken = obj.access_token
|
const accessToken = obj.access_token
|
||||||
const accessTokenCamel = obj.accessToken
|
const accessTokenCamel = obj.accessToken
|
||||||
|
const sessionToken = obj.session_token
|
||||||
|
const sessionTokenCamel = obj.sessionToken
|
||||||
const grokSsoToken = isGrokProvider.value
|
const grokSsoToken = isGrokProvider.value
|
||||||
? normalizeStringField(obj.sso_token) ?? normalizeStringField(obj.ssoToken) ?? normalizeStringField(obj.token) ?? grokCookieImport?.access_token
|
? normalizeStringField(obj.sso_token) ?? normalizeStringField(obj.ssoToken) ?? normalizeStringField(obj.token) ?? grokCookieImport?.access_token
|
||||||
: undefined
|
: undefined
|
||||||
@@ -1417,12 +1419,15 @@ function parseImportText(text: string): {
|
|||||||
const normalizedAccessToken = typeof accessToken === 'string' && accessToken.trim()
|
const normalizedAccessToken = typeof accessToken === 'string' && accessToken.trim()
|
||||||
? accessToken.trim()
|
? accessToken.trim()
|
||||||
: (typeof accessTokenCamel === 'string' && accessTokenCamel.trim() ? accessTokenCamel.trim() : undefined)
|
: (typeof accessTokenCamel === 'string' && accessTokenCamel.trim() ? accessTokenCamel.trim() : undefined)
|
||||||
const importedAccessToken = normalizedAccessToken ?? grokSsoToken
|
const normalizedSessionToken = typeof sessionToken === 'string' && sessionToken.trim()
|
||||||
|
? sessionToken.trim()
|
||||||
|
: (typeof sessionTokenCamel === 'string' && sessionTokenCamel.trim() ? sessionTokenCamel.trim() : undefined)
|
||||||
|
const importedAccessToken = normalizedAccessToken ?? grokSsoToken ?? normalizedSessionToken
|
||||||
if (normalizedRefreshToken || importedAccessToken) {
|
if (normalizedRefreshToken || importedAccessToken) {
|
||||||
return {
|
return {
|
||||||
refresh_token: normalizedRefreshToken,
|
refresh_token: normalizedRefreshToken,
|
||||||
access_token: importedAccessToken,
|
access_token: importedAccessToken,
|
||||||
expires_at: normalizeNumberField(obj.expires_at) ?? normalizeNumberField(obj.expiresAt),
|
expires_at: normalizeExpiryField(obj.expires_at) ?? normalizeExpiryField(obj.expiresAt) ?? normalizeExpiryField(obj.expired),
|
||||||
name: (typeof obj.name === 'string' ? obj.name : undefined) || (typeof obj.oauth_email === 'string' ? obj.oauth_email : undefined),
|
name: (typeof obj.name === 'string' ? obj.name : undefined) || (typeof obj.oauth_email === 'string' ? obj.oauth_email : undefined),
|
||||||
email: normalizeStringField(obj.email) ?? normalizeStringField(obj.oauth_email),
|
email: normalizeStringField(obj.email) ?? normalizeStringField(obj.oauth_email),
|
||||||
account_id: normalizeStringField(obj.account_id) ?? normalizeStringField(obj.accountId) ?? normalizeStringField(obj.chatgpt_account_id) ?? normalizeStringField(obj.chatgptAccountId),
|
account_id: normalizeStringField(obj.account_id) ?? normalizeStringField(obj.accountId) ?? normalizeStringField(obj.chatgpt_account_id) ?? normalizeStringField(obj.chatgptAccountId),
|
||||||
@@ -1534,6 +1539,18 @@ function normalizeNumberField(value: unknown): number | undefined {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeExpiryField(value: unknown): number | undefined {
|
||||||
|
const numeric = normalizeNumberField(value)
|
||||||
|
if (numeric) return numeric
|
||||||
|
if (typeof value === 'string' && value.trim()) {
|
||||||
|
const parsed = Date.parse(value.trim())
|
||||||
|
if (Number.isFinite(parsed) && parsed > 0) {
|
||||||
|
return Math.floor(parsed / 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
function isLikelyJwtToken(token: string): boolean {
|
function isLikelyJwtToken(token: string): boolean {
|
||||||
const parts = token.trim().split('.')
|
const parts = token.trim().split('.')
|
||||||
if (parts.length !== 3 || parts.some(part => !part)) return false
|
if (parts.length !== 3 || parts.some(part => !part)) return false
|
||||||
|
|||||||
Reference in New Issue
Block a user