From ae0dc2c30dd62d6ef4f87239298ec89c47c55861 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:13:46 +0800 Subject: [PATCH] feat: register or login with email and password (#2075) * feat: login with password * chore: auto-fix linting and formatting issues * feat: show email * refactor: login form * chore: update * fix: show login error * chore: update * credential provider * feat: confirm password * chore: update * revokeOtherSessions when update password * changelog * typecheck * chore: update * chore: update hono * feat: forget password * chore: update * feat: reset password page * feat: register form * chore: update * chore: update * chore: update * chore: update * fix: email login handler * fix: navigate to login after register * chore: remove forget password button for now * chore: update * feat: forget password page * chore: update hono * fix: forget-password link * feat: login email text * refactor: enhance login and forget password functionality - Updated the forget password page to include a back navigation button using MotionButtonBase. - Refactored the login component to utilize the new Login module, simplifying the structure. - Adjusted translations for consistency in the login text across English and German locales. - Improved the useAuthProviders hook to return a more structured AuthProvider interface. Signed-off-by: Innei * feat: add form validation and UI enhancements for login-related pages - Introduced form validation state management in forget-password, register, and reset-password components. - Updated button states to be disabled when forms are invalid, improving user experience. - Enhanced UI elements with consistent styling and layout adjustments, including the addition of MotionButtonBase for navigation. - Improved accessibility and responsiveness of card components. Signed-off-by: Innei * feat: enhance login component with dynamic provider buttons - Added MotionButtonBase for improved button animations and interactions. - Refactored the rendering logic to conditionally display login options based on the presence of credential providers. - Introduced a new icon mapping for providers to enhance visual representation. - Improved layout and styling for better user experience during login. Signed-off-by: Innei * feat: add GitHub provider icon and adjust button margin in login component - Introduced a GitHub icon to the provider icon map for enhanced visual representation. - Adjusted the margin of the submit button to improve layout consistency. Signed-off-by: Innei * chore: update --------- Signed-off-by: Innei Co-authored-by: hyoban Co-authored-by: DIYgod Co-authored-by: Innei Signed-off-by: Innei --- .../modules/profile/profile-setting-form.tsx | 5 + .../modules/profile/update-password-form.tsx | 138 +++++ .../src/pages/settings/(settings)/profile.tsx | 2 + apps/renderer/src/queries/auth.ts | 19 +- apps/server/client/modules/login/index.tsx | 302 ++++++++++ .../client/pages/(login)/forget-password.tsx | 112 ++++ apps/server/client/pages/(login)/login.tsx | 132 +---- apps/server/client/pages/(login)/register.tsx | 139 +++++ .../client/pages/(login)/reset-password.tsx | 130 ++++ apps/server/client/query/users.ts | 19 +- apps/server/package.json | 4 +- changelog/next.md | 2 + locales/external/de.json | 2 +- locales/external/en.json | 27 +- locales/settings/en.json | 7 + packages/shared/src/auth.ts | 42 +- packages/shared/src/hono.ts | 556 ++++++++++++------ pnpm-lock.yaml | 6 + 18 files changed, 1303 insertions(+), 341 deletions(-) create mode 100644 apps/renderer/src/modules/profile/update-password-form.tsx create mode 100644 apps/server/client/modules/login/index.tsx create mode 100644 apps/server/client/pages/(login)/forget-password.tsx create mode 100644 apps/server/client/pages/(login)/register.tsx create mode 100644 apps/server/client/pages/(login)/reset-password.tsx diff --git a/apps/renderer/src/modules/profile/profile-setting-form.tsx b/apps/renderer/src/modules/profile/profile-setting-form.tsx index 1319b59303..4bc0038f13 100644 --- a/apps/renderer/src/modules/profile/profile-setting-form.tsx +++ b/apps/renderer/src/modules/profile/profile-setting-form.tsx @@ -10,6 +10,7 @@ import { FormMessage, } from "@follow/components/ui/form/index.jsx" import { Input } from "@follow/components/ui/input/index.js" +import { Label } from "@follow/components/ui/label/index.js" import { cn } from "@follow/utils/utils" import { zodResolver } from "@hookform/resolvers/zod" import { useMutation } from "@tanstack/react-query" @@ -72,6 +73,10 @@ export const ProfileSettingForm = ({ return (
+
+ +

{user?.email}

+
data.newPassword === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +const UpdateExistingPasswordForm = () => { + const { t } = useTranslation("settings") + + const form = useForm>({ + resolver: zodResolver(updatePasswordFormSchema), + defaultValues: { + currentPassword: "", + newPassword: "", + confirmPassword: "", + }, + }) + + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await changePassword({ + currentPassword: values.currentPassword, + newPassword: values.newPassword, + revokeOtherSessions: true, + }) + if (res.error) { + throw new Error(res.error.message) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: (_) => { + toast(t("profile.update_password_success"), { + duration: 3000, + }) + form.reset() + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + return ( + + + ( + + {t("profile.change_password.label")} + + + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
+ +
+ + + ) +} + +export const UpdatePasswordForm = () => { + const { data: hasPassword, isLoading } = useHasPassword() + + if (isLoading || !hasPassword) { + return null + } + + return +} diff --git a/apps/renderer/src/pages/settings/(settings)/profile.tsx b/apps/renderer/src/pages/settings/(settings)/profile.tsx index 510dfac7ca..0a3464f9dc 100644 --- a/apps/renderer/src/pages/settings/(settings)/profile.tsx +++ b/apps/renderer/src/pages/settings/(settings)/profile.tsx @@ -1,4 +1,5 @@ import { ProfileSettingForm } from "~/modules/profile/profile-setting-form" +import { UpdatePasswordForm } from "~/modules/profile/update-password-form" import { SettingsTitle } from "~/modules/settings/title" import { defineSettingPageData } from "~/modules/settings/utils" @@ -15,6 +16,7 @@ export function Component() { <> + ) } diff --git a/apps/renderer/src/queries/auth.ts b/apps/renderer/src/queries/auth.ts index 354d9b4199..16751ddba3 100644 --- a/apps/renderer/src/queries/auth.ts +++ b/apps/renderer/src/queries/auth.ts @@ -1,4 +1,4 @@ -import { getSession } from "@follow/shared/auth" +import { getSession, listAccounts } from "@follow/shared/auth" import type { AuthSession } from "@follow/shared/hono" import type { FetchError } from "ofetch" @@ -7,6 +7,23 @@ import { defineQuery } from "~/lib/defineQuery" export const auth = { getSession: () => defineQuery(["auth", "session"], () => getSession()), + getAccounts: () => + defineQuery(["auth", "accounts"], async () => { + const accounts = await listAccounts() + return accounts.data as Array<{ id: string; provider: string }> + }), +} + +export const useAccounts = () => { + return useAuthQuery(auth.getAccounts()) +} + +export const useHasPassword = () => { + const accounts = useAccounts() + return { + ...accounts, + data: !!accounts.data?.find((account) => account.provider === "credential"), + } } export const useSession = (options?: { enabled?: boolean }) => { diff --git a/apps/server/client/modules/login/index.tsx b/apps/server/client/modules/login/index.tsx new file mode 100644 index 0000000000..949408ae08 --- /dev/null +++ b/apps/server/client/modules/login/index.tsx @@ -0,0 +1,302 @@ +import { UserAvatar } from "@client/components/ui/user-avatar" +import { queryClient } from "@client/lib/query-client" +import { useSession } from "@client/query/auth" +import { useAuthProviders } from "@client/query/users" +import { Logo } from "@follow/components/icons/logo.jsx" +import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx" +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.js" +import { Divider } from "@follow/components/ui/divider/index.js" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { LoadingCircle } from "@follow/components/ui/loading/index.jsx" +import { authProvidersConfig } from "@follow/constants" +import { createSession, loginHandler, signOut } from "@follow/shared/auth" +import { DEEPLINK_SCHEME } from "@follow/shared/constants" +import { cn } from "@follow/utils/utils" +import { zodResolver } from "@hookform/resolvers/zod" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { Link, useLocation, useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const overrideProviderIconMap: Record = { + apple: , + github: , +} + +export function Login() { + const { status, refetch } = useSession() + + const [redirecting, setRedirecting] = useState(false) + + const { data: authProviders, isLoading } = useAuthProviders() + + const location = useLocation() + const urlParams = new URLSearchParams(location.search) + const provider = urlParams.get("provider") + const isCredentialProvider = provider === "credential" + + const isAuthenticated = status === "authenticated" + + const { t } = useTranslation("external") + + useEffect(() => { + if (provider && !isCredentialProvider && status === "unauthenticated") { + loginHandler(provider) + setRedirecting(true) + } + }, [isCredentialProvider, provider, status]) + + const getCallbackUrl = useCallback(async () => { + const { data } = await createSession() + if (!data) return null + return { + url: `${DEEPLINK_SCHEME}auth?ck=${data.ck}&userId=${data.userId}`, + userId: data.userId, + } + }, []) + + const handleOpenApp = useCallback(async () => { + const callbackUrl = await getCallbackUrl() + if (!callbackUrl) return + window.open(callbackUrl.url, "_top") + }, [getCallbackUrl]) + + const onceRef = useRef(false) + useEffect(() => { + if (isAuthenticated && !onceRef.current) { + handleOpenApp() + } + onceRef.current = true + }, [handleOpenApp, isAuthenticated]) + + const LoginOrStatusContent = useMemo(() => { + switch (true) { + case isAuthenticated: { + return ( +
+
+ +
+ +
+
+

+ {t("redirect.successMessage", { app_name: APP_NAME })}
+
+ {t("redirect.instruction", { app_name: APP_NAME })} +

+
+ + + +
+
+ ) + } + default: { + if (!authProviders?.credential) { + return ( +
+ {Object.entries(authProviders || []) + .filter(([key]) => key !== "credential") + .map(([key, provider]) => ( + + ))} +
+ ) + } else { + return ( + <> + +
+
+ +

{t("login.or")}

+ +
+
+
+ {Object.entries(authProviders || []) + .filter(([key]) => key !== "credential") + .map(([key, provider]) => ( + { + loginHandler(key) + }} + > + {overrideProviderIconMap[provider.id] ? ( +
+ {overrideProviderIconMap[provider.id]} +
+ ) : ( +
+ )} + + ))} +
+ + ) + } + } + } + }, [authProviders, handleOpenApp, isAuthenticated, refetch, t]) + const Content = useMemo(() => { + switch (true) { + case redirecting: { + return
{t("login.redirecting")}
+ } + default: { + return
{LoginOrStatusContent}
+ } + } + }, [LoginOrStatusContent, redirecting, t]) + + return ( +
+ + {isLoading && } + + + <> + {!isAuthenticated && !isLoading && ( +

+ {t("login.logInTo")} + {` ${APP_NAME}`} +

+ )} + {Content} + +
+
+ ) +} + +const formSchema = z.object({ + email: z.string().email(), + password: z.string().max(128), +}) + +async function onSubmit(values: z.infer) { + const res = await loginHandler("credential", "browser", values) + if (res?.error) { + toast.error(res.error.message) + return + } + queryClient.invalidateQueries({ queryKey: ["auth", "session"] }) +} + +function LoginWithPassword() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + }, + }) + const { isValid } = form.formState + const navigate = useNavigate() + + return ( +
+ + ( + + {t("login.email")} + + + + + + )} + /> + ( + + {t("login.password")} + + + + + + )} + /> + + {t("login.forget_password.note")} + + + + + + ) +} diff --git a/apps/server/client/pages/(login)/forget-password.tsx b/apps/server/client/pages/(login)/forget-password.tsx new file mode 100644 index 0000000000..7b1f800c83 --- /dev/null +++ b/apps/server/client/pages/(login)/forget-password.tsx @@ -0,0 +1,112 @@ +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.jsx" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@follow/components/ui/card/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { forgetPassword } from "@follow/shared/auth" +import { env } from "@follow/shared/env" +import { zodResolver } from "@hookform/resolvers/zod" +import { useMutation } from "@tanstack/react-query" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const forgetPasswordFormSchema = z.object({ + email: z.string().email(), +}) + +export function Component() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(forgetPasswordFormSchema), + defaultValues: { + email: "", + }, + }) + + const { isValid } = form.formState + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await forgetPassword({ + email: values.email, + redirectTo: `${env.VITE_WEB_URL}/reset-password`, + }) + if (res.error) { + throw new Error(res.error.message) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: () => { + toast.success(t("login.forget_password.success")) + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + const navigate = useNavigate() + + return ( +
+ + + + { + history.length > 1 ? history.back() : navigate("/login") + }} + className="-ml-1 inline-flex cursor-pointer items-center" + > + + + {t("login.forget_password.label")} + + + + + {t("login.forget_password.description")} + +
+ + ( + + {t("login.email")} + + + + + + )} + /> +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/server/client/pages/(login)/login.tsx b/apps/server/client/pages/(login)/login.tsx index c0d537051f..280f89782e 100644 --- a/apps/server/client/pages/(login)/login.tsx +++ b/apps/server/client/pages/(login)/login.tsx @@ -1,135 +1,5 @@ -import { UserAvatar } from "@client/components/ui/user-avatar" -import { useSession } from "@client/query/auth" -import { useAuthProviders } from "@client/query/users" -import { Logo } from "@follow/components/icons/logo.jsx" -import { Button } from "@follow/components/ui/button/index.js" -import { authProvidersConfig } from "@follow/constants" -import { createSession, loginHandler, signOut } from "@follow/shared/auth" -import { DEEPLINK_SCHEME } from "@follow/shared/constants" -import { cn } from "@follow/utils/utils" -import { useCallback, useEffect, useRef, useState } from "react" -import { useTranslation } from "react-i18next" -import { useLocation } from "react-router" +import { Login } from "@client/modules/login" export function Component() { return } - -function Login() { - const { status, refetch } = useSession() - - const [redirecting, setRedirecting] = useState(false) - - const { data: authProviders } = useAuthProviders() - const location = useLocation() - const urlParams = new URLSearchParams(location.search) - const provider = urlParams.get("provider") - - const isAuthenticated = status === "authenticated" - - const { t } = useTranslation("external") - - useEffect(() => { - if (provider && status === "unauthenticated") { - loginHandler(provider) - setRedirecting(true) - } - }, [status]) - - const getCallbackUrl = useCallback(async () => { - const { data } = await createSession() - if (!data) return null - return { - url: `${DEEPLINK_SCHEME}auth?ck=${data.ck}&userId=${data.userId}`, - userId: data.userId, - } - }, []) - - const handleOpenApp = useCallback(async () => { - const callbackUrl = await getCallbackUrl() - if (!callbackUrl) return - window.open(callbackUrl.url, "_top") - }, [getCallbackUrl]) - - const onceRef = useRef(false) - useEffect(() => { - if (isAuthenticated && !onceRef.current) { - handleOpenApp() - } - onceRef.current = true - }, [handleOpenApp, isAuthenticated]) - - return ( -
- - {!isAuthenticated && ( -

- {t("login.logInTo")} - {` ${APP_NAME}`} -

- )} - {redirecting ? ( -
{t("login.redirecting")}
- ) : ( -
- {isAuthenticated ? ( -
-
- -
- -
-
-

- {t("redirect.successMessage", { app_name: APP_NAME })}
-
- {t("redirect.instruction", { app_name: APP_NAME })} -

-
- - - -
-
- ) : ( - <> - {Object.entries(authProviders || []).map(([key, provider]) => ( - - ))} - - )} -
- )} -
- ) -} diff --git a/apps/server/client/pages/(login)/register.tsx b/apps/server/client/pages/(login)/register.tsx new file mode 100644 index 0000000000..9066b8a657 --- /dev/null +++ b/apps/server/client/pages/(login)/register.tsx @@ -0,0 +1,139 @@ +import { Logo } from "@follow/components/icons/logo.jsx" +import { Button } from "@follow/components/ui/button/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { signUp } from "@follow/shared/auth" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { Link, useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +export function Component() { + return ( +
+ + +
+ ) +} + +const formSchema = z + .object({ + email: z.string().email(), + password: z.string().min(8).max(128), + confirmPassword: z.string(), + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +function RegisterForm() { + const { t } = useTranslation("external") + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + confirmPassword: "", + }, + }) + + const { isValid } = form.formState + + const navigate = useNavigate() + + function onSubmit(values: z.infer) { + return signUp.email({ + email: values.email, + password: values.password, + name: values.email.split("@")[0], + callbackURL: "/", + fetchOptions: { + onSuccess() { + navigate("/login") + }, + onError(context) { + toast.error(context.error.message) + }, + }, + }) + } + + return ( +
+

+ {t("register.label", { app_name: APP_NAME })} +

+
+ + {t("register.login")} + + ), + }} + /> +
+
+ + ( + + {t("register.email")} + + + + + + )} + /> + ( + + {t("register.password")} + + + + + + )} + /> + ( + + {t("register.confirm_password")} + + + + + + )} + /> + + + +
+ ) +} diff --git a/apps/server/client/pages/(login)/reset-password.tsx b/apps/server/client/pages/(login)/reset-password.tsx new file mode 100644 index 0000000000..2dac23add9 --- /dev/null +++ b/apps/server/client/pages/(login)/reset-password.tsx @@ -0,0 +1,130 @@ +import { Button, MotionButtonBase } from "@follow/components/ui/button/index.jsx" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@follow/components/ui/card/index.jsx" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@follow/components/ui/form/index.jsx" +import { Input } from "@follow/components/ui/input/index.js" +import { resetPassword } from "@follow/shared/auth" +import { zodResolver } from "@hookform/resolvers/zod" +import { useMutation } from "@tanstack/react-query" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router" +import { toast } from "sonner" +import { z } from "zod" + +const passwordSchema = z.string().min(8).max(128) +const initPasswordFormSchema = z + .object({ + newPassword: passwordSchema, + confirmPassword: passwordSchema, + }) + .refine((data) => data.newPassword === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], + }) + +export function Component() { + const { t } = useTranslation("external") + const form = useForm>({ + resolver: zodResolver(initPasswordFormSchema), + defaultValues: { + newPassword: "", + confirmPassword: "", + }, + }) + + const { isValid } = form.formState + + const navigate = useNavigate() + const updateMutation = useMutation({ + mutationFn: async (values: z.infer) => { + const res = await resetPassword({ newPassword: values.newPassword }) + const error = res.error?.message + if (error) { + throw new Error(error) + } + }, + onError: (error) => { + toast.error(error.message) + }, + onSuccess: () => { + toast.success(t("login.reset_password.success")) + navigate("/login") + }, + }) + + function onSubmit(values: z.infer) { + updateMutation.mutate(values) + } + + return ( +
+ + + + { + history.length > 1 ? history.back() : navigate("/login") + }} + className="-ml-1 inline-flex cursor-pointer items-center" + > + + + {t("login.forget_password.label")} + + + + {t("login.reset_password.description")} +
+ + ( + + {t("login.new_password.label")} + + + + + + )} + /> + ( + + {t("login.confirm_password.label")} + + + + + + )} + /> + +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/server/client/query/users.ts b/apps/server/client/query/users.ts index 1c2e155311..cdebcba5d2 100644 --- a/apps/server/client/query/users.ts +++ b/apps/server/client/query/users.ts @@ -56,20 +56,15 @@ export const useUserQuery = (handleOrId: string | undefined) => { initialData: getHydrateData(`profiles.$get,query:id=${handleOrId}`), }) } - +export interface AuthProvider { + name: string + id: string + color: string + icon: string +} export const useAuthProviders = () => { return useQuery({ queryKey: ["providers"], - queryFn: async () => (await getProviders()).data, - placeholderData: { - google: { - id: "google", - name: "Google", - }, - github: { - id: "github", - name: "GitHub", - }, - }, + queryFn: async () => (await getProviders()).data as Record, }) } diff --git a/apps/server/package.json b/apps/server/package.json index 70e4bd318c..067decc908 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -27,6 +27,7 @@ "rc-modal-sheet": "0.3.2", "react": "^18.3.1", "react-blurhash": "^0.3.0", + "react-hook-form": "7.54.0", "react-hotkeys-hook": "4.6.1", "react-i18next": "^15.1.3", "react-photo-view": "1.2.6", @@ -35,7 +36,8 @@ "sonner": "1.7.1", "tailwindcss": "3.4.16", "use-context-selector": "2.0.0", - "xss": "1.0.15" + "xss": "1.0.15", + "zod": "3.23.8" }, "devDependencies": { "@follow/components": "workspace:*", diff --git a/changelog/next.md b/changelog/next.md index 17888d80b6..d7f8b9d6fb 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -2,6 +2,8 @@ ## New Features +- Register or Login with email and password + ## Improvements ## Bug Fixes diff --git a/locales/external/de.json b/locales/external/de.json index 40b9c343a9..9c662ce994 100644 --- a/locales/external/de.json +++ b/locales/external/de.json @@ -15,7 +15,7 @@ "invitation.getCodeMessage": "You can get an invitation code through the following ways:", "invitation.title": "Invitation Code", "login.backToWebApp": "Back to the web app", - "login.logInTo": "Log in to ", + "login.logInTo": "Login to ", "login.openApp": "Open app", "login.redirecting": "Redirecting", "login.welcomeTo": "Welcome to ", diff --git a/locales/external/en.json b/locales/external/en.json index ac5ff6e0d8..a0ac35eb35 100644 --- a/locales/external/en.json +++ b/locales/external/en.json @@ -34,14 +34,37 @@ "invitation.getCodeMessage": "You can get an invitation code in the following ways:", "invitation.title": "Invitation Code", "login.backToWebApp": "Back To Web App", + "login.confirm_password.label": "Confirm Password", "login.continueWith": "Continue with {{provider}}", - "login.logInTo": "Log in to ", + "login.email": "Email", + "login.forget_password.description": "Enter the email address associated with your account and we’ll send you an email about how to reset your password.", + "login.forget_password.label": "Forget Password", + "login.forget_password.note": "Forgot your password?", + "login.forget_password.success": "Email has been sent successfully", + "login.logInTo": "Login to ", + "login.logInWithEmail": "Login with email", + "login.new_password.label": "New Password", "login.openApp": "Open App", + "login.or": "Or", + "login.password": "Password", "login.redirecting": "Redirecting", + "login.register": "Create one", + "login.reset_password.description": "Enter new password and confirm it to reset your password", + "login.reset_password.label": "Reset Password", + "login.reset_password.success": "Password has been successfully reset", "login.signOut": "Sign out", + "login.signUp": "Sign up with email", + "login.submit": "Submit", "login.welcomeTo": "Welcome to ", "redirect.continueInBrowser": "Continue in Browser", "redirect.instruction": "Now is the time to open {{app_name}} and safely close this page.", "redirect.openApp": "Open {{app_name}}", - "redirect.successMessage": "You have successfully connected to {{app_name}} Account." + "redirect.successMessage": "You have successfully connected to {{app_name}} Account.", + "register.confirm_password": "Confirm Password", + "register.email": "Email", + "register.label": "Create a {{app_name}} account", + "register.login": "Login", + "register.note": "Already have an account? ", + "register.password": "Password", + "register.submit": "Create account" } diff --git a/locales/settings/en.json b/locales/settings/en.json index 7c2788fe4f..1c770ab962 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -259,14 +259,21 @@ "lists.title": "Title", "lists.view": "View", "profile.avatar.label": "Avatar", + "profile.change_password.label": "Change Password", + "profile.confirm_password.label": "Confirm Password", + "profile.current_password.label": "Current Password", + "profile.email.label": "Email", "profile.handle.description": "Your unique identifier.", "profile.handle.label": "Handle", "profile.name.description": "Your public display name.", "profile.name.label": "Display Name", + "profile.new_password.label": "New Password", + "profile.reset_password_mail_sent": "Reset password mail sent.", "profile.sidebar_title": "Profile", "profile.submit": "Submit", "profile.title": "Profile Settings", "profile.updateSuccess": "Profile updated.", + "profile.update_password_success": "Password updated.", "titles.about": "About", "titles.actions": "Actions", "titles.appearance": "Appearance", diff --git a/packages/shared/src/auth.ts b/packages/shared/src/auth.ts index f3a992c5a4..6d8d342de9 100644 --- a/packages/shared/src/auth.ts +++ b/packages/shared/src/auth.ts @@ -14,6 +14,14 @@ const serverPlugins = [ id: "createSession", $InferServerPlugin: {} as (typeof authPlugins)[1], }, + inferAdditionalFields({ + user: { + handle: { + type: "string", + required: false, + }, + }, + }), ] satisfies BetterAuthClientPlugin[] const authClient = createAuthClient({ @@ -21,14 +29,44 @@ const authClient = createAuthClient({ plugins: serverPlugins, }) -export const { signIn, signOut, getSession, getProviders, createSession } = authClient +// @keep-sorted +export const { + changePassword, + createSession, + forgetPassword, + getProviders, + getSession, + linkSocial, + listAccounts, + resetPassword, + signIn, + signOut, + signUp, + updateUser, +} = authClient export const LOGIN_CALLBACK_URL = `${WEB_URL}/login` export type LoginRuntime = "browser" | "app" -export const loginHandler = (provider: string, runtime: LoginRuntime = "app") => { +export const loginHandler = async ( + provider: string, + runtime?: LoginRuntime, + args?: { + email?: string + password?: string + }, +) => { + const { email, password } = args ?? {} if (IN_ELECTRON) { window.open(`${WEB_URL}/login?provider=${provider}`) } else { + if (provider === "credential") { + if (!email || !password) { + window.location.href = "/login" + return + } + return signIn.email({ email, password }) + } + signIn.social({ provider: provider as "google" | "github" | "apple", callbackURL: runtime === "app" ? LOGIN_CALLBACK_URL : undefined, diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index f009cb30c9..738643bb77 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -341,7 +341,32 @@ declare const actions: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: { + name: string; + condition: ConditionItem[] | ConditionItem[][]; + result: { + disabled?: boolean; + translation?: z.infer; + summary?: boolean; + readability?: boolean; + sourceContent?: boolean; + silence?: boolean; + block?: boolean; + newEntryNotification?: boolean; + rewriteRules?: { + from: string; + to: string; + }[]; + blockRules?: { + field: z.infer; + operator: z.infer; + value: string | number; + }[]; + webhooks?: string[]; + }; + }[]; + }>; }; dialect: "pg"; }>; @@ -1004,7 +1029,38 @@ declare const airdrops: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: { + "Invitations count": number; + "Purchase lists cost": number; + "Total tip amount": number; + "Feeds subscriptions count": number; + "Lists subscriptions count": number; + "Inbox subscriptions count": number; + "Recent read count in the last month": number; + "Mint count": number; + "Claimed feeds count": number; + "Claimed feeds subscriptions count": number; + "Lists with more than 1 feed count": number; + "Created lists subscriptions count": number; + "Created lists income amount": number; + "GitHub Community Contributions": number; + "Invitations count Rank": number; + "Purchase lists cost Rank": number; + "Total tip amount Rank": number; + "Feeds subscriptions count Rank": number; + "Lists subscriptions count Rank": number; + "Inbox subscriptions count Rank": number; + "Recent read count in the last month Rank": number; + "Mint count Rank": number; + "Claimed feeds count Rank": number; + "Claimed feeds subscriptions count Rank": number; + "Lists with more than 1 feed count Rank": number; + "Created lists subscriptions count Rank": number; + "Created lists income amount Rank": number; + "GitHub Community Contributions Rank": number; + } | null; + }>; verify: drizzle_orm_pg_core.PgColumn<{ name: "verify"; tableName: "airdrops"; @@ -1485,6 +1541,15 @@ declare const CommonEntryFields: { data: string[]; driverParam: string | string[]; enumValues: [string, ...string[]]; + size: undefined; + baseBuilder: { + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }; }, { name: "categories"; dataType: "string"; @@ -1711,7 +1776,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: MediaModel[]; + }>; categories: drizzle_orm_pg_core.PgColumn<{ name: "categories"; tableName: "entries"; @@ -1741,10 +1808,20 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; attachments: drizzle_orm_pg_core.PgColumn<{ name: "attachments"; tableName: "entries"; @@ -1761,7 +1838,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: AttachmentsModel[]; + }>; extra: drizzle_orm_pg_core.PgColumn<{ name: "extra"; tableName: "entries"; @@ -1778,7 +1857,9 @@ declare const entries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: ExtraModel; + }>; language: drizzle_orm_pg_core.PgColumn<{ name: "language"; tableName: "entries"; @@ -2250,10 +2331,20 @@ declare const entryReadHistories: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "user_ids"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; readCount: drizzle_orm_pg_core.PgColumn<{ name: "read_count"; tableName: "entryReadHistories"; @@ -3112,7 +3203,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: MediaModel[]; + }>; categories: drizzle_orm_pg_core.PgColumn<{ name: "categories"; tableName: "inboxes_entries"; @@ -3142,10 +3235,20 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "categories"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; attachments: drizzle_orm_pg_core.PgColumn<{ name: "attachments"; tableName: "inboxes_entries"; @@ -3162,7 +3265,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: AttachmentsModel[]; + }>; extra: drizzle_orm_pg_core.PgColumn<{ name: "extra"; tableName: "inboxes_entries"; @@ -3179,7 +3284,9 @@ declare const inboxesEntries: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: ExtraModel; + }>; language: drizzle_orm_pg_core.PgColumn<{ name: "language"; tableName: "inboxes_entries"; @@ -4117,10 +4224,20 @@ declare const lists: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, object, object>; + }, {}, {}>; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + baseBuilder: drizzle_orm_pg_core.PgColumnBuilder<{ + name: "feed_ids"; + dataType: "string"; + columnType: "PgText"; + data: string; + enumValues: [string, ...string[]]; + driverParam: string; + }, {}, {}, drizzle_orm.ColumnBuilderExtraConfig>; + size: undefined; + }>; title: drizzle_orm_pg_core.PgColumn<{ name: "title"; tableName: "lists"; @@ -4710,7 +4827,9 @@ declare const settings: drizzle_orm_pg_core.PgTableWithColumns<{ baseColumn: never; identity: undefined; generated: undefined; - }, {}, {}>; + }, {}, { + $type: Record; + }>; updateAt: drizzle_orm_pg_core.PgColumn<{ name: "update_at"; tableName: "settings"; @@ -5036,16 +5155,16 @@ declare const users: drizzle_orm_pg_core.PgTableWithColumns<{ dialect: "pg"; }>; declare function lower(handle: AnyPgColumn): SQL; -declare const usersOpenApiSchema: z.ZodObject; - email: z.ZodString; - emailVerified: z.ZodNullable; - image: z.ZodNullable; - handle: z.ZodNullable; - createdAt: z.ZodDate; - updatedAt: z.ZodDate; -}, "email">, z.UnknownKeysParam, z.ZodTypeAny, { +declare const usersOpenApiSchema: zod.ZodObject; + email: zod.ZodString; + emailVerified: zod.ZodNullable; + image: zod.ZodNullable; + handle: zod.ZodNullable; + createdAt: zod.ZodDate; + updatedAt: zod.ZodDate; +}, "email">, "strip", zod.ZodTypeAny, { name: string | null; id: string; emailVerified: boolean | null; @@ -6203,12 +6322,13 @@ declare const boosts: drizzle_orm_pg_core.PgTableWithColumns<{ declare const auth: { handler: (request: Request) => Promise; api: { - getSession: ((context: { + getSession: (context: { headers: Headers; query?: { disableCookieCache?: boolean; } | undefined; - }) => Promise<{ + asResponse?: R | undefined; + }) => false extends R ? Promise<{ user: { id: string; createdAt: Date; @@ -6239,17 +6359,17 @@ declare const auth: { toUserId: string | null; } | undefined; role: "user" | "trial"; - } | null>) & { + } | null> & { options: { method: "GET"; query: zod.ZodOptional; + disableCookieCache: zod.ZodOptional]>>; disableRefresh: zod.ZodOptional; }, "strip", zod.ZodTypeAny, { disableCookieCache?: boolean | undefined; disableRefresh?: boolean | undefined; }, { - disableCookieCache?: boolean | undefined; + disableCookieCache?: string | boolean | undefined; disableRefresh?: boolean | undefined; }>>; requireHeaders: true; @@ -6295,9 +6415,19 @@ declare const auth: { metadata: { CUSTOM_SESSION: boolean; }; + query: zod.ZodOptional]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }; path: "/get-session"; - }; + } : Promise; } & { getProviders: { >; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; + newUserCallbackURL: zod.ZodOptional; errorCallbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; disableRedirect: zod.ZodOptional; idToken: zod.ZodOptional>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6385,10 +6516,11 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6397,6 +6529,7 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }>; @@ -6472,8 +6605,9 @@ declare const auth: { }>>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; + newUserCallbackURL: zod.ZodOptional; errorCallbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; disableRedirect: zod.ZodOptional; idToken: zod.ZodOptional>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6504,10 +6638,11 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; idToken?: { token: string; accessToken?: string | undefined; @@ -6516,6 +6651,7 @@ declare const auth: { nonce?: string | undefined; } | undefined; callbackURL?: string | undefined; + newUserCallbackURL?: string | undefined; errorCallbackURL?: string | undefined; disableRedirect?: boolean | undefined; }>; @@ -6684,11 +6820,25 @@ declare const auth: { schema: { type: "object"; properties: { - user: { + id: { type: string; + description: string; }; - session: { + email: { + type: string; + description: string; + }; + name: { + type: string; + description: string; + }; + image: { + type: string; + description: string; + }; + emailVerified: { type: string; + description: string; }; }; }; @@ -6701,38 +6851,11 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/sign-up/email"; options: { @@ -6802,11 +6925,25 @@ declare const auth: { schema: { type: "object"; properties: { - user: { + id: { + type: string; + description: string; + }; + email: { type: string; + description: string; }; - session: { + name: { type: string; + description: string; + }; + image: { + type: string; + description: string; + }; + emailVerified: { + type: string; + description: string; }; }; }; @@ -6850,9 +6987,6 @@ declare const auth: { schema: { type: "object"; properties: { - session: { - type: string; - }; user: { type: string; }; @@ -6876,22 +7010,12 @@ declare const auth: { }] ? Response : { user: { id: string; - createdAt: Date; - updatedAt: Date; email: string; - emailVerified: boolean; name: string; - image?: string | null | undefined; - }; - session: { - id: string; - userId: string; + image: string | null | undefined; + emailVerified: boolean; createdAt: Date; updatedAt: Date; - expiresAt: Date; - token: string; - ipAddress?: string | null | undefined; - userAgent?: string | null | undefined; }; redirect: boolean; url: string | undefined; @@ -6926,9 +7050,6 @@ declare const auth: { schema: { type: "object"; properties: { - session: { - type: string; - }; user: { type: string; }; @@ -7806,7 +7927,13 @@ declare const auth: { }>]>(...ctx: C): Promise; path: "/update-user"; options: { @@ -8540,12 +8667,12 @@ declare const auth: { }>>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }>; use: better_call.Endpoint>; body: zod.ZodObject<{ callbackURL: zod.ZodOptional; - provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab")[]]>; + provider: zod.ZodEnum<["github", ...("apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit")[]]>; }, "strip", zod.ZodTypeAny, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }, { - provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab"; + provider: "apple" | "discord" | "facebook" | "github" | "google" | "microsoft" | "spotify" | "twitch" | "twitter" | "dropbox" | "linkedin" | "gitlab" | "reddit"; callbackURL?: string | undefined; }>; use: better_call.Endpoint; }; plugins: ({ id: "custom-session"; @@ -8915,6 +9047,16 @@ declare const auth: { metadata: { CUSTOM_SESSION: boolean; }; + query: zod.ZodOptional]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }> | undefined)?]>(...ctx: C): Promise]>>; + disableRefresh: zod.ZodOptional; + }, "strip", zod.ZodTypeAny, { + disableCookieCache?: boolean | undefined; + disableRefresh?: boolean | undefined; + }, { + disableCookieCache?: string | boolean | undefined; + disableRefresh?: boolean | undefined; + }>>; }; method: better_call.Method | better_call.Method[]; headers: Headers; @@ -9041,6 +9193,28 @@ declare const auth: { }; }; }; + $ErrorCodes: { + USER_NOT_FOUND: string; + FAILED_TO_CREATE_USER: string; + FAILED_TO_CREATE_SESSION: string; + FAILED_TO_UPDATE_USER: string; + FAILED_TO_GET_SESSION: string; + INVALID_PASSWORD: string; + INVALID_EMAIL: string; + INVALID_EMAIL_OR_PASSWORD: string; + SOCIAL_ACCOUNT_ALREADY_LINKED: string; + PROVIDER_NOT_FOUND: string; + INVALID_TOKEN: string; + ID_TOKEN_NOT_SUPPORTED: string; + FAILED_TO_GET_USER_INFO: string; + USER_EMAIL_NOT_FOUND: string; + EMAIL_NOT_VERIFIED: string; + PASSWORD_TOO_SHORT: string; + PASSWORD_TOO_LONG: string; + USER_ALREADY_EXISTS: string; + EMAIL_CAN_NOT_BE_UPDATED: string; + CREDENTIAL_ACCOUNT_NOT_FOUND: string; + }; }; type AuthSession = Awaited>; @@ -9089,7 +9263,7 @@ declare const _routes: hono_hono_base.HonoBase