mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-02 22:33:33 +00:00
sentinel expiry
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use log::{error, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -14,13 +15,9 @@ pub struct SoraSentinelToken {
|
||||
/// Clientside time when it was generated (client-side).
|
||||
pub generated_at: DateTime<Utc>,
|
||||
|
||||
/// Expiration time (reported by the server)
|
||||
/// Seconds of validity (reported by the server, or our best guess).
|
||||
/// If the server doesn't tell us, we'll fill it in with our best guess.
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
|
||||
/// Seconds of validity (reported by the server)
|
||||
/// If the server doesn't tell us, we'll fill it in with our best guess.
|
||||
pub expires_in_seconds: Option<u32>,
|
||||
pub expires_in_seconds: u32,
|
||||
}
|
||||
|
||||
/// This is what is sent over the wire in requests.
|
||||
@@ -50,22 +47,30 @@ pub struct RawSoraSentinelToken {
|
||||
}
|
||||
|
||||
impl SoraSentinelToken {
|
||||
/// Check to see if the sentinel token is expired and in need of a refresh.
|
||||
pub fn is_expired(&self) -> bool {
|
||||
// NB(1): We're not trusting the server timestamp for now.
|
||||
// NB(2): We're not self-expiring for now.
|
||||
let now = Utc::now();
|
||||
|
||||
if let Some(expires_in_seconds) = self.expires_in_seconds {
|
||||
let maybe_expires_at = now
|
||||
.checked_add_signed(TimeDelta::seconds(expires_in_seconds as i64));
|
||||
let maybe_expires_at = self.generated_at
|
||||
.checked_add_signed(TimeDelta::seconds(self.expires_in_seconds as i64));
|
||||
|
||||
if let Some(expires_at) = maybe_expires_at {
|
||||
if now >= expires_at {
|
||||
return true;
|
||||
}
|
||||
let expires_at = match maybe_expires_at {
|
||||
Some(expires_at) => expires_at,
|
||||
None => {
|
||||
error!("Could not compute expires_at for sentinel token!");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if now >= expires_at {
|
||||
return true;
|
||||
}
|
||||
|
||||
if expires_at.signed_duration_since(now).num_seconds() < 30 {
|
||||
warn!("Less than 30 seconds remaining on sentinel token, treating as expired.");
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::requests::auth_sentinel_2::response::SentinelResponse;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use log::{info, warn};
|
||||
use std::f32::consts::E;
|
||||
|
||||
// Sora's servers typically tell us to expire in nine minutes (540 seconds).
|
||||
const DEFAULT_EXPIRE_SECONDS : u32 = 60 * 3; // 3 minutes.
|
||||
|
||||
pub (super) struct ExtractedExpiry {
|
||||
pub (super) generated_at: DateTime<Utc>,
|
||||
pub (super) expires_in_seconds: u32,
|
||||
}
|
||||
|
||||
pub (super) fn extract_expiry(generated_at: DateTime<Utc>, response: &SentinelResponse) -> ExtractedExpiry {
|
||||
info!("Sentinel expires at: {:?} (in {:?} seconds)", response.expire_at, response.expire_after);
|
||||
|
||||
const LOWER_BOUNDS_SECONDS: u32 = 60;
|
||||
const UPPER_BOUNDS_SECONDS: u32 = 60 * 60;
|
||||
|
||||
// Trust the `expire_at` timestamp first.
|
||||
let mut maybe_expires_at = response.expire_at
|
||||
.map(|timestamp| DateTime::from_timestamp(timestamp as i64, 0))
|
||||
.flatten();
|
||||
|
||||
if let Some(expires_at) = maybe_expires_at {
|
||||
let seconds = expires_at.signed_duration_since(generated_at).num_seconds();
|
||||
|
||||
const LOWER_BOUNDS_SECONDS_I64: i64 = LOWER_BOUNDS_SECONDS as i64;
|
||||
const UPPER_BOUNDS_SECONDS_I64: i64 = UPPER_BOUNDS_SECONDS as i64;
|
||||
|
||||
if seconds.is_positive()
|
||||
&& seconds > LOWER_BOUNDS_SECONDS_I64
|
||||
&& seconds < UPPER_BOUNDS_SECONDS_I64
|
||||
{
|
||||
info!("Using sentinel expire_at timestamp, expires in {} seconds (at {})", seconds, expires_at);
|
||||
|
||||
return ExtractedExpiry {
|
||||
generated_at,
|
||||
expires_in_seconds: seconds as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut expires_in_seconds = response.expire_after
|
||||
.unwrap_or_else(|| {
|
||||
warn!("No default expire_after from sentinel response, defaulting to {} seconds", DEFAULT_EXPIRE_SECONDS);
|
||||
DEFAULT_EXPIRE_SECONDS
|
||||
});
|
||||
|
||||
if expires_in_seconds < LOWER_BOUNDS_SECONDS {
|
||||
warn!("Warning: sentinel token expires very soon: {} seconds, defaulting to {} seconds", expires_in_seconds, DEFAULT_EXPIRE_SECONDS);
|
||||
expires_in_seconds = DEFAULT_EXPIRE_SECONDS;
|
||||
} else if expires_in_seconds > UPPER_BOUNDS_SECONDS {
|
||||
warn!("Warning: sentinel token expires very late: {} seconds, defaulting to {} seconds", expires_in_seconds, DEFAULT_EXPIRE_SECONDS);
|
||||
expires_in_seconds = DEFAULT_EXPIRE_SECONDS;
|
||||
}
|
||||
|
||||
ExtractedExpiry {
|
||||
generated_at,
|
||||
expires_in_seconds,
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -4,12 +4,14 @@ use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::error::sora_specific_api_error::SoraSpecificApiError;
|
||||
use crate::requests::auth_sentinel::request::GenerateSentinelRefreshRequest;
|
||||
use crate::requests::auth_sentinel_2::extract_expiry::extract_expiry;
|
||||
use crate::requests::auth_sentinel_2::request::SentinelRequest;
|
||||
use crate::requests::auth_sentinel_2::response::SentinelResponse;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use chrono::{DateTime, NaiveDateTime, TimeDelta, TimeZone, Utc};
|
||||
use errors::AnyhowResult;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{debug, error};
|
||||
use log::{debug, error, info};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
use thiserror::Error;
|
||||
@@ -19,7 +21,6 @@ use wreq::Client;
|
||||
const SORA_SENTINEL_ENDPOINT: &str = "https://chatgpt.com/backend-api/sentinel/req";
|
||||
|
||||
|
||||
|
||||
/// This generates a Sentinel Token that works with Sora 1.0 and Sora 2.0 consumer products.
|
||||
pub async fn generate_sentinel_token_2() -> Result<SoraSentinelToken, SoraError> {
|
||||
let (_request, base64_request) = GenerateSentinelRefreshRequest::new().with_fourth_and_tenth();
|
||||
@@ -77,9 +78,9 @@ pub async fn generate_sentinel_token_2() -> Result<SoraSentinelToken, SoraError>
|
||||
Ok(sentinel_token)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn into_sora_sentinel_token(request: SentinelRequest, response: SentinelResponse, raw_body: &str) -> Result<SoraSentinelToken, SoraSpecificApiError> {
|
||||
let expiry = extract_expiry(Utc::now(), &response);
|
||||
|
||||
let maybe_token = response.token;
|
||||
|
||||
let maybe_turnstile = response.turnstile
|
||||
@@ -126,12 +127,12 @@ fn into_sora_sentinel_token(request: SentinelRequest, response: SentinelResponse
|
||||
c: token,
|
||||
};
|
||||
|
||||
|
||||
let sentinel = SoraSentinelToken {
|
||||
token: sentinel_raw,
|
||||
browser_user_agent: CLIENT_USER_AGENT.to_string(),
|
||||
generated_at: Default::default(), // TODO
|
||||
expires_at: None, // TODO
|
||||
expires_in_seconds: None, // TODO
|
||||
generated_at: expiry.generated_at,
|
||||
expires_in_seconds: expiry.expires_in_seconds,
|
||||
};
|
||||
|
||||
Ok(sentinel)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub (super) mod extract_expiry;
|
||||
pub (super) mod request;
|
||||
pub (super) mod response;
|
||||
pub mod generate_sentinel_token_2;
|
||||
@@ -1,10 +1,18 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub (crate) struct SentinelResponse {
|
||||
pub (crate) token: Option<String>,
|
||||
pub (crate) turnstile: Option<SentinelTurnstile>,
|
||||
|
||||
|
||||
// NB: appears to be integer seconds.
|
||||
// Not required for the sentinel token flow, but helps with renew.
|
||||
pub (crate) expire_after: Option<u32>,
|
||||
|
||||
// NB: appears to be Unix timestamp in seconds.
|
||||
// Not required for the sentinel token flow, but helps with renew.
|
||||
pub (crate) expire_at: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user