[App-server] Add account/login/cancel v2 endpoint (#6288)
Add `account/login/cancel` v2 endpoint for auth. this is similar implementation to `cancelLoginChatgpt` v1 endpoint.
This commit is contained in:
@@ -148,6 +148,13 @@ client_request_definitions! {
|
|||||||
response: v2::LoginAccountResponse,
|
response: v2::LoginAccountResponse,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[serde(rename = "account/login/cancel")]
|
||||||
|
#[ts(rename = "account/login/cancel")]
|
||||||
|
CancelLoginAccount {
|
||||||
|
params: v2::CancelLoginAccountParams,
|
||||||
|
response: v2::CancelLoginAccountResponse,
|
||||||
|
},
|
||||||
|
|
||||||
#[serde(rename = "account/logout")]
|
#[serde(rename = "account/logout")]
|
||||||
#[ts(rename = "account/logout")]
|
#[ts(rename = "account/logout")]
|
||||||
LogoutAccount {
|
LogoutAccount {
|
||||||
@@ -235,6 +242,7 @@ client_request_definitions! {
|
|||||||
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
|
||||||
response: v1::LoginChatGptResponse,
|
response: v1::LoginChatGptResponse,
|
||||||
},
|
},
|
||||||
|
// DEPRECATED in favor of CancelLoginAccount
|
||||||
CancelLoginChatGpt {
|
CancelLoginChatGpt {
|
||||||
params: v1::CancelLoginChatGptParams,
|
params: v1::CancelLoginChatGptParams,
|
||||||
response: v1::CancelLoginChatGptResponse,
|
response: v1::CancelLoginChatGptResponse,
|
||||||
|
|||||||
@@ -374,11 +374,9 @@ pub enum InputItem {
|
|||||||
LocalImage { path: PathBuf },
|
LocalImage { path: PathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated in favor of AccountLoginCompletedNotification.
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
/// Deprecated: use `account/login/completed` instead.
|
/// Deprecated in favor of AccountLoginCompletedNotification.
|
||||||
pub struct LoginChatGptCompleteNotification {
|
pub struct LoginChatGptCompleteNotification {
|
||||||
#[schemars(with = "String")]
|
#[schemars(with = "String")]
|
||||||
pub login_id: Uuid,
|
pub login_id: Uuid,
|
||||||
|
|||||||
@@ -167,6 +167,18 @@ pub enum LoginAccountResponse {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct CancelLoginAccountParams {
|
||||||
|
pub login_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export_to = "v2/")]
|
||||||
|
pub struct CancelLoginAccountResponse {}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export_to = "v2/")]
|
#[ts(export_to = "v2/")]
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ use codex_app_server_protocol::ArchiveConversationParams;
|
|||||||
use codex_app_server_protocol::ArchiveConversationResponse;
|
use codex_app_server_protocol::ArchiveConversationResponse;
|
||||||
use codex_app_server_protocol::AuthMode;
|
use codex_app_server_protocol::AuthMode;
|
||||||
use codex_app_server_protocol::AuthStatusChangeNotification;
|
use codex_app_server_protocol::AuthStatusChangeNotification;
|
||||||
|
use codex_app_server_protocol::CancelLoginAccountParams;
|
||||||
|
use codex_app_server_protocol::CancelLoginAccountResponse;
|
||||||
|
use codex_app_server_protocol::CancelLoginChatGptResponse;
|
||||||
use codex_app_server_protocol::ClientRequest;
|
use codex_app_server_protocol::ClientRequest;
|
||||||
use codex_app_server_protocol::ConversationSummary;
|
use codex_app_server_protocol::ConversationSummary;
|
||||||
use codex_app_server_protocol::ExecCommandApprovalParams;
|
use codex_app_server_protocol::ExecCommandApprovalParams;
|
||||||
@@ -233,6 +236,9 @@ impl CodexMessageProcessor {
|
|||||||
} => {
|
} => {
|
||||||
self.logout_v2(request_id).await;
|
self.logout_v2(request_id).await;
|
||||||
}
|
}
|
||||||
|
ClientRequest::CancelLoginAccount { request_id, params } => {
|
||||||
|
self.cancel_login_v2(request_id, params).await;
|
||||||
|
}
|
||||||
ClientRequest::GetAccount {
|
ClientRequest::GetAccount {
|
||||||
request_id,
|
request_id,
|
||||||
params: _,
|
params: _,
|
||||||
@@ -644,27 +650,59 @@ impl CodexMessageProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cancel_login_chatgpt(&mut self, request_id: RequestId, login_id: Uuid) {
|
async fn cancel_login_chatgpt_common(
|
||||||
|
&mut self,
|
||||||
|
login_id: Uuid,
|
||||||
|
) -> std::result::Result<(), JSONRPCErrorError> {
|
||||||
let mut guard = self.active_login.lock().await;
|
let mut guard = self.active_login.lock().await;
|
||||||
if guard.as_ref().map(|l| l.login_id) == Some(login_id) {
|
if guard.as_ref().map(|l| l.login_id) == Some(login_id) {
|
||||||
if let Some(active) = guard.take() {
|
if let Some(active) = guard.take() {
|
||||||
active.drop();
|
active.drop();
|
||||||
}
|
}
|
||||||
drop(guard);
|
Ok(())
|
||||||
self.outgoing
|
|
||||||
.send_response(
|
|
||||||
request_id,
|
|
||||||
codex_app_server_protocol::CancelLoginChatGptResponse {},
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
} else {
|
} else {
|
||||||
drop(guard);
|
Err(JSONRPCErrorError {
|
||||||
let error = JSONRPCErrorError {
|
|
||||||
code: INVALID_REQUEST_ERROR_CODE,
|
code: INVALID_REQUEST_ERROR_CODE,
|
||||||
message: format!("login id not found: {login_id}"),
|
message: format!("login id not found: {login_id}"),
|
||||||
data: None,
|
data: None,
|
||||||
};
|
})
|
||||||
self.outgoing.send_error(request_id, error).await;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cancel_login_chatgpt(&mut self, request_id: RequestId, login_id: Uuid) {
|
||||||
|
match self.cancel_login_chatgpt_common(login_id).await {
|
||||||
|
Ok(()) => {
|
||||||
|
self.outgoing
|
||||||
|
.send_response(request_id, CancelLoginChatGptResponse {})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.outgoing.send_error(request_id, error).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cancel_login_v2(&mut self, request_id: RequestId, params: CancelLoginAccountParams) {
|
||||||
|
let login_id = params.login_id;
|
||||||
|
match Uuid::parse_str(&login_id) {
|
||||||
|
Ok(uuid) => match self.cancel_login_chatgpt_common(uuid).await {
|
||||||
|
Ok(()) => {
|
||||||
|
self.outgoing
|
||||||
|
.send_response(request_id, CancelLoginAccountResponse {})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
self.outgoing.send_error(request_id, error).await;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let error = JSONRPCErrorError {
|
||||||
|
code: INVALID_REQUEST_ERROR_CODE,
|
||||||
|
message: format!("invalid login id: {login_id}"),
|
||||||
|
data: None,
|
||||||
|
};
|
||||||
|
self.outgoing.send_error(request_id, error).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use anyhow::Context;
|
|||||||
use assert_cmd::prelude::*;
|
use assert_cmd::prelude::*;
|
||||||
use codex_app_server_protocol::AddConversationListenerParams;
|
use codex_app_server_protocol::AddConversationListenerParams;
|
||||||
use codex_app_server_protocol::ArchiveConversationParams;
|
use codex_app_server_protocol::ArchiveConversationParams;
|
||||||
|
use codex_app_server_protocol::CancelLoginAccountParams;
|
||||||
use codex_app_server_protocol::CancelLoginChatGptParams;
|
use codex_app_server_protocol::CancelLoginChatGptParams;
|
||||||
use codex_app_server_protocol::ClientInfo;
|
use codex_app_server_protocol::ClientInfo;
|
||||||
use codex_app_server_protocol::ClientNotification;
|
use codex_app_server_protocol::ClientNotification;
|
||||||
@@ -386,6 +387,15 @@ impl McpProcess {
|
|||||||
self.send_request("account/login/start", Some(params)).await
|
self.send_request("account/login/start", Some(params)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send an `account/login/cancel` JSON-RPC request.
|
||||||
|
pub async fn send_cancel_login_account_request(
|
||||||
|
&mut self,
|
||||||
|
params: CancelLoginAccountParams,
|
||||||
|
) -> anyhow::Result<i64> {
|
||||||
|
let params = Some(serde_json::to_value(params)?);
|
||||||
|
self.send_request("account/login/cancel", params).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a `fuzzyFileSearch` JSON-RPC request.
|
/// Send a `fuzzyFileSearch` JSON-RPC request.
|
||||||
pub async fn send_fuzzy_file_search_request(
|
pub async fn send_fuzzy_file_search_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use anyhow::bail;
|
|||||||
use app_test_support::McpProcess;
|
use app_test_support::McpProcess;
|
||||||
use app_test_support::to_response;
|
use app_test_support::to_response;
|
||||||
use codex_app_server_protocol::AuthMode;
|
use codex_app_server_protocol::AuthMode;
|
||||||
|
use codex_app_server_protocol::CancelLoginAccountParams;
|
||||||
|
use codex_app_server_protocol::CancelLoginAccountResponse;
|
||||||
use codex_app_server_protocol::GetAuthStatusParams;
|
use codex_app_server_protocol::GetAuthStatusParams;
|
||||||
use codex_app_server_protocol::GetAuthStatusResponse;
|
use codex_app_server_protocol::GetAuthStatusResponse;
|
||||||
use codex_app_server_protocol::JSONRPCError;
|
use codex_app_server_protocol::JSONRPCError;
|
||||||
@@ -16,6 +18,7 @@ use codex_login::login_with_api_key;
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
@@ -229,17 +232,51 @@ async fn login_account_chatgpt_start() -> Result<()> {
|
|||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let login: LoginAccountResponse = to_response(resp)?;
|
let login: LoginAccountResponse = to_response(resp)?;
|
||||||
let LoginAccountResponse::Chatgpt {
|
let LoginAccountResponse::Chatgpt { login_id, auth_url } = login else {
|
||||||
login_id: _,
|
|
||||||
auth_url,
|
|
||||||
} = login
|
|
||||||
else {
|
|
||||||
bail!("unexpected login response: {login:?}");
|
bail!("unexpected login response: {login:?}");
|
||||||
};
|
};
|
||||||
assert!(
|
assert!(
|
||||||
auth_url.contains("redirect_uri=http%3A%2F%2Flocalhost"),
|
auth_url.contains("redirect_uri=http%3A%2F%2Flocalhost"),
|
||||||
"auth_url should contain a redirect_uri to localhost"
|
"auth_url should contain a redirect_uri to localhost"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cancel_id = mcp
|
||||||
|
.send_cancel_login_account_request(CancelLoginAccountParams {
|
||||||
|
login_id: login_id.clone(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let cancel_resp: JSONRPCResponse = timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
let _ok: CancelLoginAccountResponse = to_response(cancel_resp)?;
|
||||||
|
|
||||||
|
let note = timeout(
|
||||||
|
DEFAULT_READ_TIMEOUT,
|
||||||
|
mcp.read_stream_until_notification_message("account/login/completed"),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
let parsed: ServerNotification = note.try_into()?;
|
||||||
|
let ServerNotification::AccountLoginCompleted(payload) = parsed else {
|
||||||
|
bail!("unexpected notification: {parsed:?}");
|
||||||
|
};
|
||||||
|
pretty_assertions::assert_eq!(payload.login_id, Some(login_id));
|
||||||
|
pretty_assertions::assert_eq!(payload.success, false);
|
||||||
|
assert!(
|
||||||
|
payload.error.is_some(),
|
||||||
|
"expected a non-empty error on cancel"
|
||||||
|
);
|
||||||
|
|
||||||
|
let maybe_updated = timeout(
|
||||||
|
Duration::from_millis(500),
|
||||||
|
mcp.read_stream_until_notification_message("account/updated"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(
|
||||||
|
maybe_updated.is_err(),
|
||||||
|
"account/updated should not be emitted when login is cancelled"
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user