Skip to content

fix: handle studioctl auth username casing#18816

Merged
martinothamar merged 2 commits into
Altinn:mainfrom
martinothamar-agent:fix/studioctl-mixed-case-settings-owner
May 21, 2026
Merged

fix: handle studioctl auth username casing#18816
martinothamar merged 2 commits into
Altinn:mainfrom
martinothamar-agent:fix/studioctl-mixed-case-settings-owner

Conversation

@martinothamar-agent
Copy link
Copy Markdown
Contributor

@martinothamar-agent martinothamar-agent commented May 18, 2026

What

  • Resolve Studio OIDC usernames from Gitea user.name instead of user.lower_name, so ClaimTypes.Name uses canonical casing such as Jondyr.
  • Canonicalize an existing Designer UserAccounts username when it only differs from the linked Gitea username by casing.
  • Treat settings owner route checks case-insensitively, matching Gitea username route lookup behavior.
  • Keep studioctl auth using the authenticated username directly and add focused tests for canonical casing.

Why

Gitea preserves canonical username casing in name / API login, while using lower_name for case-insensitive lookup and uniqueness. Designer was leaking lower_name into the Studio OIDC auth claim, and settings route checks compared owner values case-sensitively. That could make studioctl redirect to /settings/jondyr/studioctl-auth for a canonical user Jondyr.

Testing

  • dotnet test src/Designer/backend/tests/Designer.Tests/Designer.Tests.csproj --filter StudioctlAuthServiceTests --logger "console;verbosity=minimal"
  • corepack yarn jest settings/layouts/UserPageLayout/UserPageLayout.test.tsx settings/layouts/OrgPageLayout/OrgPageLayout.test.tsx settings/components/OwnerIndexRedirect/OwnerIndexRedirect.test.tsx settings/features/user/pages/StudioctlAuth/StudioctlAuth.test.tsx --runInBand --config=jest.config.js --coverage --collectCoverageFrom='settings/layouts/UserPageLayout/UserPageLayout.tsx' --collectCoverageFrom='settings/layouts/OrgPageLayout/OrgPageLayout.tsx'
  • Manual local Designer test:
    • Rebuilt/restarted Designer at http://studio.localhost.
    • Used local Gitea user Jondyr with lower_name=jondyr.
    • Mapped fake Ansattporten admin subject to that user.
    • Seeded existing Designer user_accounts.username = jondyr.
    • Exercised /Login -> fake Ansattporten -> OIDC callback -> designer/api/v1/studioctl/auth/authorize.
    • Verified /designer/api/v1/studio-oidc/userinfo returned username: "Jondyr".
    • Verified Designer updated the mapping to Jondyr.
    • Verified final URL was /settings/Jondyr/studioctl-auth?requestId=... with HTTP 200.
    • Verified manually opening /settings/jondyr/studioctl-auth?requestId=... loaded the same auth request instead of 404.

cc @martinothamar

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Warning

Rate limit exceeded

@martinothamar-agent has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 36 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9538a3a5-c59b-443e-87bc-981036122fe9

📥 Commits

Reviewing files that changed from the base of the PR and between 57eab29 and ed21b33.

📒 Files selected for processing (10)
  • src/Designer/backend/src/Designer/Services/Implementation/GiteaDbStudioOidcUsernameProvider.cs
  • src/Designer/backend/tests/Designer.Tests/Services/StudioctlAuthServiceTests.cs
  • src/Designer/frontend/settings/components/OwnerIndexRedirect/OwnerIndexRedirect.test.tsx
  • src/Designer/frontend/settings/components/OwnerIndexRedirect/OwnerIndexRedirect.tsx
  • src/Designer/frontend/settings/features/user/pages/StudioctlAuth/StudioctlAuth.test.tsx
  • src/Designer/frontend/settings/features/user/pages/StudioctlAuth/StudioctlAuth.tsx
  • src/Designer/frontend/settings/layouts/OrgPageLayout/OrgPageLayout.test.tsx
  • src/Designer/frontend/settings/layouts/OrgPageLayout/OrgPageLayout.tsx
  • src/Designer/frontend/settings/layouts/UserPageLayout/UserPageLayout.test.tsx
  • src/Designer/frontend/settings/layouts/UserPageLayout/UserPageLayout.tsx
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added quality/testing Tests that are missing, needs to be created or could be improved. skip-releasenotes Issues that do not make sense to list in our release notes backend solution/studio/designer labels May 18, 2026
@martinothamar
Copy link
Copy Markdown
Contributor

martinothamar commented May 18, 2026

how does the username (in the toke?) get the wrong casing? Before your change

@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Before this change the username used by StudioctlAuthController comes from AuthenticationHelper.GetDeveloperUserName(HttpContext), i.e. User.Identity.Name.

For Studio OIDC that name is set in StudioOidcController.Callback from computedUsername, and computedUsername comes from GiteaDbStudioOidcUsernameProvider.ResolveUsernameAsync. The existing Gitea lookup explicitly selects u.lower_name from Gitea:

SELECT u.lower_name
FROM external_login_user elu
JOIN "user" u ON elu.user_id = u.id
WHERE elu.external_id = @sub
LIMIT 1

So for a Gitea user where user.name = Jondyr and user.lower_name = jondyr, the auth cookie gets ClaimTypes.Name = jondyr. Before this PR, that same lower-case value was also used to build /settings/{username}/studioctl-auth, which gives /settings/jondyr/....

This PR keeps using the computed/authenticated username for pending-request ownership, but asks Gitea /user for the current user when building the settings URL. That API returns the canonical login casing (Jondyr), matching the settings route.

@Jondyr
Copy link
Copy Markdown
Member

Jondyr commented May 18, 2026

@martinothamar-agent
Why is the username case sensitive at all in this context?

@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Good question. It probably should not be case-sensitive for identity in this context.

I checked how Gitea handles this: route/API params such as /:username are resolved through GetUserByName, and that lookup does lower_name = strings.ToLower(input). So Gitea accepts Jondyr, jondyr, etc. as the same user/org identifier, while still preserving canonical casing in user.name / API login.

The case-sensitive part here is on the Designer settings side: the settings route owner is compared to the current user login as a string. With Studio OIDC, the auth claim currently comes from Gitea lower_name, so we ended up generating /settings/jondyr/studioctl-auth; the settings app then compares that to the canonical user.login (Jondyr) and renders 404.

So I agree the more Gitea-like invariant is: compare owner/usernames case-insensitively for identity, and use canonical casing only when generating/normalizing URLs. This PR fixes the studioctl entrypoint by generating the canonical URL, but the broader cleanup would be to make the settings owner checks case-insensitive too.

@martinothamar
Copy link
Copy Markdown
Contributor

Proposed by @mirkoSekulic internally:

  • the claim should be corrected to have the "real" username, i.e Jondyr in this case (please verify that this is indeed safe and wont break anything else, do some manual testing if needed)
  • the URL parameter should be treated as case-insensitive, same as gitea itself

lets do that

@martinothamar-agent martinothamar-agent force-pushed the fix/studioctl-mixed-case-settings-owner branch from 2610aa2 to ca1b35d Compare May 18, 2026 12:10
@martinothamar-agent martinothamar-agent changed the title fix: preserve studioctl auth settings owner casing fix: handle studioctl auth username casing May 18, 2026
@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Updated the PR accordingly.

Changes now follow the proposed direction:

  • Studio OIDC resolves the canonical Gitea username from user.name instead of user.lower_name, so the auth claim becomes Jondyr for this case.
  • Existing Designer UserAccounts mappings are corrected when they only differ from the linked Gitea username by casing, so an old jondyr mapping is updated to Jondyr.
  • Settings owner checks now compare case-insensitively, matching Gitea route lookup behavior.
  • Removed the earlier controller-level workaround; studioctl auth uses the authenticated username directly again.

I also manually tested the risky path locally with an existing lower-case Designer mapping (user_accounts.username = jondyr). After OIDC login, /designer/api/v1/studio-oidc/userinfo returned Jondyr, the DB mapping was updated to Jondyr, and studioctl ended on /settings/Jondyr/studioctl-auth?... with HTTP 200.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.84%. Comparing base (50540fa) to head (ed21b33).
⚠️ Report is 25 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main   #18816    +/-   ##
========================================
  Coverage   95.84%   95.84%            
========================================
  Files        3018     3023     +5     
  Lines       39575    39697   +122     
  Branches     4849     4885    +36     
========================================
+ Hits        37931    38049   +118     
- Misses       1230     1233     +3     
- Partials      414      415     +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@martinothamar-agent martinothamar-agent force-pushed the fix/studioctl-mixed-case-settings-owner branch 2 times, most recently from 3a170ce to 9190db8 Compare May 18, 2026 17:08
@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Addressed the Codecov coverage complaint in the latest push. I added explicit null-user coverage for both UserPageLayout and OrgPageLayout so the user?.login ?? '' fallback branches are covered.

Local verification now reports 100% branch coverage for both touched layout files with:

corepack yarn jest settings/layouts/UserPageLayout/UserPageLayout.test.tsx settings/layouts/OrgPageLayout/OrgPageLayout.test.tsx settings/components/OwnerIndexRedirect/OwnerIndexRedirect.test.tsx settings/features/user/pages/StudioctlAuth/StudioctlAuth.test.tsx --runInBand --config=jest.config.js --coverage --collectCoverageFrom='settings/layouts/UserPageLayout/UserPageLayout.tsx' --collectCoverageFrom='settings/layouts/OrgPageLayout/OrgPageLayout.tsx'

Also repeated the manual Designer flow after the rebase; canonical URL, lowercase settings URL, userinfo, and DB mapping migration all passed.

@mirkoSekulic
Copy link
Copy Markdown
Contributor

Already stored user data should be immutable.
Is there a reason to change the username for existing users to match the Gitea canonical format?
Already saved users should work just fine with the Gitea API, even if they are lowercased.

@martinothamar-agent
Copy link
Copy Markdown
Contributor Author

Addressed Mirko's feedback in 8ec94e705b.\n\nExisting Designer UserAccounts rows are no longer rewritten to match Gitea canonical casing. The Studio OIDC username provider now returns an existing mapping unchanged after the deactivation check. New mappings still use Gitea user.name, so new users get canonical casing without mutating old data.\n\nThe settings routes already compare owner/usernames case-insensitively in this PR, so an existing lower-case mapping can keep generating /settings/jondyr/... while still matching a canonical Jondyr frontend user login.\n\nVerification:\n- dotnet test src/Designer/backend/tests/Designer.Tests/Designer.Tests.csproj --filter "FullyQualifiedName~StudioctlAuthServiceTests|FullyQualifiedName~GiteaDbStudioOidcUsernameProviderTests"\n- corepack yarn jest settings/layouts/UserPageLayout/UserPageLayout.test.tsx settings/layouts/OrgPageLayout/OrgPageLayout.test.tsx settings/components/OwnerIndexRedirect/OwnerIndexRedirect.test.tsx settings/features/user/pages/StudioctlAuth/StudioctlAuth.test.tsx --runInBand --config=jest.config.js

Copy link
Copy Markdown
Member

@Jondyr Jondyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested in dev, found no issues. nice! 👍

@martinothamar martinothamar merged commit 1742a53 into Altinn:main May 21, 2026
24 checks passed
@martinothamar-agent martinothamar-agent deleted the fix/studioctl-mixed-case-settings-owner branch May 21, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend frontend quality/testing Tests that are missing, needs to be created or could be improved. skip-releasenotes Issues that do not make sense to list in our release notes solution/studio/designer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants