redirect on login now works
This commit is contained in:
		
							parent
							
								
									8a04297768
								
							
						
					
					
						commit
						e1cdba824a
					
				
							
								
								
									
										
											BIN
										
									
								
								prisma/dev.db
								
								
								
								
							
							
						
						
									
										
											BIN
										
									
								
								prisma/dev.db
								
								
								
								
							
										
											Binary file not shown.
										
									
								
							|  | @ -6,28 +6,21 @@ import { loginSchema, LoginSchema } from 'app/login/schema'; | ||||||
| import { NextResponse } from 'next/server'; | import { NextResponse } from 'next/server'; | ||||||
| 
 | 
 | ||||||
| export async function getJWTSecretKey<Uint8Array>() { | export async function getJWTSecretKey<Uint8Array>() { | ||||||
| 	console.log('getJWTSecretKey called!') |  | ||||||
| 	const secret = process.env.JWT_SECRET | 	const secret = process.env.JWT_SECRET | ||||||
| 	console.log('secret: ' + secret) |  | ||||||
| 	if (!secret) throw new Error("There is no JWT secret key") | 	if (!secret) throw new Error("There is no JWT secret key") | ||||||
| 	console.log('encoding...') |  | ||||||
| 	try { | 	try { | ||||||
| 		const enc: Uint8Array = new TextEncoder().encode(secret) | 		const enc: Uint8Array = new TextEncoder().encode(secret) | ||||||
| 		console.log('enc') |  | ||||||
| 		return enc | 		return enc | ||||||
| 	} catch (error) { | 	} catch (error) { | ||||||
| 		console.error('aw shit: ' + error.message) | 		throw new Error("failed to getJWTSecretKey", error.message) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function verifyJwt(token: string): Promise<JWTPayload | null> { | export async function verifyJwt(token: string): Promise<JWTPayload | null> { | ||||||
| 	console.log('verifyJwt called for token: ' + token) |  | ||||||
| 	const key = await getJWTSecretKey() |  | ||||||
| 	console.log('key: ' + key) |  | ||||||
| 	const { payload } = await jwtVerify(token, key) |  | ||||||
| 	console.log('payload: ' + payload) |  | ||||||
| 	return payload |  | ||||||
| 	try { | 	try { | ||||||
|  | 		const key = await getJWTSecretKey() | ||||||
|  | 		const { payload } = await jwtVerify(token, key) | ||||||
|  | 		return payload | ||||||
| 	} catch { | 	} catch { | ||||||
| 		return null | 		return null | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -3,24 +3,39 @@ import { Genre, Story } from "@prisma/client" | ||||||
| import prisma from "./db" | import prisma from "./db" | ||||||
| import { revalidatePath } from "next/cache" | import { revalidatePath } from "next/cache" | ||||||
| import { redirect } from "next/navigation" | import { redirect } from "next/navigation" | ||||||
|  | import { z } from "zod" | ||||||
|  | import { storySchema } from "app/ui/forms/schemas" | ||||||
| 
 | 
 | ||||||
| export async function createStory(data: Story & { genres: number[] }) { | //TODO - data validation, error handling, unauthorized access handling
 | ||||||
|  | 
 | ||||||
|  | export async function createStory(data: Story & { genres: number[] }): Promise<Story> | undefined { | ||||||
|  | 	// will return undefined if middleware authorization fails
 | ||||||
| 	"use server" | 	"use server" | ||||||
|  | 	//Prepare data
 | ||||||
| 	const genresArray = data.genres.map((e) => { return { id: e } }) | 	const genresArray = data.genres.map((e) => { return { id: e } }) | ||||||
| 	const res = await prisma.story.create({ | 	const storyData = { | ||||||
| 		data: { |  | ||||||
| 		title: data.title, | 		title: data.title, | ||||||
| 		word_count: data.word_count, | 		word_count: data.word_count, | ||||||
| 	} | 	} | ||||||
| 	}) | 	//prepare schemas
 | ||||||
| 	console.log(res) | 	const schema = storySchema.omit({ id: true, genres: true }) | ||||||
| 	const genresRes = await prisma.story.update({ | 	const genreSchema = z.object({ id: z.number() }) | ||||||
|  | 	const genresSchema = z.array(genreSchema) | ||||||
|  | 	//validate
 | ||||||
|  | 	const isSafe = schema.safeParse(storyData) && genresSchema.safeParse(genresArray) | ||||||
|  | 	if (!isSafe) throw new Error("server-side validation failed") | ||||||
|  | 	//submit
 | ||||||
|  | 	try { | ||||||
|  | 		const res = await prisma.story.create({ data: storyData }) | ||||||
|  | 		await prisma.story.update({ | ||||||
| 			where: { id: res.id }, | 			where: { id: res.id }, | ||||||
| 			data: { | 			data: { | ||||||
| 				genres: { set: genresArray } | 				genres: { set: genresArray } | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	console.log(genresRes) | 	} catch (error) { | ||||||
|  | 		throw new Error("database failure") | ||||||
|  | 	} | ||||||
| 	revalidatePath("/story") | 	revalidatePath("/story") | ||||||
| 	redirect("/story") | 	redirect("/story") | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,22 +9,22 @@ import { Button } from "@/components/ui/button"; | ||||||
| import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||||
| import { loginSchema } from "./schema"; | import { loginSchema } from "./schema"; | ||||||
| import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||||
| 
 | import { useSearchParams } from "next/navigation"; | ||||||
|  | import revalidate from "./revalidate"; | ||||||
|  | import { useState } from "react"; | ||||||
|  | import Link from "next/link"; | ||||||
| 
 | 
 | ||||||
| export default function LoginForm() { | export default function LoginForm() { | ||||||
| 	const router = useRouter() | 	const router = useRouter() | ||||||
|  | 	const searchParams = useSearchParams() | ||||||
|  | 	const redirect = searchParams.get("from") | ||||||
| 	const form = useForm<z.infer<typeof loginSchema>>({ | 	const form = useForm<z.infer<typeof loginSchema>>({ | ||||||
| 		resolver: zodResolver(loginSchema), | 		resolver: zodResolver(loginSchema), | ||||||
| 	}) | 	}) | ||||||
|  | 	const [submitted, setSubmitted] = useState(false) | ||||||
| 
 | 
 | ||||||
| 	const onSubmit = form.handleSubmit(async (data) => { | 	const onSubmit = form.handleSubmit(async (data, event) => { | ||||||
| 		// const res = await login(data)
 | 		event.preventDefault() | ||||||
| 		// if (res?.error) {
 |  | ||||||
| 		// 	toast({ title: "Whoops!", description: res.error })
 |  | ||||||
| 		// 	form.reset()
 |  | ||||||
| 		// } else {
 |  | ||||||
| 		// 	toast({ title: "login successful" })
 |  | ||||||
| 		// }
 |  | ||||||
| 		const res = await fetch('/api/auth/login', { | 		const res = await fetch('/api/auth/login', { | ||||||
| 			method: 'POST', | 			method: 'POST', | ||||||
| 			headers: { | 			headers: { | ||||||
|  | @ -34,7 +34,10 @@ export default function LoginForm() { | ||||||
| 		}) | 		}) | ||||||
| 		if (res.status === 200) { | 		if (res.status === 200) { | ||||||
| 			toast({ title: "login successful!" }) | 			toast({ title: "login successful!" }) | ||||||
| 			router.push('/submission') | 			setSubmitted(true) | ||||||
|  | 			await revalidate(redirect) | ||||||
|  | 			router.refresh() | ||||||
|  | 			router.push(redirect) | ||||||
| 		} else { | 		} else { | ||||||
| 			toast({ title: "login failed!" }) | 			toast({ title: "login failed!" }) | ||||||
| 		} | 		} | ||||||
|  | @ -42,6 +45,8 @@ export default function LoginForm() { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	return ( | 	return ( | ||||||
|  | 		<> | ||||||
|  | 			{submitted ? <p>Logging in...</p> : | ||||||
| 				<Form {...form}> | 				<Form {...form}> | ||||||
| 					<form onSubmit={onSubmit}> | 					<form onSubmit={onSubmit}> | ||||||
| 						<FormField | 						<FormField | ||||||
|  | @ -73,6 +78,8 @@ export default function LoginForm() { | ||||||
| 						<Button type="submit">SUBMIT</Button> | 						<Button type="submit">SUBMIT</Button> | ||||||
| 					</form> | 					</form> | ||||||
| 				</Form> | 				</Form> | ||||||
|  | 			} | ||||||
|  | 		</> | ||||||
| 
 | 
 | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,11 @@ | ||||||
|  | "use server" | ||||||
|  | import { revalidatePath } from "next/cache" | ||||||
|  | export default async function revalidate(path: string) { | ||||||
|  | 	try { | ||||||
|  | 		revalidatePath(path) | ||||||
|  | 		return true | ||||||
|  | 	} catch (error) { | ||||||
|  | 		console.error(error) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -16,18 +16,12 @@ import { | ||||||
| } from "@/components/ui/form" | } from "@/components/ui/form" | ||||||
| import { Input } from "@/components/ui/input" | import { Input } from "@/components/ui/input" | ||||||
| import { Checkbox } from "@/components/ui/checkbox" | import { Checkbox } from "@/components/ui/checkbox" | ||||||
| 
 | import { storySchema } from "./schemas" | ||||||
| const formSchema = z.object({ |  | ||||||
| 	title: z.string().min(2).max(50), |  | ||||||
| 	word_count: z.number(), |  | ||||||
| 	genres: z.object({ id: z.number(), name: z.string() }).array() |  | ||||||
| }) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| export default function FancyForm({ genres }) { | export default function FancyForm({ genres }) { | ||||||
| 	// 1. Define your form.
 | 	// 1. Define your form.
 | ||||||
| 	const form = useForm<z.infer<typeof formSchema>>({ | 	const form = useForm<z.infer<typeof storySchema>>({ | ||||||
| 		resolver: zodResolver(formSchema), | 		resolver: zodResolver(storySchema), | ||||||
| 		defaultValues: { | 		defaultValues: { | ||||||
| 			title: "", | 			title: "", | ||||||
| 			word_count: 0, | 			word_count: 0, | ||||||
|  | @ -35,7 +29,7 @@ export default function FancyForm({ genres }) { | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| 	// 2. Define a submit handler.
 | 	// 2. Define a submit handler.
 | ||||||
| 	function onSubmit(values: z.infer<typeof formSchema>) { | 	function onSubmit(values: z.infer<typeof storySchema>) { | ||||||
| 		// Do something with the form values.
 | 		// Do something with the form values.
 | ||||||
| 		// ✅ This will be type-safe and validated.
 | 		// ✅ This will be type-safe and validated.
 | ||||||
| 		console.log(values) | 		console.log(values) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,7 @@ | ||||||
|  | import { z } from "zod"; | ||||||
|  | 
 | ||||||
|  | export const storySchema = z.object({ | ||||||
|  | 	title: z.string().min(2).max(50), | ||||||
|  | 	word_count: z.number(), | ||||||
|  | 	genres: z.object({ id: z.number(), name: z.string() }).array() | ||||||
|  | }) | ||||||
|  | @ -40,17 +40,23 @@ export default function StoryForm({ genres, createStory, className, existingData | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 	function onSubmit(values: z.infer<typeof formSchema>) { | 	async function onSubmit(values: z.infer<typeof formSchema>) { | ||||||
|  | 		try { | ||||||
|  | 			const res = await createStory(values) | ||||||
|  | 			//server actions return undefined if middleware authentication fails
 | ||||||
|  | 			if (res === undefined) throw new Error("something went wrong") | ||||||
| 			toast({ | 			toast({ | ||||||
| 			title: "You submitted the following values:", | 				title: "Successfully submitted:", | ||||||
| 			description: ( | 				description: values.title, | ||||||
| 				<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4"> |  | ||||||
| 					<code className="text-white">{JSON.stringify(values, null, 2)}</code> |  | ||||||
| 				</pre> |  | ||||||
| 			), |  | ||||||
| 			}) | 			}) | ||||||
| 		createStory(values) | 			//TODO refresh data without reloading page?
 | ||||||
| 		console.log(values) | 		} catch (error) { | ||||||
|  | 			toast({ | ||||||
|  | 				title: "UH-OH", | ||||||
|  | 				description: error.message | ||||||
|  | 			}) | ||||||
|  | 			console.error(error) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ export default function GenrePickerInputCell(props: CellContext<any, any>) { | ||||||
|   const genres = props.table.options.meta.genres |   const genres = props.table.options.meta.genres | ||||||
|   const [isActive, setIsActive] = useState(false) |   const [isActive, setIsActive] = useState(false) | ||||||
| 
 | 
 | ||||||
|   async function onSubmit({ genres }: { genres: number[] }) { |   async function onSubmit({ genres }: { genres: number[] }, event: Event) { | ||||||
|     event.preventDefault() |     event.preventDefault() | ||||||
|     const genresArray = genres.map((e) => { return { id: e } }) |     const genresArray = genres.map((e) => { return { id: e } }) | ||||||
|     console.log(`genres: ${genres}, genresArray: ${JSON.stringify(genresArray)}`) |     console.log(`genres: ${genres}, genresArray: ${JSON.stringify(genresArray)}`) | ||||||
|  |  | ||||||
|  | @ -13,15 +13,18 @@ function matchesWildcard(path: string, pattern: string): boolean { | ||||||
| 	return path === pattern; | 	return path === pattern; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default async function(request: NextRequest) { | export default async function(request: NextRequest): Promise<NextResponse> | undefined { | ||||||
| 	// const url = `${process.env.NEXT_PUBLIC_BASE_URL}/login?redirect=${request.nextUrl.pathname + request.nextUrl.search}`
 | 
 | ||||||
|  | 
 | ||||||
| 	const url = request.nextUrl.clone() | 	const url = request.nextUrl.clone() | ||||||
| 	url.pathname = "/login" | 	url.pathname = "/login" | ||||||
|  | 	url.searchParams.set('from', request.nextUrl.pathname) | ||||||
| 	if (protectedRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) { | 	if (protectedRoutes.some(pattern => matchesWildcard(request.nextUrl.pathname, pattern))) { | ||||||
| 		const token = request.cookies.get('token') | 		const token = request.cookies.get('token') | ||||||
| 		//NOTE - may need to add logic to return 401 for api routes
 | 		//NOTE - may need to add logic to return 401 for api routes
 | ||||||
| 
 | 
 | ||||||
| 		if (!token) { | 		if (!token) { | ||||||
|  | 			console.log("there is no jwt") | ||||||
| 			return NextResponse.redirect(url) | 			return NextResponse.redirect(url) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -34,12 +37,14 @@ export default async function(request: NextRequest) { | ||||||
| 				request.cookies.delete('token') | 				request.cookies.delete('token') | ||||||
| 				return NextResponse.redirect(url) | 				return NextResponse.redirect(url) | ||||||
| 			} | 			} | ||||||
| 		} catch { | 		} catch (error) { | ||||||
| 			//delete token (failsafe)
 | 			//delete token (failsafe)
 | ||||||
|  | 			console.error("failed to very jwt", error.message) | ||||||
| 			request.cookies.delete('token') | 			request.cookies.delete('token') | ||||||
| 			return NextResponse.redirect(url) | 			return NextResponse.redirect(url) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		//TODO - TEST THIS BECAUSE IT PROBABLY DOESN'T WORK
 | ||||||
| 		//redirect from login if already logged in
 | 		//redirect from login if already logged in
 | ||||||
| 		let redirectToApp = false | 		let redirectToApp = false | ||||||
| 		if (request.nextUrl.pathname === "/login") { | 		if (request.nextUrl.pathname === "/login") { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue