2026-04-28 15:46:21 +08:00
|
|
|
use crate::{AppState, GatewayError};
|
|
|
|
|
use aether_oauth::core::{current_unix_secs, generate_oauth_nonce};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
|
|
const IDENTITY_OAUTH_STATE_TTL_SECS: u64 = 10 * 60;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
|
pub(crate) enum IdentityOAuthStateMode {
|
|
|
|
|
Login,
|
|
|
|
|
Bind,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
pub(crate) struct StoredIdentityOAuthState {
|
|
|
|
|
pub(crate) nonce: String,
|
|
|
|
|
pub(crate) provider_type: String,
|
|
|
|
|
pub(crate) mode: IdentityOAuthStateMode,
|
|
|
|
|
pub(crate) client_device_id: String,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub(crate) pkce_verifier: Option<String>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub(crate) bind_user_id: Option<String>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub(crate) bind_session_id: Option<String>,
|
|
|
|
|
pub(crate) created_at: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl StoredIdentityOAuthState {
|
|
|
|
|
pub(crate) fn login(
|
|
|
|
|
provider_type: impl Into<String>,
|
|
|
|
|
client_device_id: impl Into<String>,
|
|
|
|
|
pkce_verifier: Option<String>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
nonce: generate_oauth_nonce(),
|
|
|
|
|
provider_type: provider_type.into(),
|
|
|
|
|
mode: IdentityOAuthStateMode::Login,
|
|
|
|
|
client_device_id: client_device_id.into(),
|
|
|
|
|
pkce_verifier,
|
|
|
|
|
bind_user_id: None,
|
|
|
|
|
bind_session_id: None,
|
|
|
|
|
created_at: current_unix_secs(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn bind(
|
|
|
|
|
provider_type: impl Into<String>,
|
|
|
|
|
client_device_id: impl Into<String>,
|
|
|
|
|
pkce_verifier: Option<String>,
|
|
|
|
|
user_id: impl Into<String>,
|
|
|
|
|
session_id: impl Into<String>,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
nonce: generate_oauth_nonce(),
|
|
|
|
|
provider_type: provider_type.into(),
|
|
|
|
|
mode: IdentityOAuthStateMode::Bind,
|
|
|
|
|
client_device_id: client_device_id.into(),
|
|
|
|
|
pkce_verifier,
|
|
|
|
|
bind_user_id: Some(user_id.into()),
|
|
|
|
|
bind_session_id: Some(session_id.into()),
|
|
|
|
|
created_at: current_unix_secs(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn identity_oauth_state_storage_key(nonce: &str) -> String {
|
|
|
|
|
format!("identity_oauth_state:{}", nonce.trim())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) async fn save_identity_oauth_state(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
record: &StoredIdentityOAuthState,
|
|
|
|
|
) -> Result<(), GatewayError> {
|
|
|
|
|
let key = identity_oauth_state_storage_key(&record.nonce);
|
|
|
|
|
let value =
|
|
|
|
|
serde_json::to_string(record).map_err(|err| GatewayError::Internal(err.to_string()))?;
|
2026-05-08 00:18:12 +08:00
|
|
|
state
|
|
|
|
|
.runtime_kv_setex(&key, &value, IDENTITY_OAUTH_STATE_TTL_SECS)
|
|
|
|
|
.await
|
2026-04-28 15:46:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) async fn consume_identity_oauth_state(
|
|
|
|
|
state: &AppState,
|
|
|
|
|
nonce: &str,
|
|
|
|
|
) -> Result<Option<StoredIdentityOAuthState>, GatewayError> {
|
|
|
|
|
let key = identity_oauth_state_storage_key(nonce);
|
2026-05-08 00:18:12 +08:00
|
|
|
let raw = state.runtime_kv_getdel(&key).await?;
|
2026-04-28 15:46:21 +08:00
|
|
|
raw.map(|value| {
|
|
|
|
|
serde_json::from_str::<StoredIdentityOAuthState>(&value)
|
|
|
|
|
.map_err(|err| GatewayError::Internal(err.to_string()))
|
|
|
|
|
})
|
|
|
|
|
.transpose()
|
|
|
|
|
}
|