Skip to content

Support for Custom URI Schemes in OAuth2 Redirect URIs#37356

Merged
wxiaoguang merged 5 commits intogo-gitea:mainfrom
wxiaoguang:fix-oauth2-scheme
Apr 22, 2026
Merged

Support for Custom URI Schemes in OAuth2 Redirect URIs#37356
wxiaoguang merged 5 commits intogo-gitea:mainfrom
wxiaoguang:fix-oauth2-scheme

Conversation

@wxiaoguang
Copy link
Copy Markdown
Contributor

@wxiaoguang wxiaoguang commented Apr 22, 2026

Fix #34349

https://gitea.com/gitea/docs/pulls/387

Diff with ignoring spaces: https://github.com/go-gitea/gitea/pull/37356/changes?w=1

By the way, remove (ctx *APIContext) HasAPIError() and (ctx *APIContext) GetErrMsg() because they do nothing, the error handling has been done in API's middeware

The existing OAuth2 tests were not quite right, refactored them together

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Apr 22, 2026
@github-actions github-actions Bot added the docs-update-needed The document needs to be updated synchronously label Apr 22, 2026
@wxiaoguang wxiaoguang added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label Apr 22, 2026
@wxiaoguang wxiaoguang requested a review from Copilot April 22, 2026 05:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for OAuth2 application redirect URIs that use custom URI schemes (for native/mobile/desktop app integrations), controlled by a new CUSTOM_SCHEMES setting, and wires this validation into both the web UI and API endpoints.

Changes:

  • Introduces OAuth2.CustomSchemes configuration and documents it in app.example.ini.
  • Replaces the previous redirect URI list validation with a new scheme-based validator shared by web UI and API.
  • Updates Swagger responses and adjusts integration tests for the new behavior.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
services/forms/user_form.go Adds redirect-URI validation helper and custom-scheme support for the settings UI form.
routers/api/v1/user/app.go Adds redirect-URI validation to create/update OAuth2 application API endpoints.
modules/setting/oauth2.go Adds CustomSchemes to OAuth2 settings struct.
custom/conf/app.example.ini Documents new CUSTOM_SCHEMES configuration option.
modules/web/middleware/binding.go Adds ReportValidationError helper for form validation reporting.
templates/swagger/v1_json.tmpl Adds 400 response to an OAuth2 application endpoint in Swagger JSON.
tests/integration/user_settings_test.go Updates assertions and adds coverage for custom scheme acceptance in UI.
modules/validation/binding.go / modules/validation/binding_test.go / modules/validation/validurllist_test.go Removes the ValidUrlList binding rule and its tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routers/api/v1/user/app.go
Comment thread routers/api/v1/user/app.go
Comment thread modules/setting/oauth2.go
Comment thread services/forms/user_form.go
Comment thread services/forms/user_form.go Outdated
Comment thread modules/web/middleware/binding.go Outdated
Comment thread routers/api/v1/user/app.go
@wxiaoguang wxiaoguang force-pushed the fix-oauth2-scheme branch 8 times, most recently from f564d78 to f5c3c5c Compare April 22, 2026 10:20
@wxiaoguang wxiaoguang requested a review from Copilot April 22, 2026 10:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tests/integration/oauth_test.go:512

  • testRefreshTokenInvalidation mutates the global setting.OAuth2.InvalidateRefreshTokens but does not restore it. Since this file now runs many OAuth2 subtests under a single PrepareTestEnv, this can leak state into later subtests and make the suite order-dependent/flaky. Save the original value and defer restoring it (or use test.MockVariableValue) around these assignments.
	// test without invalidation
	setting.OAuth2.InvalidateRefreshTokens = false

	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
		"grant_type": "refresh_token",
		"client_id":  "da7da3ba-9a13-4167-856f-3899de0b0138",
		// omit secret
		"redirect_uri":  "https://example.com",
		"refresh_token": parsed.RefreshToken,
	})
	resp = MakeRequest(t, req, http.StatusBadRequest)
	parsedError := new(oauth2_provider.AccessTokenError)
	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
	assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
	assert.Equal(t, "invalid empty client secret", parsedError.ErrorDescription)

	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
		"grant_type":    "refresh_token",
		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
		"redirect_uri":  "https://example.com",
		"refresh_token": "UNEXPECTED",
	})
	resp = MakeRequest(t, req, http.StatusBadRequest)
	parsedError = new(oauth2_provider.AccessTokenError)
	assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
	assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
	assert.Equal(t, "unable to parse refresh token", parsedError.ErrorDescription)

	req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
		"grant_type":    "refresh_token",
		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
		"redirect_uri":  "https://example.com",
		"refresh_token": parsed.RefreshToken,
	})

	bs, err := io.ReadAll(req.Body)
	assert.NoError(t, err)

	req.Body = io.NopCloser(bytes.NewReader(bs))
	MakeRequest(t, req, http.StatusOK)

	req.Body = io.NopCloser(bytes.NewReader(bs))
	MakeRequest(t, req, http.StatusOK)

	// test with invalidation
	setting.OAuth2.InvalidateRefreshTokens = true
	req.Body = io.NopCloser(bytes.NewReader(bs))

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Apr 22, 2026

  1. DetectInvalidOAuth2ApplicationRedirectURI uses strings.Cut(u, ":"), which accepts pathological inputs like "my-scheme:" or "x:" (empty remainder). Prefer url.Parse and require a non-empty opaque/host.
  2. No test asserts that a scheme not in CustomSchemes (e.g. javascript:alert(1)) is rejected — worth adding since this is the security-critical path.
  3. middleware.Validate now runs AssignForm even when errs.Len() == 0, whereas it previously returned early. This affects every form in the app — either restore the early return or call out the intentional change.
  4. Dropping ValidUrlList means malformed http(s) URIs (bad ports, invalid IPv6, etc.) now pass validation since only the scheme token before : is checked. If intentional, worth documenting; otherwise run IsValidURL for http/https entries.

This comment was written with the help of Claude Opus 4.7

@wxiaoguang
Copy link
Copy Markdown
Contributor Author

  1. DetectInvalidOAuth2ApplicationRedirectURI uses strings.Cut(u, ":"), which accepts pathological inputs like "my-scheme:" or "x:" (empty remainder). Prefer url.Parse and require a non-empty opaque/host.

No need. No bad case

2. No test asserts that a scheme not in CustomSchemes (e.g. javascript:alert(1)) is rejected — worth adding since this is the security-critical path.

Site admin added it, they must like it. Just like they can run rm -rf /

3. middleware.Validate now runs AssignForm even when errs.Len() == 0, whereas it previously returned early. This affects every form in the app — either restore the early return or call out the intentional change.

What's wrong?

4. Dropping ValidUrlList means malformed http(s) URIs (bad ports, invalid IPv6, etc.) now pass validation since only the scheme token before : is checked. If intentional, worth documenting; otherwise run IsValidURL for http/https entries.

Totally useless.

@wxiaoguang
Copy link
Copy Markdown
Contributor Author

2. No test asserts that a scheme not in CustomSchemes (e.g. javascript:alert(1)) is rejected — worth adding since this is the security-critical path.

For the "tests", maybe your AI is still blind.

image

@wxiaoguang
Copy link
Copy Markdown
Contributor Author

  1. Dropping ValidUrlList means malformed http(s) URIs (bad ports, invalid IPv6, etc.) now pass validation since only the scheme token before : is checked. If intentional, worth documenting; otherwise run IsValidURL for http/https entries.

Totally useless.

Although it is useless, in case some newbie would complain that "Oh, the URL I inputted is invalid", added the IsValidURL check in 787d61e

@GiteaBot GiteaBot removed the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Apr 22, 2026
@GiteaBot GiteaBot added the lgtm/need 1 This PR needs approval from one additional maintainer to be merged. label Apr 22, 2026
@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Apr 22, 2026
@wxiaoguang wxiaoguang enabled auto-merge (squash) April 22, 2026 20:36
@wxiaoguang wxiaoguang added the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 22, 2026
@wxiaoguang wxiaoguang merged commit 83bdfc2 into go-gitea:main Apr 22, 2026
26 checks passed
@GiteaBot GiteaBot added this to the 1.27.0 milestone Apr 22, 2026
@GiteaBot GiteaBot removed the reviewed/wait-merge This pull request is part of the merge queue. It will be merged soon. label Apr 22, 2026
@wxiaoguang wxiaoguang deleted the fix-oauth2-scheme branch April 22, 2026 22:52
zjjhot added a commit to zjjhot/gitea that referenced this pull request Apr 23, 2026
* main:
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 23, 2026
* origin/main: (32 commits)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
  Add URL to `Learn more about blocking a user` (go-gitea#37355)
  fix: use TriggerEvent instead of Event in workflow runs API response for scheduled runs (go-gitea#37288)
  Add event.schedule context for schedule actions task (go-gitea#37320)
  Fix typos (go-gitea#37346)
  Fix an issue where changing an organization’s visibility caused problems when users had forked its repositories. (go-gitea#37324)
  Fail vite build on rolldown warnings via NODE_ENV=test (go-gitea#37270)
  Use modern "git update-index --cacheinfo" syntax to support more file names (go-gitea#37338)
  Fix URL related escaping for oauth2 (go-gitea#37334)
  When the requested arch rpm is missing fall back to noarch (go-gitea#37236)
  Fix `relative-time` error and improve global error handler (go-gitea#37241)
  Enhance styling in actions page (go-gitea#37323)
  fix(oauth): Error on auth sources with spaces (go-gitea#37327)
  Fix actions concurrency groups cross-branch leak (go-gitea#37311)
  ...

# Conflicts:
#	services/actions/commit_status.go
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 23, 2026
* origin/main: (204 commits)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
  Add URL to `Learn more about blocking a user` (go-gitea#37355)
  fix: use TriggerEvent instead of Event in workflow runs API response for scheduled runs (go-gitea#37288)
  Add event.schedule context for schedule actions task (go-gitea#37320)
  Fix typos (go-gitea#37346)
  Fix an issue where changing an organization’s visibility caused problems when users had forked its repositories. (go-gitea#37324)
  Fail vite build on rolldown warnings via NODE_ENV=test (go-gitea#37270)
  Use modern "git update-index --cacheinfo" syntax to support more file names (go-gitea#37338)
  Fix URL related escaping for oauth2 (go-gitea#37334)
  When the requested arch rpm is missing fall back to noarch (go-gitea#37236)
  Fix `relative-time` error and improve global error handler (go-gitea#37241)
  Enhance styling in actions page (go-gitea#37323)
  fix(oauth): Error on auth sources with spaces (go-gitea#37327)
  Fix actions concurrency groups cross-branch leak (go-gitea#37311)
  ...

# Conflicts:
#	web_src/js/index-domready.ts
#	web_src/js/markup/content.ts
#	web_src/js/markup/refissue.ts
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 23, 2026
* origin/main: (204 commits)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
  Add URL to `Learn more about blocking a user` (go-gitea#37355)
  fix: use TriggerEvent instead of Event in workflow runs API response for scheduled runs (go-gitea#37288)
  Add event.schedule context for schedule actions task (go-gitea#37320)
  Fix typos (go-gitea#37346)
  Fix an issue where changing an organization’s visibility caused problems when users had forked its repositories. (go-gitea#37324)
  Fail vite build on rolldown warnings via NODE_ENV=test (go-gitea#37270)
  Use modern "git update-index --cacheinfo" syntax to support more file names (go-gitea#37338)
  Fix URL related escaping for oauth2 (go-gitea#37334)
  When the requested arch rpm is missing fall back to noarch (go-gitea#37236)
  Fix `relative-time` error and improve global error handler (go-gitea#37241)
  Enhance styling in actions page (go-gitea#37323)
  fix(oauth): Error on auth sources with spaces (go-gitea#37327)
  Fix actions concurrency groups cross-branch leak (go-gitea#37311)
  ...

Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>

# Conflicts:
#	web_src/js/index-domready.ts
#	web_src/js/markup/content.ts
#	web_src/js/markup/refissue.ts
silverwind added a commit to mohammad-rj/gitea that referenced this pull request Apr 24, 2026
* origin/main: (127 commits)
  Refactor pull request view (1) (go-gitea#37380)
  Improve AGENTS.md (go-gitea#37382)
  Remove dead CSS (go-gitea#37376)
  Add pr-review e2e test and speed up e2e tests (go-gitea#37345)
  Drop Fomantic tab, checkbox and form patches (go-gitea#37377)
  fix: dump with default zip type produces uncompressed zip (go-gitea#37401)
  Allow fast-forward-only merge when signed commits are required (go-gitea#37335)
  Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119)
  Move review request functions to a standalone file (go-gitea#37358)
  Fix repo init README EOL (go-gitea#37388)
  Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365)
  Remove external service dependencies in migration tests (go-gitea#36866)
  Extend issue context popup beyond markdown content (go-gitea#36908)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  Fix cmd tests by mocking builtin paths (go-gitea#37369)
  chore: upgrade Go version in devcontainer image to 1.26 (go-gitea#37374)
  Fix button layout shift when collapsing file tree in editor (go-gitea#37363)
  Update `Block a user` form (go-gitea#37359)
  Remove IsValidExternalURL/IsAPIURL and use IsValidURL at call sites (go-gitea#37364)
  ...

# Conflicts:
#	modules/eventsource/event.go
#	tests/e2e/events.test.ts
silverwind added a commit to silverwind/gitea that referenced this pull request Apr 25, 2026
* origin/main: (51 commits)
  Fix color regressions, add `priority` color (go-gitea#37417)
  [skip ci] Updated translations via Crowdin
  Stabilize e2e logout propagation test (go-gitea#37403)
  refactor: serve site manifest via `/assets/site-manifest.json` endpoint (go-gitea#37405)
  feat(security): set X-Content-Type-Options: nosniff by default (go-gitea#37354)
  Refactor pull request view (1) (go-gitea#37380)
  Improve AGENTS.md (go-gitea#37382)
  Remove dead CSS (go-gitea#37376)
  Add pr-review e2e test and speed up e2e tests (go-gitea#37345)
  Drop Fomantic tab, checkbox and form patches (go-gitea#37377)
  fix: dump with default zip type produces uncompressed zip (go-gitea#37401)
  Allow fast-forward-only merge when signed commits are required (go-gitea#37335)
  Introduce `ActionRunAttempt` to represent each execution of a run (go-gitea#37119)
  Move review request functions to a standalone file (go-gitea#37358)
  Fix repo init README EOL (go-gitea#37388)
  Fix org team assignee/reviewer lookups for team member permissions (go-gitea#37365)
  Remove external service dependencies in migration tests (go-gitea#36866)
  Extend issue context popup beyond markdown content (go-gitea#36908)
  fix: commit status reporting (go-gitea#37372)
  Support for Custom URI Schemes in OAuth2 Redirect URIs (go-gitea#37356)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-update-needed The document needs to be updated synchronously lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Custom URI Schemes in OAuth2 Redirect URIs for Mobile App Integration

5 participants