mirror of
https://github.com/luxfi/kms.git
synced 2026-07-27 03:38:31 +00:00
fix(kms): post client_credentials/auth_code to /v1/iam/oauth/token
The hanzoai/iam OAuth API moved under the /v1/iam prefix. The legacy root /login/oauth/access_token is now a frontend redirect, not the token API, so the KMS auth-login proxy and OIDC SSO code-exchange both got HTML back -> empty access_token -> 401 'invalid credentials' to the kms-operator (breaking every KMSSecret sync, incl. luxd-staking on devnet/testnet/mainnet). Route both call sites to the canonical token endpoint via a single env-overridable knob IAM_TOKEN_PATH (default /v1/iam/oauth/token), so a future path move needs no rebuild. cmd/kms tests updated + green.
This commit is contained in:
+9
-4
@@ -110,6 +110,11 @@ func main() {
|
||||
vaultID := envOr("MPC_VAULT_ID", "")
|
||||
nodeID := envOr("KMS_NODE_ID", "kms-0")
|
||||
iamEndpoint := envOr("IAM_ENDPOINT", "https://hanzo.id")
|
||||
// Canonical OAuth2 token endpoint on hanzoai/iam. The API moved under
|
||||
// the `/v1/iam` prefix; the legacy root `/login/oauth/access_token`
|
||||
// route is now a frontend redirect, not the token API. Env-overridable
|
||||
// so a future path move needs no rebuild — one knob, one default.
|
||||
iamTokenPath := envOr("IAM_TOKEN_PATH", "/v1/iam/oauth/token")
|
||||
dataDir := envOr("KMS_DATA_DIR", "/data/kms")
|
||||
listen := envOr("KMS_LISTEN", ":8080")
|
||||
|
||||
@@ -204,10 +209,10 @@ func main() {
|
||||
"client_id": {req.ClientID},
|
||||
"client_secret": {req.ClientSecret},
|
||||
}
|
||||
// Canonical OAuth2 path on hanzoai/iam — no `/api/` prefix (killed in
|
||||
// v2.381.0). `/login/oauth/access_token` is mounted at root per the
|
||||
// OAuth2 spec; `/oauth/access_token` is also wired as an alias.
|
||||
resp, err := http.PostForm(iamEndpoint+"/login/oauth/access_token", form)
|
||||
// Canonical OAuth2 token endpoint on hanzoai/iam, under the `/v1/iam`
|
||||
// API prefix (default IAM_TOKEN_PATH=/v1/iam/oauth/token). The legacy
|
||||
// root `/login/oauth/access_token` is a frontend redirect, not the API.
|
||||
resp, err := http.PostForm(iamEndpoint+iamTokenPath, form)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusBadGateway, map[string]any{"statusCode": 502, "message": "identity provider unreachable"})
|
||||
return
|
||||
|
||||
+5
-3
@@ -77,6 +77,7 @@ const (
|
||||
type oidcConfig struct {
|
||||
iamEndpoint string // e.g. https://iam.dev.example.com
|
||||
iamHost string // parsed Host of iamEndpoint (for origin comparisons)
|
||||
tokenPath string // OAuth2 token endpoint path (IAM_TOKEN_PATH)
|
||||
clientID string // e.g. lux-kms
|
||||
clientSecret string // KMSSecret-injected
|
||||
stateSecret []byte // HMAC key for state-nonce signing (>=32 bytes)
|
||||
@@ -130,6 +131,7 @@ func loadOIDCConfig() *oidcConfig {
|
||||
return &oidcConfig{
|
||||
iamEndpoint: strings.TrimRight(iam, "/"),
|
||||
iamHost: iamURL.Host,
|
||||
tokenPath: envOr("IAM_TOKEN_PATH", "/v1/iam/oauth/token"),
|
||||
clientID: cid,
|
||||
clientSecret: cs,
|
||||
stateSecret: []byte(ss),
|
||||
@@ -508,8 +510,8 @@ func (c *oidcConfig) handleLogout(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
// exchangeCode posts the auth code to IAM's token endpoint and returns
|
||||
// the access_token. The IAM endpoint is canonical Hanzo IAM at
|
||||
// /login/oauth/access_token (no /api/ prefix — killed in iam v2.381).
|
||||
// the access_token. The canonical Hanzo IAM token endpoint lives under
|
||||
// the /v1/iam API prefix (c.tokenPath, default /v1/iam/oauth/token).
|
||||
func (c *oidcConfig) exchangeCode(ctx context.Context, code, redirectURI string) (string, error) {
|
||||
form := url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
@@ -519,7 +521,7 @@ func (c *oidcConfig) exchangeCode(ctx context.Context, code, redirectURI string)
|
||||
"redirect_uri": {redirectURI},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
||||
c.iamEndpoint+"/login/oauth/access_token",
|
||||
c.iamEndpoint+c.tokenPath,
|
||||
strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -29,6 +29,7 @@ func newTestCfg(t *testing.T, iamEndpoint string) *oidcConfig {
|
||||
return &oidcConfig{
|
||||
iamEndpoint: strings.TrimRight(iamEndpoint, "/"),
|
||||
iamHost: u.Host,
|
||||
tokenPath: "/v1/iam/oauth/token",
|
||||
clientID: "lux-kms",
|
||||
clientSecret: "test-secret",
|
||||
stateSecret: []byte("test-secret-test-secret-test-32!"),
|
||||
@@ -132,7 +133,7 @@ func TestSsoOidcLogin_includesStateNonce(t *testing.T) {
|
||||
// 4. good state → token exchange + 302 /
|
||||
func TestSsoOidcCallback_validatesState(t *testing.T) {
|
||||
iam := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/login/oauth/access_token" {
|
||||
if r.URL.Path != "/v1/iam/oauth/token" {
|
||||
t.Errorf("unexpected IAM path: %s", r.URL.Path)
|
||||
}
|
||||
body, _ := io.ReadAll(r.Body)
|
||||
|
||||
Reference in New Issue
Block a user