diff --git a/hscontrol/api/v2/keys.go b/hscontrol/api/v2/keys.go index e14376f9b..f44c3b890 100644 --- a/hscontrol/api/v2/keys.go +++ b/hscontrol/api/v2/keys.go @@ -156,11 +156,16 @@ func registerKeys(api huma.API, b Backend) { return nil, err } - if in.Body.KeyType == keyTypeClient { + switch in.Body.KeyType { + case keyTypeClient: return createOAuthClient(ctx, b, in.Body) + case "", keyTypeAuth: + return createAuthKey(ctx, b, in.Body) + default: + return nil, huma.Error400BadRequest( + "invalid key type, " + strconv.Quote(in.Body.KeyType) + " is not supported", + ) } - - return createAuthKey(ctx, b, in.Body) }) huma.Register(api, huma.Operation{ diff --git a/hscontrol/apiv2_compat_test.go b/hscontrol/apiv2_compat_test.go index a368f003a..1170ecde9 100644 --- a/hscontrol/apiv2_compat_test.go +++ b/hscontrol/apiv2_compat_test.go @@ -71,9 +71,7 @@ var apiv2CompatSkipReasons = map[string]string{ "key-list/setup-token-keys/4/tags": mintTagsNotEchoed, "key-list/setup-token-keys/6/tags": mintTagsNotEchoed, - "oauth/K2/0/status": unknownKeyType, - "oauth/K2/0/outcome": unknownKeyType, - "oauth/K5/0/status": unknownKeyType, + "oauth/K5/0/status": unknownKeyType, "oauth/E4/0/status": tagRefusalStatus, "oauth/E5/0/status": tagRefusalStatus, @@ -202,7 +200,7 @@ const ( const ( mintScopeRefusalStatus = "TOKEN_SCOPE_REFUSAL_STATUS: SaaS refuses a token scope the client lacks with 403 and a message; headscale with 400 invalid_scope" mintTagsNotEchoed = "TOKEN_TAGS_NOT_ECHOED: SaaS's token answer names the token's tags; headscale's has no tags" - unknownKeyType = "UNKNOWN_KEY_TYPE: SaaS refuses an unknown keyType with 400 before it checks scopes; headscale makes an auth key from it" + unknownKeyType = "UNKNOWN_KEY_TYPE: SaaS refuses an unknown keyType with 400 before it checks scopes; headscale checks the scope first and answers 403" tagRefusalStatus = "TAG_REFUSAL_STATUS: SaaS refuses a tag outside the token's grant with 400 requested tags are invalid or not permitted; headscale with 403" keyListWithoutAll = "KEY_LIST_WITHOUT_ALL: without all=true SaaS lists an OAuth token only the auth keys OAuth tokens made, whatever its tags and theirs; headscale lists every key" keyListInvalidKeys = "KEY_LIST_INVALID_KEYS: SaaS leaves spent and revoked auth keys out of a listing; headscale lists them" diff --git a/hscontrol/apiv2_oauth_test.go b/hscontrol/apiv2_oauth_test.go index 1f04a2975..a4d23b1e0 100644 --- a/hscontrol/apiv2_oauth_test.go +++ b/hscontrol/apiv2_oauth_test.go @@ -275,6 +275,25 @@ func TestAPIv2OAuth_ValidationErrorsBounded(t *testing.T) { assert.Equal(t, "and 980 more", out.Data[0].Errors[20]) } +// TestAPIv2OAuth_UnknownKeyTypeRefused: a keyType other than auth or client +// is refused, not taken as an auth key. +func TestAPIv2OAuth_UnknownKeyTypeRefused(t *testing.T) { + app, baseURL, admin := newOAuthTestServer(t) + + status, body := apiReq(t, http.MethodPost, baseURL+"/api/v2/tailnet/-/keys", admin, map[string]any{ + "keyType": "nosuch", + "capabilities": map[string]any{"devices": map[string]any{"create": map[string]any{ + "reusable": true, "ephemeral": false, "preauthorized": true, "tags": []string{"tag:ci"}, + }}}, + }) + assert.Equal(t, http.StatusBadRequest, status) + assert.Contains(t, string(body), `invalid key type, \"nosuch\" is not supported`) + + keys, err := app.state.ListPreAuthKeys() + require.NoError(t, err) + assert.Empty(t, keys) +} + // TestAPIv2OAuth_UnknownPropertiesIgnored: a body property the API does not // define is ignored, at the top and nested, as Tailscale's API does. func TestAPIv2OAuth_UnknownPropertiesIgnored(t *testing.T) {