fix: Increase valid user-supplied URL length to 1024 (#12585)

* fix: Increase valid user-supplied URL length to 1024

* fix: Wrap URL length migration in a transaction

Wrap the multi-column changeColumn operations in a transaction so a
failure on any column rolls back the whole migration rather than leaving
the database partially migrated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-06-04 23:30:55 -04:00
committed by GitHub
parent ce3d710888
commit 1cc10f5fff
4 changed files with 120 additions and 5 deletions
@@ -7,6 +7,7 @@ import { useTranslation, Trans } from "react-i18next";
import styled from "styled-components";
import { randomString } from "@shared/random";
import { TeamPreference } from "@shared/types";
import { WebhookSubscriptionValidation } from "@shared/validations";
import type WebhookSubscription from "~/models/WebhookSubscription";
import Button from "~/components/Button";
import Input from "~/components/Input";
@@ -229,6 +230,7 @@ function WebhookSubscriptionForm({ handleSubmit, webhookSubscription }: Props) {
required
flex
pattern={isCloudHosted ? "https://.*" : "https?://.*"}
maxLength={WebhookSubscriptionValidation.maxUrlLength}
placeholder="https://…"
label={t("URL")}
error={
@@ -238,7 +240,10 @@ function WebhookSubscriptionForm({ handleSubmit, webhookSubscription }: Props) {
)
: undefined
}
{...register("url", { required: true })}
{...register("url", {
required: true,
maxLength: WebhookSubscriptionValidation.maxUrlLength,
})}
/>
<Input
flex
+4
View File
@@ -1,10 +1,14 @@
import { z } from "zod";
import { WebhookSubscriptionValidation } from "@shared/validations";
import env from "@server/env";
import { WebhookSubscription } from "@server/models";
import { BaseSchema } from "@server/routes/api/schema";
const webhookUrl = z
.url()
.max(WebhookSubscriptionValidation.maxUrlLength, {
error: `Webhook url must be ${WebhookSubscriptionValidation.maxUrlLength} characters or less`,
})
.refine((val) => !env.isCloudHosted || val.startsWith("https://"), {
error: "Webhook url must use https",
});
@@ -0,0 +1,106 @@
"use strict";
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.changeColumn(
"webhook_subscriptions",
"url",
{
type: Sequelize.STRING(1024),
allowNull: false,
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"developerUrl",
{
type: Sequelize.STRING(1024),
allowNull: true,
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"avatarUrl",
{
type: Sequelize.STRING(1024),
allowNull: true,
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"redirectUris",
{
type: Sequelize.ARRAY(Sequelize.STRING(1024)),
allowNull: false,
defaultValue: [],
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_authorization_codes",
"redirectUri",
{
type: Sequelize.STRING(1024),
allowNull: false,
},
{ transaction }
);
});
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.changeColumn(
"oauth_authorization_codes",
"redirectUri",
{
type: Sequelize.STRING(255),
allowNull: false,
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"redirectUris",
{
type: Sequelize.ARRAY(Sequelize.STRING(255)),
allowNull: false,
defaultValue: [],
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"avatarUrl",
{
type: Sequelize.STRING(255),
allowNull: true,
},
{ transaction }
);
await queryInterface.changeColumn(
"oauth_clients",
"developerUrl",
{
type: Sequelize.STRING(255),
allowNull: true,
},
{ transaction }
);
await queryInterface.changeColumn(
"webhook_subscriptions",
"url",
{
type: Sequelize.STRING(255),
allowNull: false,
},
{ transaction }
);
});
},
};
+4 -4
View File
@@ -89,13 +89,13 @@ export const OAuthClientValidation = {
maxDeveloperNameLength: 100,
/** The maximum length of the OAuth client developer URL */
maxDeveloperUrlLength: 255,
maxDeveloperUrlLength: 1024,
/** The maximum length of the OAuth client avatar URL */
maxAvatarUrlLength: 255,
maxAvatarUrlLength: 1024,
/** The maximum length of an OAuth client redirect URI */
maxRedirectUriLength: 255,
maxRedirectUriLength: 1024,
/** The allowed OAuth client types */
clientTypes: ["confidential", "public"] as const,
@@ -170,7 +170,7 @@ export const WebhookSubscriptionValidation = {
/** The maximum length of the webhook name */
maxNameLength: 255,
/** The maximum length of the webhook url */
maxUrlLength: 255,
maxUrlLength: 1024,
};
export const EmojiValidation = {