redirect on login now works

This commit is contained in:
andrzej 2024-09-18 11:56:08 +02:00
parent 8a04297768
commit e1cdba824a
10 changed files with 130 additions and 92 deletions

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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") {