From 365b5753419238bb96bc3f9b744d380ff20cbafc Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:14:41 +0200 Subject: [feature] add TOTP two-factor authentication (2FA) (#3960) * [feature] add TOTP two-factor authentication (2FA) * use byteutil.S2B to avoid allocations when comparing + generating password hashes * don't bother with string conversion for consts * use io.ReadFull * use MustGenerateSecret for backup codes * rename util functions --- web/source/settings/views/user/profile.tsx | 371 ----------------------------- 1 file changed, 371 deletions(-) delete mode 100644 web/source/settings/views/user/profile.tsx (limited to 'web/source/settings/views/user/profile.tsx') diff --git a/web/source/settings/views/user/profile.tsx b/web/source/settings/views/user/profile.tsx deleted file mode 100644 index d6fcbf56d..000000000 --- a/web/source/settings/views/user/profile.tsx +++ /dev/null @@ -1,371 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -import React, { useMemo, useState } from "react"; - -import { - useTextInput, - useFileInput, - useBoolInput, - useFieldArrayInput, -} from "../../lib/form"; - -import useFormSubmit from "../../lib/form/submit"; -import { useWithFormContext, FormContext } from "../../lib/form/context"; - -import { - TextInput, - TextArea, - FileInput, - Checkbox, - Select -} from "../../components/form/inputs"; - -import FormWithData from "../../lib/form/form-with-data"; -import FakeProfile from "../../components/profile"; -import MutationButton from "../../components/form/mutation-button"; - -import { useAccountThemesQuery, useDeleteAvatarMutation, useDeleteHeaderMutation } from "../../lib/query/user"; -import { useUpdateCredentialsMutation } from "../../lib/query/user"; -import { useVerifyCredentialsQuery } from "../../lib/query/login"; -import { useInstanceV1Query } from "../../lib/query/gts-api"; -import { Account } from "../../lib/types/account"; - -export default function UserProfile() { - return ( - - ); -} - -interface UserProfileFormProps { - data: Account; -} - -function UserProfileForm({ data: profile }: UserProfileFormProps) { - const { data: instance } = useInstanceV1Query(); - const instanceConfig = React.useMemo(() => { - return { - allowCustomCSS: instance?.configuration?.accounts?.allow_custom_css === true, - maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6 - }; - }, [instance]); - - // Parse out available theme options into nice format. - const { data: themes } = useAccountThemesQuery(); - const themeOptions = useMemo(() => { - let themeOptions = [ - - ]; - - themes?.forEach((theme) => { - const value = theme.file_name; - let text = theme.title; - if (theme.description) { - text += " - " + theme.description; - } - themeOptions.push( - - ); - }); - - return themeOptions; - }, [themes]); - - const form = { - avatar: useFileInput("avatar", { withPreview: true }), - avatarDescription: useTextInput("avatar_description", { source: profile }), - header: useFileInput("header", { withPreview: true }), - headerDescription: useTextInput("header_description", { source: profile }), - displayName: useTextInput("display_name", { source: profile }), - note: useTextInput("note", { source: profile, valueSelector: (p) => p.source?.note }), - bot: useBoolInput("bot", { source: profile }), - locked: useBoolInput("locked", { source: profile }), - discoverable: useBoolInput("discoverable", { source: profile}), - enableRSS: useBoolInput("enable_rss", { source: profile }), - hideCollections: useBoolInput("hide_collections", { source: profile }), - webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p: Account) => p.source?.web_visibility }), - webLayout: useTextInput("web_layout", { source: profile, valueSelector: (p: Account) => p.source?.web_layout }), - fields: useFieldArrayInput("fields_attributes", { - defaultValue: profile?.source?.fields, - length: instanceConfig.maxPinnedFields - }), - customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }), - theme: useTextInput("theme", { source: profile }), - }; - - const [ noHeader, setNoHeader ] = useState(!profile.header_media_id); - const [ deleteHeader, deleteHeaderRes ] = useDeleteHeaderMutation(); - const [ noAvatar, setNoAvatar ] = useState(!profile.avatar_media_id); - const [ deleteAvatar, deleteAvatarRes ] = useDeleteAvatarMutation(); - - const [submitForm, result] = useFormSubmit(form, useUpdateCredentialsMutation(), { - changedOnly: true, - onFinish: (res) => { - if ('data' in res) { - form.avatar.reset(); - form.header.reset(); - setNoAvatar(!res.data.avatar_media_id); - setNoHeader(!res.data.header_media_id); - } - } - }); - - return ( -
-

Profile

-
- - -
- Header - - - { - e.preventDefault(); - deleteHeader().then(res => { - if ('data' in res) { - setNoHeader(true); - } - }); - }} - /> -
- -
- Avatar - - - { - e.preventDefault(); - deleteAvatar().then(res => { - if ('data' in res) { - setNoAvatar(true); - } - }); - }} - /> -
- - After choosing theme or layout and saving, open your profile and refresh to see changes. - - - - - - } - /> -
- -
-

Basic Information

- - Learn more about these settings (opens in a new tab) - -
- - -