From ea603804a2de29dc8a54ab5a0a1a1e628993fddd Mon Sep 17 00:00:00 2001 From: strNophix Date: Sun, 16 Oct 2022 15:58:23 +0200 Subject: [PATCH] WIP bunch of stuff --- ...entity.schema.json => default.schema.json} | 6 +- .docker/kratos/kratos.yml | 22 +-- client/components/FormField.tsx | 22 +-- client/components/Input.tsx | 35 ++-- client/components/SignupForm.tsx | 168 ++++++++++++------ client/components/SubmitButton.tsx | 17 +- client/config/index.ts | 3 +- client/pages/api/.ory/[...paths].ts | 13 ++ client/services/ory/index.ts | 4 + 9 files changed, 187 insertions(+), 103 deletions(-) rename .docker/kratos/{identity.schema.json => default.schema.json} (89%) create mode 100644 client/pages/api/.ory/[...paths].ts create mode 100644 client/services/ory/index.ts diff --git a/.docker/kratos/identity.schema.json b/.docker/kratos/default.schema.json similarity index 89% rename from .docker/kratos/identity.schema.json rename to .docker/kratos/default.schema.json index 12222ae..d84cbb0 100644 --- a/.docker/kratos/identity.schema.json +++ b/.docker/kratos/default.schema.json @@ -10,7 +10,6 @@ "email": { "type": "string", "format": "email", - "title": "E-Mail", "minLength": 3, "ory.sh/kratos": { "credentials": { @@ -27,11 +26,12 @@ } }, "username": { + "title": "Username", "type": "string", - "title": "Username" + "minLength": 3 } }, - "required": ["email", "username"], + "required": ["email"], "additionalProperties": false } } diff --git a/.docker/kratos/kratos.yml b/.docker/kratos/kratos.yml index 7db6617..5bb6918 100644 --- a/.docker/kratos/kratos.yml +++ b/.docker/kratos/kratos.yml @@ -11,42 +11,42 @@ serve: base_url: http://kratos:4434/ selfservice: - default_browser_return_url: http://127.0.0.1:5173/ + default_browser_return_url: http://127.0.0.1:3000/ allowed_return_urls: - - http://127.0.0.1:5173/ + - http://127.0.0.1:3000/ methods: password: enabled: true flows: error: - ui_url: http://127.0.0.1:5173/error + ui_url: http://127.0.0.1:3000/error settings: - ui_url: http://127.0.0.1:5173/settings + ui_url: http://127.0.0.1:3000/settings privileged_session_max_age: 15m recovery: enabled: false - ui_url: http://127.0.0.1:5173/recovery + ui_url: http://127.0.0.1:3000/recovery verification: enabled: false - ui_url: http://127.0.0.1:5173/verification + ui_url: http://127.0.0.1:3000/verification after: - default_browser_return_url: http://127.0.0.1:5173/ + default_browser_return_url: http://127.0.0.1:3000/ logout: after: - default_browser_return_url: http://127.0.0.1:5173/login + default_browser_return_url: http://127.0.0.1:3000/login login: - ui_url: http://127.0.0.1:5173/login + ui_url: http://127.0.0.1:3000/login lifespan: 10m registration: lifespan: 10m - ui_url: http://127.0.0.1:5173/signup + ui_url: http://127.0.0.1:3000/signup after: password: hooks: @@ -75,7 +75,7 @@ identity: default_schema_id: default schemas: - id: default - url: file:///etc/config/kratos/identity.schema.json + url: file:///etc/config/kratos/default.schema.json courier: smtp: diff --git a/client/components/FormField.tsx b/client/components/FormField.tsx index fe7e64b..5c3c413 100644 --- a/client/components/FormField.tsx +++ b/client/components/FormField.tsx @@ -1,25 +1,25 @@ -import { forwardRef, ReactNode } from 'react'; +/* eslint-disable react/display-name */ +import { forwardRef, ReactNode } from "react" -import Input from './Input'; +import Input from "./Input" -interface FormFieldProps extends React.ComponentPropsWithoutRef<'input'> { - label: string; - bottomElement?: ReactNode; +interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> { + label?: string + bottomElement?: ReactNode } const FormField = forwardRef( - ({ label, bottomElement, ...inputProps }, ref) => { + ({ label, bottomElement, hidden, ...inputProps }, ref) => { return (
-
{bottomElement}
- ); - } -); + ) + }, +) -export default FormField; +export default FormField diff --git a/client/components/Input.tsx b/client/components/Input.tsx index 1162805..04f83d8 100644 --- a/client/components/Input.tsx +++ b/client/components/Input.tsx @@ -1,19 +1,22 @@ -import clsx from 'clsx'; -import { forwardRef } from 'react'; +/* eslint-disable react/display-name */ +import clsx from "clsx" +import { forwardRef } from "react" -type InputProps = React.ComponentPropsWithoutRef<'input'>; +type InputProps = React.ComponentPropsWithoutRef<"input"> -const Input = forwardRef(({ className, ...rest }, ref) => { - return ( - - ); -}); +const Input = forwardRef( + ({ className, ...rest }, ref) => { + return ( + + ) + }, +) -export default Input; +export default Input diff --git a/client/components/SignupForm.tsx b/client/components/SignupForm.tsx index 634596f..43fa3b5 100644 --- a/client/components/SignupForm.tsx +++ b/client/components/SignupForm.tsx @@ -1,73 +1,135 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { SelfServiceRegistrationFlow } from '@ory/client'; -import { FC, useEffect, useState } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; +import { zodResolver } from "@hookform/resolvers/zod" +import { SelfServiceRegistrationFlow } from "@ory/client" +import { useRouter } from "next/router" +import { useMemo } from "react" +import { FC, useEffect, useState } from "react" +import { SubmitHandler, useForm } from "react-hook-form" +import { z } from "zod" +import ory from "../services/ory" +import FormField from "./FormField" +import InlineLink from "./InlineLink" +import Input from "./Input" +import SubmitButton from "./SubmitButton" -import FormField from './FormField'; -import InlineLink from './InlineLink'; -import SubmitButton from './SubmitButton'; - -const PASSWORD_REGEX = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/; +const PASSWORD_REGEX = + /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/ const SignupFormSchema = z .object({ - username: z.string().trim().min(1).max(16), - password: z.string().trim().regex(PASSWORD_REGEX), + csrfToken: z.string(), + username: z + .string() + .trim() + .min(1, { message: "Username must be at least 1 character long." }) + .max(16, { message: "Username can't be longer than 16 characters.." }), + password: z + .string() + .trim() + .regex( + PASSWORD_REGEX, + "Password must be 8-64 long and must contain a number, uppercase, lowercase and special character.", + ), passwordRepeat: z.string().trim(), - email: z.string().email().trim(), + email: z.string().email({ message: "Not a valid email address" }).trim(), }) .refine((data) => data.password === data.passwordRepeat, { - message: "Passwords don't match", - path: ['passwordRepeat'], - }); + message: "Passwords do not match.", + path: ["passwordRepeat"], + }) -type SignupFormValues = z.infer; +type SignupFormValues = z.infer + +const formFields = [ + { id: "username", label: "Username", type: "text" }, + { id: "password", label: "Password", type: "password" }, + { id: "passwordRepeat", label: "Confirm Password", type: "password" }, + { id: "email", label: "Email", type: "email" }, +] const SignupForm: FC = () => { - // const navigate = useNavigate(); - // const { flow: flowId, return_to: returnTo } = useParams<{ flow?: string; return_to?: string }>(); - // const [flow, setFlow] = useState(); - const { register, handleSubmit } = useForm({ + const router = useRouter() + const [flow, setFlow] = useState() + const { flow: flowId, return_to: returnTo } = router.query + const { register, handleSubmit, formState } = useForm({ resolver: zodResolver(SignupFormSchema), - }); + }) + + useEffect(() => { + const func = async () => { + if (!router.isReady || flow) { + return + } + + let serviceFlow + if (flowId) { + serviceFlow = await ory.getSelfServiceRegistrationFlow(String(flowId)) + } else { + serviceFlow = + await ory.initializeSelfServiceRegistrationFlowForBrowsers( + returnTo ? String(returnTo) : undefined, + ) + } + + setFlow(serviceFlow.data) + } + + func() + }, [flowId, router, router.isReady, returnTo, flow]) const onSubmit: SubmitHandler = async (data) => { - console.log({data}) - }; + await router.push(`/signup?flow=${flow?.id}`, undefined, { + shallow: true, + }) + try { + const resp = await ory.submitSelfServiceRegistrationFlow( + String(flow?.id), + { + csrf_token: data.csrfToken, + method: "password", + password: data.password, + traits: { + email: data.email, + // username: data.username, + }, + }, + ) + console.log(resp) + } catch (e) { + console.log(e) + } + } return (
+ {flow && ( + + )}

- Creating an account allows you to participate in chat, follow your favorite channels, and - broadcast from your own channel. + Creating an account allows you to participate in chat, follow your + favorite channels, and broadcast from your own channel.

- - - - + {formFields.map((field) => ( + + {formState.errors[(field.id as any) || ""]?.message} +

+ } + /> + ))}

- By clicking Sign Up, you are agreeing to twitch-clone's{' '} + By clicking Sign Up, you are agreeing to twitch-clone's{" "} Terms of Service @@ -75,7 +137,7 @@ const SignupForm: FC = () => {

- ); -}; + ) +} -export default SignupForm; +export default SignupForm diff --git a/client/components/SubmitButton.tsx b/client/components/SubmitButton.tsx index 2314bc0..85ef69a 100644 --- a/client/components/SubmitButton.tsx +++ b/client/components/SubmitButton.tsx @@ -1,16 +1,19 @@ -import clsx from 'clsx'; -import { FC } from 'react'; +import clsx from "clsx" +import { FC } from "react" -type ButtonProps = React.ComponentPropsWithoutRef<'input'>; +type ButtonProps = React.ComponentPropsWithoutRef<"input"> const SubmitButton: FC = ({ className, ...rest }) => { return ( - ); -}; + ) +} -export default SubmitButton; +export default SubmitButton diff --git a/client/config/index.ts b/client/config/index.ts index 5421cc1..3a2a2b0 100644 --- a/client/config/index.ts +++ b/client/config/index.ts @@ -1,2 +1 @@ -export const API_URL = 'http://localhost:5000'; -export const KRATOS_URL = 'http://127.0.0.1:4433'; +export const KRATOS_URL = "http://127.0.0.1:4433" diff --git a/client/pages/api/.ory/[...paths].ts b/client/pages/api/.ory/[...paths].ts new file mode 100644 index 0000000..5d4bb31 --- /dev/null +++ b/client/pages/api/.ory/[...paths].ts @@ -0,0 +1,13 @@ +// @ory/integrations offers a package for integrating with NextJS. +import { config, createApiHandler } from "@ory/integrations/next-edge" + +// We need to export the config. +export { config } + +// And create the Ory Cloud API "bridge". +export default createApiHandler({ + fallbackToPlayground: true, + // Because vercel.app is a public suffix and setting cookies for + // vercel.app is not possible. + dontUseTldForCookieDomain: true, +}) diff --git a/client/services/ory/index.ts b/client/services/ory/index.ts new file mode 100644 index 0000000..033ac87 --- /dev/null +++ b/client/services/ory/index.ts @@ -0,0 +1,4 @@ +import { Configuration, V0alpha2Api } from "@ory/client" +import { edgeConfig } from "@ory/integrations/next" + +export default new V0alpha2Api(new Configuration(edgeConfig))