-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: move account pages from club page
- Loading branch information
Showing
20 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { login } from "./login"; | ||
import { signup } from "./signup"; | ||
import { settings } from "./settings"; | ||
import { logout } from "./logout"; | ||
|
||
export const server = { | ||
login, | ||
signup, | ||
settings, | ||
logout, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { ActionError, defineAction } from "astro:actions"; | ||
import { z } from "astro:schema"; | ||
import { slug } from "@lib/schema"; | ||
import { db, users } from "@lib/db"; | ||
import { lucia, hashOptions } from "@lib/auth/index"; | ||
import { verify } from "@node-rs/argon2"; | ||
import { eq } from "drizzle-orm"; | ||
|
||
export const login = defineAction({ | ||
accept: "form", | ||
input: z.object({ | ||
slug: slug(), | ||
password: z.string(), | ||
}), | ||
handler: async ({ slug, password }, { cookies }) => { | ||
const authError = new ActionError({ | ||
code: "BAD_REQUEST", | ||
message: "The entered slug or password is incorrect.", | ||
}); | ||
|
||
const [user] = await db.select().from(users).where(eq(users.slug, slug)); | ||
if (!user) throw authError; | ||
|
||
const passwordValid = await verify(user.password, password, hashOptions); | ||
if (!passwordValid) throw authError; | ||
|
||
const session = await lucia.createSession(user.id, {}); | ||
const sessionCookie = lucia.createSessionCookie(session.id); | ||
|
||
cookies.set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes, | ||
); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ActionError, defineAction } from "astro:actions"; | ||
import { lucia } from "@lib/auth/index"; | ||
|
||
export const logout = defineAction({ | ||
accept: "form", | ||
handler: async (_, { locals: { user, session } }) => { | ||
if (!user || !session) | ||
throw new ActionError({ | ||
code: "UNAUTHORIZED", | ||
message: "You must be logged in to log out.", | ||
}); | ||
|
||
await lucia.invalidateSession(session.id); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { ActionError, defineAction } from "astro:actions"; | ||
import { z } from "astro:schema"; | ||
import { emptyString, slug } from "@lib/schema"; | ||
import { db, users } from "@lib/db"; | ||
import { hashOptions } from "@lib/auth/index"; | ||
import { hash } from "@node-rs/argon2"; | ||
import { eq } from "drizzle-orm"; | ||
|
||
export const settings = defineAction({ | ||
accept: "form", | ||
input: z.object({ | ||
name: emptyString().nullable().or(z.string()), | ||
slug: emptyString().nullable().or(slug()), | ||
password: emptyString().nullable().or(z.string()), | ||
}), | ||
handler: async ({ name, slug, password }, { locals: { user } }) => { | ||
if (!user) | ||
throw new ActionError({ | ||
code: "UNAUTHORIZED", | ||
message: "You need to be logged in to edit your preferences.", | ||
}); | ||
|
||
if (name == user.name) name = null; | ||
if (slug == user.slug) slug = null; | ||
if (!(name || slug || password)) return; | ||
|
||
await db | ||
.update(users) | ||
.set({ | ||
name: name ?? undefined, | ||
slug: slug ?? undefined, | ||
password: password ? await hash(password, hashOptions) : undefined, | ||
}) | ||
.where(eq(users.id, user.id)); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { defineAction } from "astro:actions"; | ||
import { z } from "astro:schema"; | ||
import { slug } from "@lib/schema"; | ||
import { db, users } from "@lib/db"; | ||
import { lucia, hashOptions } from "@lib/auth/index"; | ||
import { hash } from "@node-rs/argon2"; | ||
import { generateIdFromEntropySize } from "lucia"; | ||
|
||
export const signup = defineAction({ | ||
accept: "form", | ||
input: z.object({ | ||
name: z.string(), | ||
slug: slug(), | ||
password: z.string(), | ||
}), | ||
handler: async ({ name, slug, password }, { cookies }) => { | ||
const id = generateIdFromEntropySize(10); | ||
await db.insert(users).values({ | ||
id, | ||
name, | ||
slug, | ||
password: await hash(password, hashOptions), | ||
}); | ||
|
||
const session = await lucia.createSession(id, {}); | ||
const sessionCookie = lucia.createSessionCookie(session.id); | ||
|
||
cookies.set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes, | ||
); | ||
}, | ||
}); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
declare namespace App { | ||
interface Locals { | ||
session: import("lucia").Session | null; | ||
user: import("lucia").User | null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
--- | ||
import Icon from "@assets/icon.png"; | ||
import "@fontsource-variable/baloo-2"; | ||
import Header from "@fancade-club/comps/layouts/Header.astro"; | ||
import Footer from "@fancade-club/comps/layouts/Footer.astro"; | ||
import Disclaimer from "@fancade-club/comps/layouts/Disclaimer.astro"; | ||
import NavLink from "@fancade-club/comps/layouts/NavLink.astro" | ||
interface Props { | ||
title?: string; | ||
description?: string; | ||
} | ||
const { title = "", description = "" } = Astro.props; | ||
const metaDescription = | ||
description || | ||
"The Fancade Club is a worldwide community of Fancade enthusiasts. Join and become a member today!"; | ||
const metaTitle = (title && title + " - ") + "Fancade Club Account"; | ||
--- | ||
|
||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="description" content={metaDescription} /> | ||
<meta name="viewport" content="width=device-width" /> | ||
<link rel="icon" type="image/svg+xml" href={Icon.src} /> | ||
<meta name="generator" content={Astro.generator} /> | ||
<title>{metaTitle}</title> | ||
</head> | ||
<body> | ||
<Header title="Account" image={Icon}><NavLink href="https://fancade.club">Club</NavLink></Header> | ||
<main> | ||
{title && <h1>{title}</h1>} | ||
<slot /> | ||
</main> | ||
<Footer><Disclaimer/> </Footer> | ||
</body> | ||
|
||
<style> | ||
body { | ||
min-height: 100vh; | ||
color: #333333; | ||
font-size: 14pt; | ||
font-family: "Baloo 2 Variable", system-ui; | ||
font-weight: 500; | ||
text-align: justify; | ||
} | ||
main { | ||
min-height: 90vh; | ||
max-width: 800px; | ||
margin: 0 auto; | ||
} | ||
</style> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Lucia } from "lucia"; | ||
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle"; | ||
import { db, sessions, users } from "@lib/db"; | ||
|
||
export const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); | ||
|
||
export const lucia = new Lucia(adapter, { | ||
sessionCookie: { | ||
attributes: { | ||
secure: import.meta.env.PROD, | ||
}, | ||
}, | ||
getUserAttributes: ({ name, slug }) => ({ | ||
name, | ||
slug, | ||
}), | ||
}); | ||
|
||
export const hashOptions = { | ||
memoryCost: 19456, | ||
timeCost: 2, | ||
hashLength: 32, | ||
parallelism: 1, | ||
}; | ||
|
||
declare module "lucia" { | ||
interface Register { | ||
Lucia: typeof lucia; | ||
DatabaseUserAttributes: { | ||
name: string; | ||
slug: string; | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./schema"; | ||
export * from "./init"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { neon } from "@neondatabase/serverless"; | ||
import { drizzle } from "drizzle-orm/neon-http"; | ||
import { DATABASE_URL } from "astro:env/server"; | ||
|
||
export const sql = neon(DATABASE_URL); | ||
export const db = drizzle(sql); | ||
export type DB = typeof db; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { pgTable, text, timestamp } from "drizzle-orm/pg-core"; | ||
|
||
export const users = pgTable("users", { | ||
id: text("id").primaryKey(), | ||
slug: text("slug").unique().notNull(), | ||
password: text("password").notNull(), | ||
name: text("name").notNull(), | ||
}); | ||
|
||
export const sessions = pgTable("sessions", { | ||
id: text("id").primaryKey(), | ||
userId: text("user_id") | ||
.notNull() | ||
.references(() => users.id), | ||
expiresAt: timestamp("expires_at", { | ||
withTimezone: true, | ||
mode: "date", | ||
}).notNull(), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { z } from "astro:schema"; | ||
import slugify from "@sindresorhus/slugify"; | ||
import zxcvbn from "zxcvbn"; | ||
|
||
export const emptyString = () => z.literal("").transform(() => null); | ||
|
||
export const slug = () => | ||
z.preprocess( | ||
(val) => slugify(z.string().parse(val)), | ||
z.string().min(3).max(16), | ||
); | ||
|
||
export const password = () => z.string().refine((val) => zxcvbn(val).score > 4); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { lucia } from "@lib/auth"; | ||
import { defineMiddleware } from "astro:middleware"; | ||
|
||
export const onRequest = defineMiddleware(async (context, next) => { | ||
const sessionId = context.cookies.get(lucia.sessionCookieName)?.value ?? null; | ||
if (!sessionId) { | ||
context.locals.user = null; | ||
context.locals.session = null; | ||
return next(); | ||
} | ||
|
||
const { session, user } = await lucia.validateSession(sessionId); | ||
if (session && session.fresh) { | ||
const sessionCookie = lucia.createSessionCookie(session.id); | ||
context.cookies.set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes, | ||
); | ||
} | ||
if (!session) { | ||
const sessionCookie = lucia.createBlankSessionCookie(); | ||
context.cookies.set( | ||
sessionCookie.name, | ||
sessionCookie.value, | ||
sessionCookie.attributes, | ||
); | ||
} | ||
context.locals.session = session; | ||
context.locals.user = user; | ||
return next(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
--- | ||
import Layout from "@layouts/Layout.astro"; | ||
--- | ||
|
||
<Layout title="Not Found"> | ||
<p> | ||
The page you tried to access doesn't exist. In case you tried to access a | ||
distinct service, it's likely it doesn't exist yet, or hasn't been approved. | ||
</p> | ||
</Layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
import Layout from "@layouts/Layout.astro"; | ||
--- | ||
|
||
<Layout title="Server Error"> | ||
<p> | ||
An internal server error occured while rendering this page. Please <a | ||
href="/contact">contribute</a | ||
> by reporting this bug and describe the sequence of actions that lead you here. | ||
</p> | ||
</Layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
const { user } = Astro.locals; | ||
if (user) return Astro.redirect("/settings"); | ||
else return Astro.redirect("/signup"); | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
--- | ||
import { actions } from "astro:actions"; | ||
import Layout from "@layouts/Layout.astro"; | ||
import Form from "@fancade-club/comps/forms/Form.astro"; | ||
import Input from "@fancade-club/comps/forms/Input.astro"; | ||
import Button from "@fancade-club/comps/forms/Button.astro"; | ||
if (Astro.locals.user) return Astro.redirect("/account"); | ||
--- | ||
|
||
<Layout title="Log In"> | ||
<Form method="POST" action={"/" + actions.login}> | ||
<Input required name="slug" label="Handle" /> | ||
<Input required type="password" name="password" label="Password" /> | ||
<Button>Log in</Button> | ||
</Form> | ||
<p>Not a member yet? <a href="/signup">Sign up</a>!</p> | ||
</Layout> |
Oops, something went wrong.