diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml deleted file mode 100644 index 0484bcab..00000000 --- a/.github/workflows/staging.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Staging build bundle - -on: - push: - branches: ["staging"] - -jobs: - tests: - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - - - name: Install NPM packages - run: | - bun install - - - name: Build dist - run: | - bun run build - - - name: Bundle - run: | - mkdir bundle - cp -r dist bundle/ - cp -r config bundle/ - cp -r docs bundle/ - cp -r CODE_OF_CONDUCT.md bundle/ - cp -r CONTRIBUTING.md bundle/ - cp -r README.md bundle/ - cp -r flake.nix bundle/ - cp -r shell.nix bundle/ - cp -r flake.lock bundle/ - cp -r LICENSE bundle/ - cp -r SECURITY.md bundle/ - tar cfJ archive.tar.xz bundle/ - - - name: Upload - uses: actions/upload-artifact@v4 - with: - name: staging-dist - path: archive.tar.xz diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish.yml new file mode 100644 index 00000000..1d29d88b --- /dev/null +++ b/.github/workflows/test-publish.yml @@ -0,0 +1,33 @@ +name: Test Publish + +on: + push: + +permissions: + contents: read + # For provenance generation + id-token: write + +jobs: + # Build job + build: + runs-on: ubuntu-latest + strategy: + matrix: + package: ["sdk", "client"] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install + run: bun install + + - name: Publish to NPM + run: bun publish --dry-run + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish to JSR + run: bunx jsr publish --allow-slow-types --allow-dirty --dry-run diff --git a/api/api/v1/accounts/update_credentials/index.ts b/api/api/v1/accounts/update_credentials/index.ts index 5568f5ad..6c48c04f 100644 --- a/api/api/v1/accounts/update_credentials/index.ts +++ b/api/api/v1/accounts/update_credentials/index.ts @@ -52,17 +52,56 @@ export default apiRoute((app) => "json", z .object({ - display_name: AccountSchema.shape.display_name.openapi({ - description: "The display name to use for the profile.", - example: "Lexi", - }), - username: AccountSchema.shape.username.openapi({ - description: "The username to use for the profile.", - example: "lexi", - }), - note: AccountSchema.shape.note.openapi({ - description: "The account bio. Markdown is supported.", - }), + display_name: AccountSchema.shape.display_name + .openapi({ + description: + "The display name to use for the profile.", + example: "Lexi", + }) + .max( + config.validation.accounts + .max_displayname_characters, + ) + .refine( + (s) => + !config.validation.filters.displayname.some( + (filter) => filter.test(s), + ), + "Display name contains blocked words", + ), + username: AccountSchema.shape.username + .openapi({ + description: "The username to use for the profile.", + example: "lexi", + }) + .max(config.validation.accounts.max_username_characters) + .refine( + (s) => + !config.validation.filters.username.some( + (filter) => filter.test(s), + ), + "Username contains blocked words", + ) + .refine( + (s) => + !config.validation.accounts.disallowed_usernames.some( + (u) => u.test(s), + ), + "Username is disallowed", + ), + note: AccountSchema.shape.note + .openapi({ + description: + "The account bio. Markdown is supported.", + }) + .max(config.validation.accounts.max_bio_characters) + .refine( + (s) => + !config.validation.filters.bio.some((filter) => + filter.test(s), + ), + "Bio contains blocked words", + ), avatar: z .string() .url() @@ -150,10 +189,14 @@ export default apiRoute((app) => fields_attributes: z .array( z.object({ - name: AccountSchema.shape.fields.element.shape - .name, - value: AccountSchema.shape.fields.element.shape - .value, + name: AccountSchema.shape.fields.element.shape.name.max( + config.validation.accounts + .max_field_name_characters, + ), + value: AccountSchema.shape.fields.element.shape.value.max( + config.validation.accounts + .max_field_value_characters, + ), }), ) .max(config.validation.accounts.max_field_count), diff --git a/api/api/v1/emojis/[id]/index.ts b/api/api/v1/emojis/[id]/index.ts index b5212522..e0a01504 100644 --- a/api/api/v1/emojis/[id]/index.ts +++ b/api/api/v1/emojis/[id]/index.ts @@ -113,7 +113,9 @@ export default apiRoute((app) => { "json", z .object({ - shortcode: CustomEmojiSchema.shape.shortcode, + shortcode: CustomEmojiSchema.shape.shortcode.max( + config.validation.emojis.max_shortcode_characters, + ), element: z .string() .url() @@ -136,7 +138,12 @@ export default apiRoute((app) => { ), ), category: CustomEmojiSchema.shape.category.optional(), - alt: CustomEmojiSchema.shape.description.optional(), + alt: CustomEmojiSchema.shape.description + .unwrap() + .max( + config.validation.emojis.max_description_characters, + ) + .optional(), global: CustomEmojiSchema.shape.global.default(false), }) .partial(), diff --git a/api/api/v1/emojis/index.ts b/api/api/v1/emojis/index.ts index 8802d79f..9e279efe 100644 --- a/api/api/v1/emojis/index.ts +++ b/api/api/v1/emojis/index.ts @@ -45,7 +45,9 @@ export default apiRoute((app) => validator( "json", z.object({ - shortcode: CustomEmojiSchema.shape.shortcode, + shortcode: CustomEmojiSchema.shape.shortcode.max( + config.validation.emojis.max_shortcode_characters, + ), element: z .string() .url() @@ -68,7 +70,10 @@ export default apiRoute((app) => ), ), category: CustomEmojiSchema.shape.category.optional(), - alt: CustomEmojiSchema.shape.description.optional(), + alt: CustomEmojiSchema.shape.description + .unwrap() + .max(config.validation.emojis.max_description_characters) + .optional(), global: CustomEmojiSchema.shape.global.default(false), }), handleZodError, diff --git a/api/api/v1/media/[id]/index.ts b/api/api/v1/media/[id]/index.ts index 089065fd..947dc701 100644 --- a/api/api/v1/media/[id]/index.ts +++ b/api/api/v1/media/[id]/index.ts @@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod"; import { z } from "zod"; import { apiRoute, auth, handleZodError } from "@/api"; import { ApiError } from "~/classes/errors/api-error"; +import { config } from "~/config"; export default apiRoute((app) => { app.get( @@ -109,7 +110,10 @@ export default apiRoute((app) => { description: "The custom thumbnail of the media to be attached, encoded using multipart form data.", }), - description: AttachmentSchema.shape.description, + description: AttachmentSchema.shape.description + .unwrap() + .max(config.validation.media.max_description_characters) + .optional(), focus: z.string().openapi({ description: "Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0. Used for media cropping on clients.", diff --git a/api/api/v1/media/index.ts b/api/api/v1/media/index.ts index 0ef53d89..711a2fe1 100644 --- a/api/api/v1/media/index.ts +++ b/api/api/v1/media/index.ts @@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod"; import { z } from "zod"; import { apiRoute, auth, handleZodError } from "@/api"; import { ApiError } from "~/classes/errors/api-error"; +import { config } from "~/config"; export default apiRoute((app) => app.post( @@ -67,7 +68,10 @@ export default apiRoute((app) => description: "The custom thumbnail of the media to be attached, encoded using multipart form data.", }), - description: AttachmentSchema.shape.description.optional(), + description: AttachmentSchema.shape.description + .unwrap() + .max(config.validation.media.max_description_characters) + .optional(), focus: z .string() .optional() diff --git a/api/api/v1/statuses/[id]/index.ts b/api/api/v1/statuses/[id]/index.ts index bedf5358..350a2862 100644 --- a/api/api/v1/statuses/[id]/index.ts +++ b/api/api/v1/statuses/[id]/index.ts @@ -25,10 +25,20 @@ import * as VersiaEntities from "~/packages/sdk/entities"; const schema = z .object({ - status: StatusSourceSchema.shape.text.optional().openapi({ - description: - "The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.", - }), + status: StatusSourceSchema.shape.text + .max(config.validation.notes.max_characters) + .refine( + (s) => + !config.validation.filters.note_content.some((filter) => + filter.test(s), + ), + "Status contains blocked words", + ) + .optional() + .openapi({ + description: + "The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.", + }), /* Versia Server API Extension */ content_type: z .enum(["text/plain", "text/html", "text/markdown"]) @@ -54,7 +64,11 @@ const schema = z }), language: StatusSchema.shape.language.optional(), "poll[options]": z - .array(PollOption.shape.title) + .array( + PollOption.shape.title.max( + config.validation.polls.max_option_characters, + ), + ) .max(config.validation.polls.max_options) .optional() .openapi({ diff --git a/api/api/v1/statuses/index.ts b/api/api/v1/statuses/index.ts index 62de4771..7bcc0dd5 100644 --- a/api/api/v1/statuses/index.ts +++ b/api/api/v1/statuses/index.ts @@ -20,10 +20,20 @@ import * as VersiaEntities from "~/packages/sdk/entities"; const schema = z .object({ - status: StatusSourceSchema.shape.text.optional().openapi({ - description: - "The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.", - }), + status: StatusSourceSchema.shape.text + .max(config.validation.notes.max_characters) + .refine( + (s) => + !config.validation.filters.note_content.some((filter) => + filter.test(s), + ), + "Status contains blocked words", + ) + .optional() + .openapi({ + description: + "The text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.", + }), /* Versia Server API Extension */ content_type: z .enum(["text/plain", "text/html", "text/markdown"]) @@ -49,7 +59,11 @@ const schema = z }), language: StatusSchema.shape.language.optional(), "poll[options]": z - .array(PollOption.shape.title) + .array( + PollOption.shape.title.max( + config.validation.polls.max_option_characters, + ), + ) .max(config.validation.polls.max_options) .optional() .openapi({ diff --git a/api/api/v2/media/index.ts b/api/api/v2/media/index.ts index f5e14987..9f42a6ee 100644 --- a/api/api/v2/media/index.ts +++ b/api/api/v2/media/index.ts @@ -8,6 +8,7 @@ import { resolver, validator } from "hono-openapi/zod"; import { z } from "zod"; import { apiRoute, auth, handleZodError } from "@/api"; import { ApiError } from "~/classes/errors/api-error"; +import { config } from "~/config"; export default apiRoute((app) => app.post( @@ -76,7 +77,10 @@ export default apiRoute((app) => description: "The custom thumbnail of the media to be attached, encoded using multipart form data.", }), - description: AttachmentSchema.shape.description.optional(), + description: AttachmentSchema.shape.description + .unwrap() + .max(config.validation.media.max_description_characters) + .optional(), focus: z .string() .optional() diff --git a/api/api/v2/search/index.ts b/api/api/v2/search/index.ts index 329a9779..1e24cbd7 100644 --- a/api/api/v2/search/index.ts +++ b/api/api/v2/search/index.ts @@ -3,6 +3,7 @@ import { Id, RolePermission, Search as SearchSchema, + userAddressRegex, zBoolean, } from "@versia/client/schemas"; import { db, Note, User } from "@versia/kit/db"; @@ -11,13 +12,7 @@ import { and, eq, inArray, isNull, sql } from "drizzle-orm"; import { describeRoute } from "hono-openapi"; import { resolver, validator } from "hono-openapi/zod"; import { z } from "zod"; -import { - apiRoute, - auth, - handleZodError, - parseUserAddress, - userAddressValidator, -} from "@/api"; +import { apiRoute, auth, handleZodError, parseUserAddress } from "@/api"; import { ApiError } from "~/classes/errors/api-error"; import { searchManager } from "~/classes/search/search-manager"; import { config } from "~/config.ts"; @@ -151,7 +146,7 @@ export default apiRoute((app) => if (!type || type === "accounts") { // Check if q is matching format username@domain.com or @username@domain.com - const accountMatches = q?.trim().match(userAddressValidator); + const accountMatches = q?.trim().match(userAddressRegex); if (accountMatches) { // Remove leading @ if it exists if (accountMatches[0].startsWith("@")) { diff --git a/bun.lock b/bun.lock index 166a95bb..849502b3 100644 --- a/bun.lock +++ b/bun.lock @@ -92,6 +92,7 @@ "dependencies": { "@badgateway/oauth2-client": "^3.0.0", "iso-639-1": "^3.1.5", + "magic-regexp": "^0.10.0", "zod": "^3.24.2", "zod-openapi": "^4.2.4", }, diff --git a/classes/config/schema.ts b/classes/config/schema.ts index 6fe8af80..ed73ad00 100644 --- a/classes/config/schema.ts +++ b/classes/config/schema.ts @@ -2,8 +2,7 @@ import { type BunFile, env, file } from "bun"; import ISO6391 from "iso-639-1"; import { types as mimeTypes } from "mime-types"; import { generateVAPIDKeys } from "web-push"; -import { ZodError, z } from "zod"; -import { fromZodError } from "zod-validation-error"; +import { z } from "zod"; import { ProxiableUrl } from "~/classes/media/url.ts"; import { RolePermission } from "~/packages/client/schemas/permissions.ts"; @@ -271,16 +270,6 @@ export const hmacKey = sensitiveString.transform(async (text, ctx) => { return text; }); -try { - console.info(); -} catch (e) { - if (e instanceof ZodError) { - throw fromZodError(e); - } - - throw e; -} - export const ConfigSchema = z .strictObject({ postgres: z diff --git a/classes/database/emoji.ts b/classes/database/emoji.ts index cc1b952a..00715305 100644 --- a/classes/database/emoji.ts +++ b/classes/database/emoji.ts @@ -1,4 +1,8 @@ -import type { CustomEmoji } from "@versia/client/schemas"; +import { + type CustomEmoji, + emojiWithColonsRegex, + emojiWithIdentifiersRegex, +} from "@versia/client/schemas"; import { db, type Instance, Media } from "@versia/kit/db"; import { Emojis, type Instances, type Medias } from "@versia/kit/tables"; import { randomUUIDv7 } from "bun"; @@ -13,7 +17,6 @@ import { type SQL, } from "drizzle-orm"; import type { z } from "zod"; -import { emojiValidatorWithColons, emojiValidatorWithIdentifiers } from "@/api"; import * as VersiaEntities from "~/packages/sdk/entities/index.ts"; import type { ImageContentFormatSchema } from "~/packages/sdk/schemas/index.ts"; import { BaseInterface } from "./base.ts"; @@ -162,7 +165,7 @@ export class Emoji extends BaseInterface { * @returns An array of emojis */ public static parseFromText(text: string): Promise { - const matches = text.match(emojiValidatorWithColons); + const matches = text.match(emojiWithColonsRegex); if (!matches || matches.length === 0) { return Promise.resolve([]); } @@ -213,9 +216,8 @@ export class Emoji extends BaseInterface { instance: Instance, ): Promise { // Extracts the shortcode from the emoji name (e.g. :shortcode: -> shortcode) - const shortcode = [ - ...emoji.name.matchAll(emojiValidatorWithIdentifiers), - ][0].groups.shortcode; + const shortcode = [...emoji.name.matchAll(emojiWithIdentifiersRegex)][0] + .groups.shortcode; if (!shortcode) { throw new Error("Could not extract shortcode from emoji name"); diff --git a/packages/client/package.json b/packages/client/package.json index d8bab4af..4a43bf2f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -65,6 +65,7 @@ "dependencies": { "@badgateway/oauth2-client": "^3.0.0", "iso-639-1": "^3.1.5", + "magic-regexp": "^0.10.0", "zod": "^3.24.2", "zod-openapi": "^4.2.4" } diff --git a/packages/client/regex.ts b/packages/client/regex.ts new file mode 100644 index 00000000..5f937e83 --- /dev/null +++ b/packages/client/regex.ts @@ -0,0 +1,48 @@ +import { + anyOf, + caseInsensitive, + charIn, + charNotIn, + createRegExp, + digit, + exactly, + global, + letter, + maybe, + not, + oneOrMore, +} from "magic-regexp"; + +export const userAddressRegex = createRegExp( + maybe("@"), + oneOrMore(anyOf(letter.lowercase, digit, charIn("-_"))).groupedAs( + "username", + ), + maybe( + exactly("@"), + oneOrMore(anyOf(letter, digit, charIn("_-.:"))).groupedAs("domain"), + ), + [global], +); + +export const emojiRegex = createRegExp( + // A-Z a-z 0-9 _ - + oneOrMore(letter.or(digit).or(charIn("_-"))), + [caseInsensitive, global], +); + +export const emojiWithColonsRegex = createRegExp( + exactly(":"), + oneOrMore(letter.or(digit).or(charIn("_-"))), + exactly(":"), + [caseInsensitive, global], +); + +export const emojiWithIdentifiersRegex = createRegExp( + exactly( + exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1), + oneOrMore(letter.or(digit).or(charIn("_-"))).groupedAs("shortcode"), + exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1), + ), + [caseInsensitive, global], +); diff --git a/packages/client/schemas.ts b/packages/client/schemas.ts index 9472ef02..7705836e 100644 --- a/packages/client/schemas.ts +++ b/packages/client/schemas.ts @@ -1,3 +1,9 @@ +export { + emojiRegex, + emojiWithColonsRegex, + emojiWithIdentifiersRegex, + userAddressRegex, +} from "./regex.ts"; export { Account, Field, Source } from "./schemas/account.ts"; export { AccountWarning } from "./schemas/account-warning.ts"; export { Appeal } from "./schemas/appeal.ts"; diff --git a/packages/client/schemas/account.ts b/packages/client/schemas/account.ts index 5d746df3..84f9c83c 100644 --- a/packages/client/schemas/account.ts +++ b/packages/client/schemas/account.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { userAddressValidator } from "@/api.ts"; -import { config } from "~/config.ts"; +import { userAddressRegex } from "../regex.ts"; import { iso631, zBoolean } from "./common.ts"; import { CustomEmoji } from "./emoji.ts"; import { Role } from "./versia.ts"; @@ -11,7 +10,6 @@ export const Field = z .string() .trim() .min(1) - .max(config.validation.accounts.max_field_name_characters) .openapi({ description: "The key of a given field’s key-value pair.", example: "Freak level", @@ -23,7 +21,6 @@ export const Field = z .string() .trim() .min(1) - .max(config.validation.accounts.max_field_value_characters) .openapi({ description: "The value associated with the name key.", example: "

High

", @@ -87,14 +84,6 @@ export const Source = z .string() .trim() .min(0) - .max(config.validation.accounts.max_bio_characters) - .refine( - (s) => - !config.validation.filters.bio.some((filter) => - filter.test(s), - ), - "Bio contains blocked words", - ) .openapi({ description: "Profile bio, in plain-text instead of in HTML.", example: "ermmm what the meow meow", @@ -102,12 +91,9 @@ export const Source = z url: "https://docs.joinmastodon.org/entities/Account/#source-note", }, }), - fields: z - .array(Field) - .max(config.validation.accounts.max_field_count) - .openapi({ - description: "Metadata about the account.", - }), + fields: z.array(Field).openapi({ + description: "Metadata about the account.", + }), }) .openapi({ description: @@ -135,25 +121,10 @@ const BaseAccount = z .string() .min(3) .trim() - .max(config.validation.accounts.max_username_characters) .regex( /^[a-z0-9_-]+$/, "Username can only contain letters, numbers, underscores and hyphens", ) - .refine( - (s) => - !config.validation.filters.username.some((filter) => - filter.test(s), - ), - "Username contains blocked words", - ) - .refine( - (s) => - !config.validation.accounts.disallowed_usernames.some((u) => - u.test(s), - ), - "Username is disallowed", - ) .openapi({ description: "The username of the account, not including domain.", @@ -166,7 +137,7 @@ const BaseAccount = z .string() .min(1) .trim() - .regex(userAddressValidator, "Invalid user address") + .regex(userAddressRegex, "Invalid user address") .openapi({ description: "The Webfinger account URI. Equal to username for local users, or username@domain for remote users.", @@ -189,14 +160,6 @@ const BaseAccount = z .string() .min(3) .trim() - .max(config.validation.accounts.max_displayname_characters) - .refine( - (s) => - !config.validation.filters.displayname.some((filter) => - filter.test(s), - ), - "Display name contains blocked words", - ) .openapi({ description: "The profile’s display name.", example: "Lexi :flower:", @@ -207,15 +170,7 @@ const BaseAccount = z note: z .string() .min(0) - .max(config.validation.accounts.max_bio_characters) .trim() - .refine( - (s) => - !config.validation.filters.bio.some((filter) => - filter.test(s), - ), - "Bio contains blocked words", - ) .openapi({ description: "The profile’s bio or description.", example: "

ermmm what the meow meow

", @@ -279,16 +234,13 @@ const BaseAccount = z url: "https://docs.joinmastodon.org/entities/Account/#locked", }, }), - fields: z - .array(Field) - .max(config.validation.accounts.max_field_count) - .openapi({ - description: - "Additional metadata attached to a profile as name-value pairs.", - externalDocs: { - url: "https://docs.joinmastodon.org/entities/Account/#fields", - }, - }), + fields: z.array(Field).openapi({ + description: + "Additional metadata attached to a profile as name-value pairs.", + externalDocs: { + url: "https://docs.joinmastodon.org/entities/Account/#fields", + }, + }), emojis: z.array(CustomEmoji).openapi({ description: "Custom emoji entities to be used when rendering the profile.", diff --git a/packages/client/schemas/attachment.ts b/packages/client/schemas/attachment.ts index 238f1159..76c9ce7e 100644 --- a/packages/client/schemas/attachment.ts +++ b/packages/client/schemas/attachment.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { config } from "~/config.ts"; import { Id } from "./common.ts"; export const Attachment = z @@ -51,16 +50,11 @@ export const Attachment = z }, }, }), - description: z - .string() - .trim() - .max(config.validation.media.max_description_characters) - .nullable() - .openapi({ - description: - "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load.", - example: "test media description", - }), + description: z.string().trim().nullable().openapi({ + description: + "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load.", + example: "test media description", + }), blurhash: z.string().nullable().openapi({ description: "A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.", diff --git a/packages/client/schemas/emoji.ts b/packages/client/schemas/emoji.ts index 333e4a05..5a1f8aae 100644 --- a/packages/client/schemas/emoji.ts +++ b/packages/client/schemas/emoji.ts @@ -1,6 +1,5 @@ import { z } from "zod"; -import { emojiValidator } from "@/api.ts"; -import { config } from "~/config.ts"; +import { emojiRegex } from "../regex.ts"; import { Id, zBoolean } from "./common.ts"; export const CustomEmoji = z @@ -14,9 +13,8 @@ export const CustomEmoji = z .string() .trim() .min(1) - .max(config.validation.emojis.max_shortcode_characters) .regex( - emojiValidator, + emojiRegex, "Shortcode must only contain letters (any case), numbers, dashes or underscores.", ) .openapi({ @@ -76,7 +74,6 @@ export const CustomEmoji = z /* Versia Server API extension */ description: z .string() - .max(config.validation.emojis.max_description_characters) .nullable() .openapi({ description: diff --git a/packages/client/schemas/instance.ts b/packages/client/schemas/instance.ts index d5ad2c02..bde4f485 100644 --- a/packages/client/schemas/instance.ts +++ b/packages/client/schemas/instance.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import pkg from "~/package.json"; import { Account } from "./account.ts"; import { iso631 } from "./common.ts"; import { Rule } from "./rule.ts"; @@ -48,7 +47,7 @@ export const Instance = z source_url: z.string().url().openapi({ description: "The URL for the source code of the software running on this instance, in keeping with AGPL license requirements.", - example: pkg.repository.url, + example: "https://github.com/versia-pub/server", }), description: z.string().openapi({ description: diff --git a/packages/client/schemas/poll.ts b/packages/client/schemas/poll.ts index 45507fd0..a2643727 100644 --- a/packages/client/schemas/poll.ts +++ b/packages/client/schemas/poll.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { config } from "~/config.ts"; import { Id } from "./common.ts"; import { CustomEmoji } from "./emoji.ts"; @@ -9,7 +8,6 @@ export const PollOption = z .string() .trim() .min(1) - .max(config.validation.polls.max_option_characters) .openapi({ description: "The text value of the poll option.", example: "yes", diff --git a/packages/client/schemas/status.ts b/packages/client/schemas/status.ts index 28376953..60623f1c 100644 --- a/packages/client/schemas/status.ts +++ b/packages/client/schemas/status.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { config } from "~/config.ts"; import { Account } from "./account.ts"; import { Attachment } from "./attachment.ts"; import { PreviewCard } from "./card.ts"; @@ -55,21 +54,10 @@ export const StatusSource = z description: "ID of the status in the database.", example: "c7db92a4-e472-4e94-a115-7411ee934ba1", }), - text: z - .string() - .max(config.validation.notes.max_characters) - .trim() - .refine( - (s) => - !config.validation.filters.note_content.some((filter) => - filter.test(s), - ), - "Status contains blocked words", - ) - .openapi({ - description: "The plain text used to compose the status.", - example: "this is a status that will be edited", - }), + text: z.string().trim().openapi({ + description: "The plain text used to compose the status.", + example: "this is a status that will be edited", + }), spoiler_text: z.string().trim().min(1).max(1024).openapi({ description: "The plain text used to compose the status’s subject or content warning.", diff --git a/packages/client/schemas/versia.ts b/packages/client/schemas/versia.ts index 5359dd0b..e49f511a 100644 --- a/packages/client/schemas/versia.ts +++ b/packages/client/schemas/versia.ts @@ -1,5 +1,4 @@ import { z } from "zod"; -import { config } from "~/config.ts"; import { Id } from "./common.ts"; import { RolePermission } from "./permissions.ts"; @@ -55,15 +54,10 @@ export const Role = z /* Versia Server API extension */ export const NoteReaction = z .object({ - name: z - .string() - .min(1) - .max(config.validation.emojis.max_shortcode_characters) - .trim() - .openapi({ - description: "Custom Emoji shortcode or Unicode emoji.", - example: "blobfox_coffee", - }), + name: z.string().min(1).trim().openapi({ + description: "Custom Emoji shortcode or Unicode emoji.", + example: "blobfox_coffee", + }), count: z.number().int().nonnegative().openapi({ description: "Number of users who reacted with this emoji.", example: 5, diff --git a/utils/api.ts b/utils/api.ts index 70434350..56f81b1e 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -14,14 +14,12 @@ import { anyOf, caseInsensitive, charIn, - charNotIn, createRegExp, digit, exactly, global, letter, maybe, - not, oneOrMore, } from "magic-regexp"; import { type ParsedQs, parse } from "qs"; @@ -49,28 +47,6 @@ export const idValidator = createRegExp( [caseInsensitive], ); -export const emojiValidator = createRegExp( - // A-Z a-z 0-9 _ - - oneOrMore(letter.or(digit).or(charIn("_-"))), - [caseInsensitive, global], -); - -export const emojiValidatorWithColons = createRegExp( - exactly(":"), - oneOrMore(letter.or(digit).or(charIn("_-"))), - exactly(":"), - [caseInsensitive, global], -); - -export const emojiValidatorWithIdentifiers = createRegExp( - exactly( - exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1), - oneOrMore(letter.or(digit).or(charIn("_-"))).groupedAs("shortcode"), - exactly(not.letter.or(not.digit).or(charNotIn("_-"))).times(1), - ), - [caseInsensitive, global], -); - export const mentionValidator = createRegExp( exactly("@"), oneOrMore(anyOf(letter.lowercase, digit, charIn("-_"))).groupedAs( @@ -83,28 +59,6 @@ export const mentionValidator = createRegExp( [global], ); -export const userAddressValidator = createRegExp( - maybe("@"), - oneOrMore(anyOf(letter.lowercase, digit, charIn("-_"))).groupedAs( - "username", - ), - maybe( - exactly("@"), - oneOrMore(anyOf(letter, digit, charIn("_-.:"))).groupedAs("domain"), - ), - [global], -); - -export const userAddressValidatorRemote = createRegExp( - maybe("@"), - oneOrMore(anyOf(letter.lowercase, digit, charIn("-_"))).groupedAs( - "username", - ), - exactly("@"), - oneOrMore(anyOf(letter, digit, charIn("_-.:"))).groupedAs("domain"), - [global], -); - export const webfingerMention = createRegExp( exactly("acct:"), oneOrMore(anyOf(letter, digit, charIn("-_"))).groupedAs("username"),