diff --git a/web/components/admin/config/general/EditSocialLinks.tsx b/web/components/admin/config/general/EditSocialLinks.tsx index a3e4fedeb..09c4a2462 100644 --- a/web/components/admin/config/general/EditSocialLinks.tsx +++ b/web/components/admin/config/general/EditSocialLinks.tsx @@ -18,7 +18,7 @@ import { isValidAccount, isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN, -} from '../../../../utils/urls'; +} from '../../../../utils/validators'; import { TextField } from '../../TextField'; import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../../../utils/input-statuses'; import { FormStatusIndicator } from '../../FormStatusIndicator'; diff --git a/web/components/admin/config/server/EditStorage.tsx b/web/components/admin/config/server/EditStorage.tsx index 2923429e2..ea2e807b2 100644 --- a/web/components/admin/config/server/EditStorage.tsx +++ b/web/components/admin/config/server/EditStorage.tsx @@ -20,7 +20,7 @@ import { } from '../../../../utils/input-statuses'; import { TextField } from '../../TextField'; import { FormStatusIndicator } from '../../FormStatusIndicator'; -import { isValidUrl } from '../../../../utils/urls'; +import { isValidUrl } from '../../../../utils/validators'; import { ToggleSwitch } from '../../ToggleSwitch'; const { Panel } = Collapse; diff --git a/web/components/modals/FediAuthModal/FediAuthModal.tsx b/web/components/modals/FediAuthModal/FediAuthModal.tsx index 2ac423571..389f9b61b 100644 --- a/web/components/modals/FediAuthModal/FediAuthModal.tsx +++ b/web/components/modals/FediAuthModal/FediAuthModal.tsx @@ -2,7 +2,7 @@ import { Alert, Button, Input, Space, Spin, Collapse } from 'antd'; import React, { FC, useState } from 'react'; import dynamic from 'next/dynamic'; import styles from './FediAuthModal.module.scss'; -import { validateAccount } from '../../../utils/validators'; +import { isValidFediverseAccount } from '../../../utils/validators'; const { Panel } = Collapse; @@ -50,7 +50,7 @@ export const FediAuthModal: FC = ({ } const validate = (acct: string) => { - setValid(validateAccount(acct)); + setValid(isValidFediverseAccount(acct)); }; const onInput = (e: React.ChangeEvent) => { diff --git a/web/components/modals/FollowModal/FollowModal.tsx b/web/components/modals/FollowModal/FollowModal.tsx index 4fbaf71d2..e9ac40d3d 100644 --- a/web/components/modals/FollowModal/FollowModal.tsx +++ b/web/components/modals/FollowModal/FollowModal.tsx @@ -2,6 +2,7 @@ import { Input, Button, Alert, Spin, Space } from 'antd'; import { FC, useState } from 'react'; import styles from './FollowModal.module.scss'; +import { isValidFediverseAccount } from '../../../utils/validators'; const ENDPOINT = '/api/remotefollow'; @@ -11,13 +12,6 @@ export type FollowModalProps = { name: string; }; -function validateAccount(a) { - const sanitized = a.replace(/^@+/, ''); - const regex = - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return regex.test(String(sanitized).toLowerCase()); -} - export const FollowModal: FC = ({ handleClose, account, name }) => { const [remoteAccount, setRemoteAccount] = useState(null); const [valid, setValid] = useState(false); @@ -26,7 +20,7 @@ export const FollowModal: FC = ({ handleClose, account, name } const handleAccountChange = a => { setRemoteAccount(a); - if (validateAccount(a)) { + if (isValidFediverseAccount(a)) { setValid(true); } else { setValid(false); diff --git a/web/components/modals/IndieAuthModal/IndieAuthModal.tsx b/web/components/modals/IndieAuthModal/IndieAuthModal.tsx index 37e541f05..623115aef 100644 --- a/web/components/modals/IndieAuthModal/IndieAuthModal.tsx +++ b/web/components/modals/IndieAuthModal/IndieAuthModal.tsx @@ -1,7 +1,7 @@ import { Alert, Input, Space, Spin, Collapse, Typography, Button } from 'antd'; import dynamic from 'next/dynamic'; import React, { FC, useState } from 'react'; -import { isValidUrl } from '../../../utils/urls'; +import { isValidUrl } from '../../../utils/validators'; const { Panel } = Collapse; const { Link } = Typography; diff --git a/web/pages/admin/actions.tsx b/web/pages/admin/actions.tsx index 46e142795..305268d41 100644 --- a/web/pages/admin/actions.tsx +++ b/web/pages/admin/actions.tsx @@ -13,7 +13,7 @@ import { } from '../../utils/config-constants'; import { createInputStatus, STATUS_ERROR, STATUS_SUCCESS } from '../../utils/input-statuses'; import { ServerStatusContext } from '../../utils/server-status-context'; -import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls'; +import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators'; import { AdminLayout } from '../../components/layouts/AdminLayout'; diff --git a/web/pages/admin/config-notify.tsx b/web/pages/admin/config-notify.tsx index 20ba0254e..8b67fecaf 100644 --- a/web/pages/admin/config-notify.tsx +++ b/web/pages/admin/config-notify.tsx @@ -12,7 +12,7 @@ import { import { TEXTFIELD_PROPS_FEDERATION_INSTANCE_URL } from '../../utils/config-constants'; import { ServerStatusContext } from '../../utils/server-status-context'; import { UpdateArgs } from '../../types/config-section'; -import { isValidUrl } from '../../utils/urls'; +import { isValidUrl } from '../../utils/validators'; import { AdminLayout } from '../../components/layouts/AdminLayout'; diff --git a/web/pages/admin/webhooks.tsx b/web/pages/admin/webhooks.tsx index f3ce362a0..2f9f57225 100644 --- a/web/pages/admin/webhooks.tsx +++ b/web/pages/admin/webhooks.tsx @@ -15,7 +15,7 @@ import { import dynamic from 'next/dynamic'; import React, { ReactElement, useEffect, useState } from 'react'; import { CREATE_WEBHOOK, DELETE_WEBHOOK, fetchData, WEBHOOKS } from '../../utils/apis'; -import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/urls'; +import { isValidUrl, DEFAULT_TEXTFIELD_URL_PATTERN } from '../../utils/validators'; import { AdminLayout } from '../../components/layouts/AdminLayout'; diff --git a/web/utils/config-constants.tsx b/web/utils/config-constants.tsx index 1d984afd8..d3ce12aef 100644 --- a/web/utils/config-constants.tsx +++ b/web/utils/config-constants.tsx @@ -1,7 +1,7 @@ // DEFAULT VALUES import { fetchData, SERVER_CONFIG_UPDATE_URL } from './apis'; import { ApiPostArgs, VideoVariant, SocialHandle } from '../types/config-section'; -import { DEFAULT_TEXTFIELD_URL_PATTERN } from './urls'; +import { DEFAULT_TEXTFIELD_URL_PATTERN } from './validators'; export const TEXT_MAXLENGTH = 255; diff --git a/web/utils/urls.ts b/web/utils/urls.ts deleted file mode 100644 index 4a64ec971..000000000 --- a/web/utils/urls.ts +++ /dev/null @@ -1,83 +0,0 @@ -// to use with fields, as the default pattern only checks for `:`, -export const DEFAULT_TEXTFIELD_URL_PATTERN = 'https?://.*'; - -/** - * Determines if a URL is valid - * @param {string} url - A URL to validate. - * @param {string[]} validProtocols - An array of valid protocols. Defaults to web. - * @returns {boolean} - True if the URI is valid, false otherwise. - */ -export function isValidUrl(url: string, validProtocols: string[] = ['http:', 'https:']): boolean { - try { - const validationObject = new URL(url); - - if ( - validationObject.protocol === '' || - validationObject.hostname === '' || - !validProtocols.includes(validationObject.protocol) - ) { - return false; - } - } catch (e) { - return false; - } - - return true; -} - -/** - * Determines if an account is valid by simply checking for a protocol, username - * and server, delimited by a colon. For example: @username:example.com - * @param {string} account - An account to validate. - * @param {string} protocol - The protocol we expect the account to be using. - * @returns {boolean} - True if the account is valid, false otherwise. - */ -export function isValidAccount(account: string, protocol: string): boolean { - if (account.startsWith('@')) { - // eslint-disable-next-line no-param-reassign - account = account.slice(1); - } - - const components = account.split(/:|@/); - const [service, user, host] = components; - - console.log({ account, protocol, service, user, host }); - if (service !== protocol) { - return false; - } - - if (components.length !== 3 || !service || !user || !host) { - return false; - } - - return true; -} - -/** - * Determines if an account is valid by simply checking for a protocol, username - * and server, delimited by a colon. For example: @username:example.com - * @param {string} account - An account to validate. - * @returns {boolean} - True if the account is valid, false otherwise. - */ -export function isValidMatrixAccount(account: string): boolean { - if (account.startsWith('matrix:')) { - // eslint-disable-next-line no-param-reassign - account = account.slice(7); - } else { - return false; - } - - if (account.startsWith('@')) { - // eslint-disable-next-line no-param-reassign - account = account.slice(1); - } - - const components = account.split(':'); - const [user, host] = components; - - if (components.length !== 2 || !user || !host) { - return false; - } - - return true; -} diff --git a/web/utils/validators.ts b/web/utils/validators.ts index 1675c72ca..99913ecb0 100644 --- a/web/utils/validators.ts +++ b/web/utils/validators.ts @@ -1,7 +1,93 @@ -// eslint-disable-next-line import/prefer-default-export -export function validateAccount(account) { - const a = account.replace(/^@+/, ''); +// to use with fields, as the default pattern only checks for `:`, +export const DEFAULT_TEXTFIELD_URL_PATTERN = 'https?://.*'; + +/** + * Determines if a URL is valid + * @param {string} url - A URL to validate. + * @param {string[]} validProtocols - An array of valid protocols. Defaults to web. + * @returns {boolean} - True if the URI is valid, false otherwise. + */ +export function isValidUrl(url: string, validProtocols: string[] = ['http:', 'https:']): boolean { + try { + const validationObject = new URL(url); + + if ( + validationObject.protocol === '' || + validationObject.hostname === '' || + !validProtocols.includes(validationObject.protocol) + ) { + return false; + } + } catch (e) { + return false; + } + + return true; +} + +/** + * Determines if an account is valid by checking for a protocol, username + * and server, delimited by a colon. For example: @username:example.com + * @param {string} account - An account to validate. + * @param {string} protocol - The protocol we expect the account to be using. + * @returns {boolean} - True if the account is valid, false otherwise. + */ +export function isValidAccount(account: string, protocol: string): boolean { + if (account.startsWith('@')) { + // eslint-disable-next-line no-param-reassign + account = account.slice(1); + } + + const components = account.split(/:|@/); + const [service, user, host] = components; + + if (service !== protocol) { + return false; + } + + if (components.length !== 3 || !service || !user || !host) { + return false; + } + + return true; +} + +/** + * Determines if an account is valid by simply checking for a protocol, username + * and server, delimited by a colon. For example: @username:example.com + * @param {string} account - An account to validate. Example: @me:matrix.org + * @returns {boolean} - True if the account is valid, false otherwise. + */ +export function isValidMatrixAccount(account: string): boolean { + if (account.startsWith('matrix:')) { + // eslint-disable-next-line no-param-reassign + account = account.slice(7); + } + + if (account.startsWith('@')) { + // eslint-disable-next-line no-param-reassign + account = account.slice(1); + } + + const components = account.split(':'); + const [user, host] = components; + + if (components.length !== 2 || !user || !host) { + return false; + } + + return true; +} + +/** + * Determines if a fediverse account is valid. + * For example: @username@example.com + * @param {string} account - An account to validate. + * @returns {boolean} - True if the account is valid, false otherwise. + */ +export function isValidFediverseAccount(account: string): boolean { + const sanitized = account.replace(/^@+/, ''); const regex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return regex.test(String(a).toLowerCase()); + return regex.test(String(sanitized).toLowerCase()); }