{{ctx.Locale.Tr "settings.authorized_integration.delete.body"}}
+diff --git a/models/auth/authorized_integration.go b/models/auth/authorized_integration.go
index 6079d28341..91415b626d 100644
--- a/models/auth/authorized_integration.go
+++ b/models/auth/authorized_integration.go
@@ -269,3 +269,29 @@ func ParseAuthorizedIntegrationUI(ui string) (AuthorizedIntegrationUI, error) {
}
return AuthorizedIntegrationUI(""), fmt.Errorf("invalid authorized integration UI: %q", ui)
}
+
+// Delete an authorized integration by ID. Must only succeed if the authorized integration identified is owned by the
+// user provided.
+func DeleteAuthorizedIntegrationByID(ctx context.Context, id, userID int64) error {
+ return db.WithTx(ctx, func(ctx context.Context) error {
+ // Delete doesn't take into account userID, but will be rolled back by the transaction if the user ID isn't
+ // correct. Needs to occur first due to foreign key.
+ if err := db.DeleteBeans(ctx,
+ &AuthorizedIntegResourceRepo{IntegID: id},
+ ); err != nil {
+ return fmt.Errorf("DeleteBeans: %w", err)
+ }
+
+ cnt, err := db.GetEngine(ctx).
+ ID(id).
+ Delete(&AuthorizedIntegration{
+ UserID: userID,
+ })
+ if err != nil {
+ return err
+ } else if cnt != 1 {
+ return fmt.Errorf("authorized integration %d does not exist: %w", id, util.ErrNotExist)
+ }
+ return nil
+ })
+}
diff --git a/models/auth/authorized_integration_test.go b/models/auth/authorized_integration_test.go
index c77fb32cc6..e2581bff34 100644
--- a/models/auth/authorized_integration_test.go
+++ b/models/auth/authorized_integration_test.go
@@ -193,3 +193,37 @@ func TestUpdateAuthorizedIntegration(t *testing.T) {
assert.Equal(t, createdUnix, fromDB.CreatedUnix)
assert.Equal(t, updatedUnix, fromDB.UpdatedUnix) // not changed -- used to track usage for authentication
}
+
+func TestDeleteAuthorizedIntegrationByID(t *testing.T) {
+ require.NoError(t, unittest.PrepareTestDatabase())
+ t.Run("simple delete", func(t *testing.T) {
+ ai := makeAuthorizedIntegration(t)
+ err := auth_model.DeleteAuthorizedIntegrationByID(t.Context(), ai.ID, ai.UserID)
+ require.NoError(t, err)
+ unittest.AssertNotExistsBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID})
+ })
+
+ t.Run("delete repo-specific", func(t *testing.T) {
+ ai := makeAuthorizedIntegration(t)
+ resRepo1 := &auth_model.AuthorizedIntegResourceRepo{
+ IntegID: ai.ID,
+ RepoID: 1,
+ }
+ err := auth_model.InsertAuthorizedIntegrationResourceRepos(t.Context(), ai.ID,
+ []*auth_model.AuthorizedIntegResourceRepo{resRepo1})
+ require.NoError(t, err)
+ unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai.ID}, 1)
+
+ err = auth_model.DeleteAuthorizedIntegrationByID(t.Context(), ai.ID, ai.UserID)
+ require.NoError(t, err)
+
+ unittest.AssertNotExistsBean(t, &auth_model.AuthorizedIntegration{ID: ai.ID})
+ unittest.AssertCount(t, &auth_model.AuthorizedIntegResourceRepo{IntegID: ai.ID}, 0)
+ })
+
+ t.Run("delete fails on wrong user", func(t *testing.T) {
+ ai := makeAuthorizedIntegration(t)
+ err := auth_model.DeleteAuthorizedIntegrationByID(t.Context(), ai.ID, 300)
+ require.ErrorIs(t, err, util.ErrNotExist)
+ })
+}
diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json
index 0fa86ac194..6e1b4d23dd 100644
--- a/options/locale_next/locale_en-US.json
+++ b/options/locale_next/locale_en-US.json
@@ -348,6 +348,9 @@
"settings.authorized_integration.specified_repos_and_invalid_scope": "Authorized integrations with specified repositories can only be used with the read:issue, write:issue, read:repository, and write:repository scopes.",
"settings.authorized_integration.add": "Add authorized integration",
"settings.authorized_integration.generic": "Generic JWT Source",
+ "settings.authorized_integration.delete.header": "Delete authorized integration",
+ "settings.authorized_integration.delete.body": "Deleting an authorized integration will revoke access to your account for the integrating application. This is permanent, and cannot be undone. Creating a new authorized integration will not have the same Audience (aud) claim. Continue?",
+ "settings.authorized_integration.deleted": "Authorized integration has been deleted successfully.",
"webauthn.insert_key": "Insert your security key",
"webauthn.sign_in": "Press the button on your security key. If your security key has no button, re-insert it.",
"webauthn.press_button": "Please press the button on your security key…",
diff --git a/routers/web/user/setting/authorized_integrations.go b/routers/web/user/setting/authorized_integrations.go
index 45a1419034..f7e6cc7f96 100644
--- a/routers/web/user/setting/authorized_integrations.go
+++ b/routers/web/user/setting/authorized_integrations.go
@@ -442,3 +442,12 @@ func repoMultiSelect(ctx *context.Context) {
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
}
+
+func DeleteAuthorizedIntegration(ctx *context.Context) {
+ if err := auth_model.DeleteAuthorizedIntegrationByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil {
+ ctx.Flash.Error("DeleteAuthorizedIntegrationByID: " + err.Error())
+ } else {
+ ctx.Flash.Success(ctx.Tr("settings.authorized_integration.deleted"))
+ }
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/authorized-integrations")
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index b8d0c25ff9..bf9bb9be6e 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -680,6 +680,7 @@ func registerRoutes(m *web.Route) {
Get(web.Bind(user_setting.AuthorizedIntegrationForm{}), user_setting.EditAuthorizedIntegration).
Post(web.Bind(user_setting.AuthorizedIntegrationForm{}), user_setting.EditAuthorizedIntegrationPost)
})
+ m.Post("/delete", user_setting.DeleteAuthorizedIntegration)
m.Get("", user_setting.ListAuthorizedIntegrations)
})
diff --git a/templates/user/settings/authorized_integrations.tmpl b/templates/user/settings/authorized_integrations.tmpl
index 9d27ae4261..b3c9700357 100644
--- a/templates/user/settings/authorized_integrations.tmpl
+++ b/templates/user/settings/authorized_integrations.tmpl
@@ -39,6 +39,10 @@
{{svg "octicon-pencil" 16 "tw-mr-1"}}
{{ctx.Locale.Tr "settings.authorized_integration.edit"}}
+
{{else}}
@@ -54,4 +58,15 @@
+
{{ctx.Locale.Tr "settings.authorized_integration.delete.body"}}
+