WIP bunch of stuff
This commit is contained in:
parent
b4ff0c8f77
commit
ea603804a2
@ -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
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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<HTMLInputElement, FormFieldProps>(
|
||||
({ label, bottomElement, ...inputProps }, ref) => {
|
||||
({ label, bottomElement, hidden, ...inputProps }, ref) => {
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<label htmlFor={inputProps.id} className="font-semibold text-sm">
|
||||
{label}
|
||||
</label>
|
||||
<br />
|
||||
<Input {...inputProps} ref={ref} />
|
||||
{bottomElement}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default FormField;
|
||||
export default FormField
|
||||
|
@ -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<HTMLInputElement, InputProps>(({ className, ...rest }, ref) => {
|
||||
return (
|
||||
<input
|
||||
className={clsx(
|
||||
'bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, ...rest }, ref) => {
|
||||
return (
|
||||
<input
|
||||
className={clsx(
|
||||
"bg-zinc-700 rounded-md box-border focus:outline outline-violet-400 text-sm",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
export default Input;
|
||||
export default Input
|
||||
|
@ -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<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 navigate = useNavigate();
|
||||
// const { flow: flowId, return_to: returnTo } = useParams<{ flow?: string; return_to?: string }>();
|
||||
// const [flow, setFlow] = useState<SelfServiceRegistrationFlow>();
|
||||
const { register, handleSubmit } = useForm<SignupFormValues>({
|
||||
const router = useRouter()
|
||||
const [flow, setFlow] = useState<SelfServiceRegistrationFlow>()
|
||||
const { flow: flowId, return_to: returnTo } = router.query
|
||||
const { register, handleSubmit, formState } = useForm<SignupFormValues>({
|
||||
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) => {
|
||||
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 (
|
||||
<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">
|
||||
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.
|
||||
</p>
|
||||
<FormField
|
||||
label="Username"
|
||||
{...register('username')}
|
||||
className="py-2 px-2 outline-2 w-full"
|
||||
/>
|
||||
<FormField
|
||||
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"
|
||||
/>
|
||||
{formFields.map((field) => (
|
||||
<FormField
|
||||
key={field.id}
|
||||
id={field.id}
|
||||
type={field.type}
|
||||
label={field.label}
|
||||
{...register(field.id as any)}
|
||||
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">
|
||||
By clicking Sign Up, you are agreeing to twitch-clone's{' '}
|
||||
By clicking Sign Up, you are agreeing to twitch-clone's{" "}
|
||||
<InlineLink to="https://tosdr.org/en/service/200">
|
||||
Terms of Service
|
||||
</InlineLink>
|
||||
@ -75,7 +137,7 @@ const SignupForm: FC = () => {
|
||||
</p>
|
||||
<SubmitButton className="w-full" value="Sign Up" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SignupForm;
|
||||
export default SignupForm
|
||||
|
@ -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<ButtonProps> = ({ className, ...rest }) => {
|
||||
return (
|
||||
<input
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default SubmitButton;
|
||||
export default SubmitButton
|
||||
|
@ -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"
|
||||
|
13
client/pages/api/.ory/[...paths].ts
Normal file
13
client/pages/api/.ory/[...paths].ts
Normal 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,
|
||||
})
|
4
client/services/ory/index.ts
Normal file
4
client/services/ory/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Configuration, V0alpha2Api } from "@ory/client"
|
||||
import { edgeConfig } from "@ory/integrations/next"
|
||||
|
||||
export default new V0alpha2Api(new Configuration(edgeConfig))
|
Loading…
x
Reference in New Issue
Block a user