redirect on login now works
This commit is contained in:
parent
9eb558dc2f
commit
c7dfbee0e0
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