fix(auth): /v1/iam/login/oauth/access_token instead of /oauth/token

The Hanzo gateway exposes Casdoor's
at  (verified via probe — POST
returns 401 JSON with proper error semantics). The bare /oauth/token
listed in iam.hanzo.ai/.well-known/openid-configuration returns 404
in prod — the gateway has no rewrite for it.

Token exchange + refresh now hit the working path and use proper
application/x-www-form-urlencoded body per RFC 6749. JSON bodies were
silently rejected by Casdoor's token endpoint (it only accepts form-
encoded for the OAuth path).

Bumps 1.8.6 → 1.8.7 in root + browser package.json + both manifests.
This commit is contained in:
Hanzo AI
2026-05-07 20:08:29 -07:00
parent 6503fa614d
commit 43a81de0d0
5 changed files with 16 additions and 12 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hanzo-ai-monorepo",
"version": "1.8.6",
"version": "1.8.7",
"private": true,
"description": "Hanzo AI Development Platform Monorepo",
"license": "MIT",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@hanzo/browser-extension",
"version": "1.8.6",
"version": "1.8.7",
"description": "Hanzo AI Browser Extension",
"main": "dist/background.js",
"scripts": {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hanzo AI",
"version": "1.8.6",
"version": "1.8.7",
"description": "AI-powered dev assistant — click-to-code navigation, source maps, WebGPU AI, and MCP integration for Claude Code.",
"permissions": [
"activeTab",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hanzo AI",
"version": "1.8.6",
"version": "1.8.7",
"description": "AI-powered dev assistant — click-to-code navigation, source maps, WebGPU AI, and MCP integration for Claude Code.",
"permissions": [
"activeTab",
+12 -8
View File
@@ -210,16 +210,20 @@ export async function login(adapter: BrowserAdapter): Promise<UserInfo> {
token_type: 'Bearer',
});
} else if (code) {
const tokenResponse = await fetch(`${config.iamApiUrl}/oauth/token`, {
// Hanzo gateway exposes Casdoor's `/api/login/oauth/access_token` at
// `/v1/iam/login/oauth/access_token`. The bare `/oauth/token` from
// the OIDC well-known doc 404s in prod. Token exchange wants
// application/x-www-form-urlencoded per RFC 6749, not JSON.
const tokenResponse = await fetch(`${config.iamApiUrl}${IAM_API_PATH}/login/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: config.clientId,
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
}),
}).toString(),
});
if (!tokenResponse.ok) throw new Error(`Token exchange failed: ${await tokenResponse.text()}`);
await storeTokens(adapter.storage, await tokenResponse.json());
@@ -267,14 +271,14 @@ export async function getValidAccessToken(storage: BrowserStorage): Promise<stri
async function refreshAccessToken(storage: BrowserStorage, refreshToken: string): Promise<string> {
const config = await getRuntimeConfig(storage);
const response = await fetch(`${config.iamApiUrl}/oauth/token`, {
const response = await fetch(`${config.iamApiUrl}${IAM_API_PATH}/login/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: config.clientId,
refresh_token: refreshToken,
}),
}).toString(),
});
if (!response.ok) throw new Error('Token refresh failed');
const tokens: TokenData = await response.json();