feat: add a list of reserved words for username, subdomain and page slug

This commit is contained in:
EGOIST 2022-05-16 23:03:05 +08:00
parent 0b2c0f0d80
commit 9e313e3e0e
7 changed files with 575 additions and 20 deletions

540
src/lib/reserved-words.ts Normal file
View File

@ -0,0 +1,540 @@
const words = [
"400",
"401",
"403",
"404",
"405",
"406",
"407",
"408",
"409",
"410",
"411",
"412",
"413",
"414",
"415",
"416",
"417",
"421",
"422",
"423",
"424",
"426",
"428",
"429",
"431",
"500",
"501",
"502",
"503",
"504",
"505",
"506",
"507",
"508",
"509",
"510",
"511",
"_domainkey",
"about",
"about-us",
"abuse",
"access",
"account",
"accounts",
"ad",
"add",
"admin",
"administration",
"administrator",
"ads",
"ads.txt",
"advertise",
"advertising",
"aes128-ctr",
"aes128-gcm",
"aes192-ctr",
"aes256-ctr",
"aes256-gcm",
"affiliate",
"affiliates",
"ajax",
"alert",
"alerts",
"alpha",
"amp",
"analytics",
"api",
"app",
"apps",
"asc",
"assets",
"auth",
"authentication",
"authorize",
"autoconfig",
"autodiscover",
"avatar",
"backup",
"banner",
"banners",
"bbs",
"beta",
"billing",
"billings",
"blogs",
"board",
"bookmark",
"bookmarks",
"broadcasthost",
"business",
"buy",
"cache",
"calendar",
"campaign",
"captcha",
"careers",
"cart",
"cas",
"categories",
"category",
"cdn",
"cgi",
"cgi-bin",
"chacha20-poly1305",
"change",
"channel",
"channels",
"chart",
"chat",
"checkout",
"clear",
"client",
"close",
"cloud",
"cms",
"com",
"comment",
"comments",
"community",
"compare",
"compose",
"config",
"connect",
"contact",
"contest",
"cookies",
"copy",
"copyright",
"count",
"cp",
"cpanel",
"create",
"crossdomain.xml",
"css",
"curve25519-sha256",
"customer",
"customers",
"customize",
"dashboard",
"db",
"deals",
"debug",
"delete",
"desc",
"destroy",
"dev",
"developer",
"developers",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group14-sha1",
"disconnect",
"discuss",
"dns",
"dns0",
"dns1",
"dns2",
"dns3",
"dns4",
"docs",
"documentation",
"domain",
"download",
"downloads",
"downvote",
"draft",
"drop",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"edit",
"editor",
"email",
"enterprise",
"error",
"errors",
"event",
"events",
"example",
"exception",
"exit",
"explore",
"export",
"extensions",
"false",
"family",
"faq",
"faqs",
"features",
"feed",
"feedback",
"feeds",
"file",
"files",
"filter",
"follow",
"follower",
"followers",
"following",
"fonts",
"forgot",
"forgot-password",
"forgotpassword",
"form",
"forms",
"forum",
"forums",
"friend",
"friends",
"ftp",
"fuck",
"get",
"git",
"go",
"graphql",
"group",
"groups",
"guest",
"guidelines",
"guides",
"head",
"header",
"help",
"hide",
"hmac-sha",
"hmac-sha1",
"hmac-sha1-etm",
"hmac-sha2-256",
"hmac-sha2-256-etm",
"hmac-sha2-512",
"hmac-sha2-512-etm",
"home",
"host",
"hosting",
"hostmaster",
"htpasswd",
"http",
"httpd",
"https",
"icons",
"images",
"imap",
"img",
"import",
"index",
"info",
"insert",
"investors",
"invitations",
"invite",
"invites",
"invoice",
"is",
"isatap",
"issues",
"it",
"jobs",
"join",
"js",
"json",
"learn",
"legal",
"license",
"licensing",
"like",
"limit",
"live",
"load",
"local",
"localdomain",
"localhost",
"lock",
"login",
"logout",
"lost-password",
"mail",
"mail0",
"mail1",
"mail2",
"mail3",
"mail4",
"mail5",
"mail6",
"mail7",
"mail8",
"mail9",
"mailer-daemon",
"mailerdaemon",
"map",
"marketing",
"marketplace",
"master",
"me",
"media",
"member",
"members",
"message",
"messages",
"metrics",
"mis",
"mobile",
"moderator",
"modify",
"more",
"mx",
"mx1",
"my",
"net",
"network",
"new",
"news",
"newsletter",
"newsletters",
"next",
"nil",
"no-reply",
"nobody",
"noc",
"none",
"noreply",
"notification",
"notifications",
"ns",
"ns0",
"ns1",
"ns2",
"ns3",
"ns4",
"ns5",
"ns6",
"ns7",
"ns8",
"ns9",
"null",
"oauth",
"oauth2",
"offer",
"offers",
"online",
"openid",
"order",
"orders",
"overview",
"owa",
"owner",
"page",
"pages",
"partners",
"passwd",
"password",
"pay",
"payment",
"payments",
"paypal",
"photo",
"photos",
"pixel",
"plans",
"plugins",
"policies",
"policy",
"pop",
"pop3",
"popular",
"portal",
"portfolio",
"post",
"postfix",
"postmaster",
"poweruser",
"preferences",
"premium",
"press",
"previous",
"pricing",
"print",
"privacy",
"privacy-policy",
"private",
"prod",
"product",
"production",
"profile",
"profiles",
"project",
"projects",
"promo",
"public",
"purchase",
"put",
"quota",
"redirect",
"reduce",
"refund",
"refunds",
"register",
"registration",
"remove",
"replies",
"reply",
"report",
"request",
"request-password",
"reset",
"reset-password",
"response",
"return",
"returns",
"review",
"reviews",
"root",
"rootuser",
"rsa-sha2-2",
"rsa-sha2-512",
"rss",
"rules",
"sales",
"save",
"script",
"sdk",
"search",
"secure",
"security",
"select",
"services",
"session",
"sessions",
"settings",
"setup",
"share",
"shift",
"shift",
"shop",
"signin",
"signup",
"site",
"sitemap",
"sites",
"smtp",
"sort",
"source",
"sql",
"ssh",
"ssh-rsa",
"ssl",
"ssladmin",
"ssladministrator",
"sslwebmaster",
"stage",
"staging",
"stat",
"static",
"statistics",
"stats",
"status",
"store",
"style",
"styles",
"stylesheet",
"stylesheets",
"subdomain",
"subscribe",
"sudo",
"super",
"superuser",
"support",
"survey",
"sync",
"sysadmin",
"sysadmin",
"system",
"tablet",
"tag",
"tags",
"team",
"telnet",
"terms",
"terms-of-use",
"test",
"testimonials",
"theme",
"themes",
"today",
"tools",
"topic",
"topics",
"tour",
"training",
"translate",
"translations",
"trending",
"trial",
"true",
"umac-128",
"umac-128-etm",
"umac-64",
"umac-64-etm",
"undefined",
"unfollow",
"unlike",
"unsubscribe",
"update",
"upgrade",
"usenet",
"user",
"username",
"users",
"uucp",
"var",
"verify",
"video",
"view",
"void",
"vote",
"vpn",
"webmail",
"webmaster",
"website",
"widget",
"widgets",
"wiki",
"wpad",
"write",
"www",
"www-data",
"www1",
"www2",
"www3",
"www4",
"you",
"yourname",
"yourusername",
"zlib",
]
export const checkReservedWords = (word: string) => {
if (words.includes(word)) {
throw new Error(`${word} is a reserved word`)
}
}

View File

@ -9,6 +9,7 @@ import {
import { type Gate } from "~/lib/gate.server"
import { sendEmailForNewPost } from "~/lib/mailgun.server"
import { getAutoExcerpt, renderPageContent } from "~/lib/markdown.server"
import { checkReservedWords } from "~/lib/reserved-words"
import { notFound } from "~/lib/server-side-props"
import { PageVisibilityEnum } from "~/lib/types"
import { isUUID } from "~/lib/uuid"
@ -26,6 +27,9 @@ const checkPageSlug = async ({
if (!slug) {
throw new Error("Missing page slug")
}
checkReservedWords(slug)
const page = await prismaPrimary.page.findFirst({
where: {
siteId,

View File

@ -7,6 +7,7 @@ import { sendLoginEmail } from "~/lib/mailgun.server"
import { SiteNavigationItem, SubscribeFormData } from "~/lib/types"
import { nanoid } from "nanoid"
import { getMembership } from "./membership"
import { checkReservedWords } from "~/lib/reserved-words"
export const checkSubdomain = async ({
subdomain,
@ -15,6 +16,8 @@ export const checkSubdomain = async ({
subdomain: string
updatingSiteId?: string
}) => {
checkReservedWords(subdomain)
const existingSite = await prismaPrimary.site.findUnique({
where: {
subdomain,

View File

@ -1,5 +1,6 @@
import { prismaPrimary } from "~/lib/db.server"
import { Gate } from "~/lib/gate.server"
import { checkReservedWords } from "~/lib/reserved-words"
export const userModel = {
async updateProfile(
@ -26,6 +27,8 @@ export const userModel = {
}
if (payload.username) {
checkReservedWords(payload.username)
const userByUsername = await prismaPrimary.user.findUnique({
where: {
username: payload.username,

View File

@ -13,9 +13,13 @@ export default function AccountProfilePage() {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
})
const updateProfile = trpc.useMutation("user.updateProfile")
const {
mutate: updateProfile,
status: updateProfileStatus,
error: updateProfileError,
} = trpc.useMutation("user.updateProfile")
const form = useForm({
const { setValue, handleSubmit, register } = useForm({
defaultValues: {
name: "",
username: "",
@ -24,25 +28,26 @@ export default function AccountProfilePage() {
},
})
const handleSubmit = form.handleSubmit((values) => {
updateProfile.mutate(values)
const onSubmit = handleSubmit((values) => {
updateProfile(values)
})
useEffect(() => {
if (updateProfile.isSuccess) {
if (updateProfileStatus === "success") {
toast.success("Saved!")
updateProfile.reset()
} else if (updateProfileStatus === "error" && updateProfileError) {
toast.error(updateProfileError.message)
}
}, [updateProfile])
}, [updateProfileStatus, updateProfileError])
useEffect(() => {
if (viewer.data) {
form.setValue("name", viewer.data.name)
form.setValue("username", viewer.data.username)
form.setValue("bio", viewer.data.bio || "")
form.setValue("email", viewer.data.email || "")
setValue("name", viewer.data.name)
setValue("username", viewer.data.username)
setValue("bio", viewer.data.bio || "")
setValue("email", viewer.data.email || "")
}
}, [viewer.data, form])
}, [viewer.data, setValue])
return (
<DashboardLayout>
@ -53,14 +58,14 @@ export default function AccountProfilePage() {
<AvatarForm filename={viewer.data.avatar} name={viewer.data.name} />
</div>
)}
<form onSubmit={handleSubmit}>
<form onSubmit={onSubmit}>
<div className="mt-5">
<Input
label="Display Name"
id="name"
required
type="text"
{...form.register("name")}
{...register("name")}
/>
</div>
<div className="mt-5">
@ -69,7 +74,7 @@ export default function AccountProfilePage() {
id="username"
required
type="text"
{...form.register("username")}
{...register("username")}
/>
</div>
<div className="mt-5">
@ -78,11 +83,11 @@ export default function AccountProfilePage() {
id="email"
required
type="email"
{...form.register("email")}
{...register("email")}
/>
</div>
<div className="mt-10">
<Button type="submit" isLoading={updateProfile.isLoading}>
<Button type="submit" isLoading={updateProfileStatus === "loading"}>
Save
</Button>
</div>

View File

@ -79,8 +79,8 @@ export default function NewSitePage() {
isBlock
required
addon={`.${OUR_DOMAIN}`}
minLength={2}
maxLength={20}
minLength={3}
maxLength={26}
{...form.register("subdomain", {})}
/>
</div>

View File

@ -174,7 +174,7 @@ export const siteRouter = createRouter()
.mutation("create", {
input: z.object({
name: z.string(),
subdomain: z.string().min(2).max(20),
subdomain: z.string().min(3).max(26),
}),
output: z.object({
id: z.string(),