diff --git a/release-notes/8.0.0/fix/4026.md b/release-notes/8.0.0/fix/4026.md
new file mode 100644
index 0000000000..747c3a789e
--- /dev/null
+++ b/release-notes/8.0.0/fix/4026.md
@@ -0,0 +1 @@
+- when an OAuth grant request submitted to a Forgejo user is denied, the server from which the request originates is not notified that it has been denied
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 1dd50a8968..b2d2abcf26 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -540,6 +540,16 @@ func GrantApplicationOAuth(ctx *context.Context) {
ctx.Error(http.StatusBadRequest)
return
}
+
+ if !form.Granted {
+ handleAuthorizeError(ctx, AuthorizeError{
+ State: form.State,
+ ErrorDescription: "the request is denied",
+ ErrorCode: ErrorCodeAccessDenied,
+ }, form.RedirectURI)
+ return
+ }
+
app, err := auth.GetOAuth2ApplicationByClientID(ctx, form.ClientID)
if err != nil {
ctx.ServerError("GetOAuth2ApplicationByClientID", err)
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index 4e603a3115..0b7bea4638 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -162,6 +162,7 @@ func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) bin
// GrantApplicationForm form for authorizing oauth2 clients
type GrantApplicationForm struct {
ClientID string `binding:"Required"`
+ Granted bool
RedirectURI string
State string
Scope string
diff --git a/templates/user/auth/grant.tmpl b/templates/user/auth/grant.tmpl
index cb9bba8749..a18a3bd27a 100644
--- a/templates/user/auth/grant.tmpl
+++ b/templates/user/auth/grant.tmpl
@@ -23,8 +23,8 @@
-
- Cancel
+
+
diff --git a/tests/integration/oauth_test.go b/tests/integration/oauth_test.go
index 1da1c6f9c0..cffb8b5814 100644
--- a/tests/integration/oauth_test.go
+++ b/tests/integration/oauth_test.go
@@ -554,3 +554,24 @@ func TestSignUpViaOAuthWithMissingFields(t *testing.T) {
resp := MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, test.RedirectURL(resp), "/user/link_account")
}
+
+func TestOAuth_GrantApplicationOAuth(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
+ ctx := loginUser(t, "user4")
+ resp := ctx.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+ htmlDoc.AssertElement(t, "#authorize-app", true)
+
+ req = NewRequestWithValues(t, "POST", "/login/oauth/grant", map[string]string{
+ "_csrf": htmlDoc.GetCSRF(),
+ "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
+ "redirect_uri": "a",
+ "state": "thestate",
+ "granted": "false",
+ })
+ resp = ctx.MakeRequest(t, req, http.StatusSeeOther)
+ assert.Contains(t, test.RedirectURL(resp), "error=access_denied&error_description=the+request+is+denied")
+}