WIP bunch of stuff

This commit is contained in:
strNophix 2022-10-16 15:58:23 +02:00
parent b4ff0c8f77
commit ea603804a2
9 changed files with 187 additions and 103 deletions

View File

@ -10,7 +10,6 @@
"email": { "email": {
"type": "string", "type": "string",
"format": "email", "format": "email",
"title": "E-Mail",
"minLength": 3, "minLength": 3,
"ory.sh/kratos": { "ory.sh/kratos": {
"credentials": { "credentials": {
@ -27,11 +26,12 @@
} }
}, },
"username": { "username": {
"title": "Username",
"type": "string", "type": "string",
"title": "Username" "minLength": 3
} }
}, },
"required": ["email", "username"], "required": ["email"],
"additionalProperties": false "additionalProperties": false
} }
} }

View File

@ -11,42 +11,42 @@ serve:
base_url: http://kratos:4434/ base_url: http://kratos:4434/
selfservice: selfservice:
default_browser_return_url: http://127.0.0.1:5173/ default_browser_return_url: http://127.0.0.1:3000/
allowed_return_urls: allowed_return_urls:
- http://127.0.0.1:5173/ - http://127.0.0.1:3000/
methods: methods:
password: password:
enabled: true enabled: true
flows: flows:
error: error:
ui_url: http://127.0.0.1:5173/error ui_url: http://127.0.0.1:3000/error
settings: settings:
ui_url: http://127.0.0.1:5173/settings ui_url: http://127.0.0.1:3000/settings
privileged_session_max_age: 15m privileged_session_max_age: 15m
recovery: recovery:
enabled: false enabled: false
ui_url: http://127.0.0.1:5173/recovery ui_url: http://127.0.0.1:3000/recovery
verification: verification:
enabled: false enabled: false
ui_url: http://127.0.0.1:5173/verification ui_url: http://127.0.0.1:3000/verification
after: after:
default_browser_return_url: http://127.0.0.1:5173/ default_browser_return_url: http://127.0.0.1:3000/
logout: logout:
after: after:
default_browser_return_url: http://127.0.0.1:5173/login default_browser_return_url: http://127.0.0.1:3000/login
login: login:
ui_url: http://127.0.0.1:5173/login ui_url: http://127.0.0.1:3000/login
lifespan: 10m lifespan: 10m
registration: registration:
lifespan: 10m lifespan: 10m
ui_url: http://127.0.0.1:5173/signup ui_url: http://127.0.0.1:3000/signup
after: after:
password: password:
hooks: hooks:
@ -75,7 +75,7 @@ identity:
default_schema_id: default default_schema_id: default
schemas: schemas:
- id: default - id: default
url: file:///etc/config/kratos/identity.schema.json url: file:///etc/config/kratos/default.schema.json
courier: courier:
smtp: smtp:

View File

@ -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'> { interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
label: string; label?: string
bottomElement?: ReactNode; bottomElement?: ReactNode
} }
const FormField = forwardRef<HTMLInputElement, FormFieldProps>( const FormField = forwardRef<HTMLInputElement, FormFieldProps>(
({ label, bottomElement, ...inputProps }, ref) => { ({ label, bottomElement, hidden, ...inputProps }, ref) => {
return ( return (
<div className="space-y-1"> <div className="space-y-1">
<label htmlFor={inputProps.id} className="font-semibold text-sm"> <label htmlFor={inputProps.id} className="font-semibold text-sm">
{label} {label}
</label> </label>
<br />
<Input {...inputProps} ref={ref} /> <Input {...inputProps} ref={ref} />
{bottomElement} {bottomElement}
</div> </div>
); )
} },
); )
export default FormField; export default FormField

View File

@ -1,19 +1,22 @@
import clsx from 'clsx'; /* eslint-disable react/display-name */
import { forwardRef } from 'react'; import clsx from "clsx"
import { forwardRef } from "react"
type InputProps = React.ComponentPropsWithoutRef<'input'>; type InputProps = React.ComponentPropsWithoutRef<"input">
const Input = forwardRef<HTMLInputElement, InputProps>(({ className, ...rest }, ref) => { const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, ...rest }, ref) => {
return ( return (
<input <input
className={clsx( className={clsx(
'bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm', "bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm",
className className,
)} )}
{...rest} {...rest}
ref={ref} ref={ref}
/> />
); )
}); },
)
export default Input; export default Input

View File

@ -1,73 +1,135 @@
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from "@hookform/resolvers/zod"
import { SelfServiceRegistrationFlow } from '@ory/client'; import { SelfServiceRegistrationFlow } from "@ory/client"
import { FC, useEffect, useState } from 'react'; import { useRouter } from "next/router"
import { SubmitHandler, useForm } from 'react-hook-form'; import { useMemo } from "react"
import { z } from 'zod'; 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'; const PASSWORD_REGEX =
import InlineLink from './InlineLink'; /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/
import SubmitButton from './SubmitButton';
const PASSWORD_REGEX = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$/;
const SignupFormSchema = z const SignupFormSchema = z
.object({ .object({
username: z.string().trim().min(1).max(16), csrfToken: z.string(),
password: z.string().trim().regex(PASSWORD_REGEX), 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(), 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, { .refine((data) => data.password === data.passwordRepeat, {
message: "Passwords don't match", message: "Passwords do not match.",
path: ['passwordRepeat'], path: ["passwordRepeat"],
}); })
type SignupFormValues = z.infer<typeof SignupFormSchema>; type SignupFormValues = z.infer<typeof SignupFormSchema>
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 SignupForm: FC = () => {
// const navigate = useNavigate(); const router = useRouter()
// const { flow: flowId, return_to: returnTo } = useParams<{ flow?: string; return_to?: string }>(); const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
// const [flow, setFlow] = useState<SelfServiceRegistrationFlow>(); const { flow: flowId, return_to: returnTo } = router.query
const { register, handleSubmit } = useForm<SignupFormValues>({ const { register, handleSubmit, formState } = useForm<SignupFormValues>({
resolver: zodResolver(SignupFormSchema), 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<SignupFormValues> = async (data) => { const onSubmit: SubmitHandler<SignupFormValues> = 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 ( return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{flow && (
<Input
hidden={true}
value={flow.ui.nodes[0].attributes.value}
{...register("csrfToken")}
/>
)}
<p className="text-sm"> <p className="text-sm">
Creating an account allows you to participate in chat, follow your favorite channels, and Creating an account allows you to participate in chat, follow your
broadcast from your own channel. favorite channels, and broadcast from your own channel.
</p> </p>
{formFields.map((field) => (
<FormField <FormField
label="Username" key={field.id}
{...register('username')} id={field.id}
className="py-2 px-2 outline-2 w-full" type={field.type}
/> label={field.label}
<FormField {...register(field.id as any)}
label="Password"
{...register('password')}
type="password"
className="py-2 px-2 outline-2 w-full"
/>
<FormField
label="Confirm Password"
{...register('passwordRepeat')}
type="password"
className="py-2 px-2 outline-2 w-full"
/>
<FormField
label="Email"
{...register('email')}
type="email"
className="py-2 px-2 outline-2 w-full" className="py-2 px-2 outline-2 w-full"
bottomElement={
<p className="text-xs">
{formState.errors[(field.id as any) || ""]?.message}
</p>
}
/> />
))}
<p className="text-sm text-center"> <p className="text-sm text-center">
By clicking Sign Up, you are agreeing to twitch-clone&apos;s{' '} By clicking Sign Up, you are agreeing to twitch-clone&apos;s{" "}
<InlineLink to="https://tosdr.org/en/service/200"> <InlineLink to="https://tosdr.org/en/service/200">
Terms of Service Terms of Service
</InlineLink> </InlineLink>
@ -75,7 +137,7 @@ const SignupForm: FC = () => {
</p> </p>
<SubmitButton className="w-full" value="Sign Up" /> <SubmitButton className="w-full" value="Sign Up" />
</form> </form>
); )
}; }
export default SignupForm; export default SignupForm

View File

@ -1,16 +1,19 @@
import clsx from 'clsx'; import clsx from "clsx"
import { FC } from 'react'; import { FC } from "react"
type ButtonProps = React.ComponentPropsWithoutRef<'input'>; type ButtonProps = React.ComponentPropsWithoutRef<"input">
const SubmitButton: FC<ButtonProps> = ({ className, ...rest }) => { const SubmitButton: FC<ButtonProps> = ({ className, ...rest }) => {
return ( return (
<input <input
type="submit" type="submit"
className={clsx('rounded-md bg-violet-500 font-semibold py-2 text-sm', className)} className={clsx(
"cursor-pointer rounded-md bg-violet-500 font-semibold py-2 text-sm",
className,
)}
{...rest} {...rest}
/> />
); )
}; }
export default SubmitButton; export default SubmitButton

View File

@ -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';

View File

@ -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,
})

View File

@ -0,0 +1,4 @@
import { Configuration, V0alpha2Api } from "@ory/client"
import { edgeConfig } from "@ory/integrations/next"
export default new V0alpha2Api(new Configuration(edgeConfig))