fix(server,ui,openapi): answer null tenant and role for a user in no namespace - #7171
Conversation
…namespace The console store checks role === undefined rather than role ?? get().role, because the nullish form would keep a stale role after switching into a namespace that grants none. Role.String returns the empty string for every role outside the four assignable ones, RoleInvalid included. The old doc comment claimed "N/A", which the code never returned. Fixes: #7166
Code Review CompleteThe automated review ran but did not post an updated summary — this usually means no new issues were found since the previous review. If you've pushed changes and want a fresh pass, comment |
|
Can we add an e2e case for this in this PR instead of leaving it for later? The e2e stack already runs with Something like: func TestLoginWithoutANamespace(t *testing.T) {
compose := environment.New(t).Up(t.Context())
t.Cleanup(compose.Down)
compose.NewUser(t, "nobody", "nobody@ossystems.com.br", ShellHubPassword)
res, err := compose.R(t.Context()).
SetBody(map[string]string{"username": "nobody", "password": ShellHubPassword}).
Post("/api/login")
require.NoError(t, err)
require.Equal(t, http.StatusOK, res.StatusCode())
var body map[string]any
require.NoError(t, json.Unmarshal(res.Body(), &body))
for _, key := range []string{"tenant", "role"} {
require.Contains(t, body, key)
assert.Nil(t, body[key])
}
}
A second case for a user who leaves their only namespace would cover |
The e2e stack runs the API under SHELLHUB_OPENAPI_VALIDATION=strict, so the 200 carries the assertion: a body the schema rejects comes back as a 500. On the commit before the fix this case fails with exactly that, so it reproduces the bug before it covers it. Decoding into a map rather than UserAuthResponse keeps a dropped key from reading as a null one, since both reach the struct as a nil pointer. Refs: #7166
|
Added in 8e6cfb0 as Two changes. The login sits inside Verified both directions locally, building the images the same way CI does:
So it reproduces the bug before it covers it, exactly as you said it would. |
What
Auth endpoints now answer
nullfortenantandrolewhen a user belongs to no namespace, instead of an empty string.Closes #7166
Needs shellhub-io/cloud#2577, which merges first. Without it the
cloud / validate-server-enterprisecheck is red here and cloud master breaks on merge.Why
role: ""is not one of the four values the OpenAPI role enum allows, so the server was breaking its own contract. The console could not tell "this user has no namespace" from "the field was not sent", because""is neithernullnorundefined.Having no namespace is a normal state, not an edge case. A user is in it right after confirming their account, and again after leaving their last namespace.
Changes
UserAuthResponse.Tenantis now*stringandRoleis*authorizer.Role. Noomitempty, so both keys are always present, and absent meansnull.AuthLocalUserandCreateUserTokenleave both nil when the user holds no membership. Tenant resolution, refusals and preferred-namespace handling are unchanged.nullable: trueand keeps themrequired. The sharednamespaceMemberRoleschema is untouched, since seven other files reference it.roleon an explicitnulland keeps the current one when the field is absent. The two cases are different and??cannot tell them apart.Role.String()doc comment. It returns"", never the"N/A"the old comment claimed.Testing
Automated:
"tenant": null, "role": null, so a dropped key cannot pass as an absent one.go mod tidyall clean.Manual, to exercise it on a dev stack:
./bin/docker-compose up -d openapi && ./bin/docker-compose restart server. Look forEnabling OpenAPI response validation in report mode, and noFailed to initialize OpenAPI validatorafter it../bin/cli user create nobody 'Secret123!-' nobody@test.comYou get
{"tenant": null, "role": null}and no validator warning.{"tenant": "", "role": ""}plus aOpenAPI response validation failedwarning naming/role../bin/cli user delete nobodyTwo callers outside the API needed updating. The e2e assertion in
tests/identity_access_test.gocompared against astring, which a*stringcan never equal. Ten MFA test cases incloudexpected""and are fixed in shellhub-io/cloud#2577.