WIP bunch of stuff
This commit is contained in:
parent
b4ff0c8f77
commit
ea603804a2
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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's{' '}
|
By clicking Sign Up, you are agreeing to twitch-clone'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
|
||||||
|
@ -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
|
||||||
|
@ -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