fix(login): hanzo.id OAuth uses browser PKCE against /v1/iam (correct endpoints)

The device-code flow hit OpenAI-shaped endpoints (/api/accounts/deviceauth/*)
that hanzo.id (Casdoor) does not serve. Fix: hanzo login now uses the browser
PKCE flow with issuer https://hanzo.id/v1/iam, so authorize/token resolve to
https://hanzo.id/v1/iam/oauth/{authorize,token} (verified vs hanzo.id OIDC
discovery). Verified the emitted authorize URL is correct.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-06-16 22:19:23 -07:00
co-authored by Hanzo Dev
parent 7a16520243
commit 83460db2c3
4 changed files with 61 additions and 12 deletions
+1
View File
@@ -17,6 +17,7 @@ pub use login::run_login_status;
pub use login::run_login_with_access_token;
pub use login::run_login_with_api_key;
pub use login::run_login_with_chatgpt;
pub use login::run_login_with_hanzo;
pub use login::run_login_with_device_code;
pub use login::run_login_with_device_code_fallback_to_browser;
pub use login::run_logout;
+51
View File
@@ -158,6 +158,57 @@ pub async fn login_with_chatgpt(
server.block_until_done().await
}
/// Browser PKCE login against Hanzo IAM (hanzo.id). Uses the same local
/// callback server as ChatGPT login, but with the Hanzo client id + issuer so
/// the authorize/token URLs resolve to https://hanzo.id/v1/iam/oauth/*.
pub async fn login_with_hanzo(
codex_home: std::path::PathBuf,
cli_auth_credentials_store_mode: codex_login::AuthCredentialsStoreMode,
auth_keyring_backend_kind: codex_login::AuthKeyringBackendKind,
) -> std::io::Result<()> {
clear_existing_auth_before_login(
&codex_home,
cli_auth_credentials_store_mode,
auth_keyring_backend_kind,
)
.await;
let mut opts = ServerOptions::new(
codex_home,
codex_login::HANZO_CLIENT_ID.to_string(),
None,
cli_auth_credentials_store_mode,
auth_keyring_backend_kind,
);
opts.issuer = codex_login::HANZO_ISSUER.to_string();
let server = run_login_server(opts)?;
print_login_server_start(server.actual_port, &server.auth_url);
server.block_until_done().await
}
pub async fn run_login_with_hanzo(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting Hanzo IAM (hanzo.id) browser login flow");
match login_with_hanzo(
config.codex_home.to_path_buf(),
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
)
.await
{
Ok(_) => {
eprintln!("{LOGIN_SUCCESS_MESSAGE} (Hanzo)");
std::process::exit(0);
}
Err(e) => {
eprintln!("Error logging in with Hanzo: {e}");
std::process::exit(1);
}
}
}
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
+4 -11
View File
@@ -16,6 +16,7 @@ use codex_cli::run_login_status;
use codex_cli::run_login_with_access_token;
use codex_cli::run_login_with_api_key;
use codex_cli::run_login_with_chatgpt;
use codex_cli::run_login_with_hanzo;
use codex_cli::run_login_with_device_code;
use codex_cli::run_logout;
use codex_cloud_tasks::Cli as CloudTasksCli;
@@ -1408,18 +1409,10 @@ async fn cli_main(
}
}
}
// Hanzo IAM (hanzo.id) is the default.
// Hanzo IAM (hanzo.id) is the default — browser PKCE
// against https://hanzo.id/v1/iam/oauth/*.
Some(LoginProvider::Hanzo) | None => {
run_login_with_device_code(
login_cli.config_overrides,
login_cli
.issuer_base_url
.or_else(|| Some(codex_login::HANZO_ISSUER.to_string())),
login_cli
.client_id
.or_else(|| Some(codex_login::HANZO_CLIENT_ID.to_string())),
)
.await;
run_login_with_hanzo(login_cli.config_overrides).await;
}
}
}
+5 -1
View File
@@ -1145,7 +1145,11 @@ pub const CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
// Hanzo IAM (hanzo.id Casdoor OAuth — public client, PKCE only)
pub const HANZO_CLIENT_ID: &str = "hanzo-dev";
pub const HANZO_ISSUER: &str = "https://hanzo.id";
// OAuth issuer base. hanzo.id (Casdoor) serves OIDC under /v1/iam, so the login
// server's `{issuer}/oauth/authorize` + `{issuer}/oauth/token` resolve to
// https://hanzo.id/v1/iam/oauth/authorize and .../oauth/token (verified against
// hanzo.id/.well-known/openid-configuration).
pub const HANZO_ISSUER: &str = "https://hanzo.id/v1/iam";
// Claude Code OAuth credentials (for Anthropic OAuth)
pub const CLAUDE_CLIENT_ID: &str = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";