Skip to content

Commit

Permalink
feat: authentication, form validation
Browse files Browse the repository at this point in the history
  • Loading branch information
elianiva committed Jan 18, 2024
1 parent 5803838 commit 681aac3
Show file tree
Hide file tree
Showing 20 changed files with 452 additions and 6 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@
},
"type": "module",
"dependencies": {
"@auth/core": "^0.21.0",
"@auth/sveltekit": "^0.7.0",
"@felte/validator-zod": "^1.0.17",
"@types/node": "^20.11.5",
"argon2": "^0.31.2",
"bits-ui": "^0.5.7",
"clsx": "^2.0.0",
"felte": "^1.2.12",
"lucide-svelte": "^0.285.0",
"mongodb": "^6.3.0",
"svelte-french-toast": "^1.2.0",
"tailwind-merge": "^1.14.0",
"tailwind-variants": "^0.1.14",
"vitest": "^1.2.0",
Expand Down
61 changes: 61 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import "@auth/sveltekit";

declare module "@auth/sveltekit" {
interface User {
id: string;
fullname: string;
email: string;
username: string;
role: string;
}
}

// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
Expand All @@ -9,4 +21,5 @@ declare global {
}
}


export {};
2 changes: 1 addition & 1 deletion src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
<div style="display: contents" class="h-full"></div>%sveltekit.body%</div>
</body>
</html>
4 changes: 4 additions & 0 deletions src/app.postcss
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@
body {
@apply bg-background text-foreground;
}
html,
body {
@apply h-full;
}
}
115 changes: 110 additions & 5 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,115 @@
import * as argon2 from "argon2";
import { SvelteKitAuth } from "@auth/sveltekit";
import CredentialsProvider from "@auth/sveltekit/providers/credentials";
import { getUserByEmailOrUsername } from "$lib/server/repositories/user-repository";
import { type Handle, redirect } from "@sveltejs/kit";
import { sequence } from "@sveltejs/kit/hooks";
import { wrapResult } from "$lib/utils";
import { authSchema } from "$lib/schema/auth";
import { dev } from "$app/environment";

export const handle = SvelteKitAuth({
providers: [CredentialsProvider({
authorize: async (credentials) => {
const authorization: Handle = async ({ event, resolve }) => {
const pathname = event.url.pathname;
// redirect for index path
if (pathname === "/") {
throw redirect(303, "/app");
}

const session = await event.locals.getSession();

if (!session) {
if (pathname.startsWith("/app")) {
throw redirect(303, "/sign-in");
}
}

if (session) {
if (pathname.startsWith("/sign-in")) {
throw redirect(303, "/app");
}
})],
});
}

return resolve(event);
};

export const handle = sequence(
SvelteKitAuth({
providers: [
CredentialsProvider({
id: "credentials",
authorize: async (credentials) => {
const validatedCredentials = authSchema.safeParse(credentials);
if (!validatedCredentials.success) {
return null;
}

// TODO(elianiva): only for temporary development purpose, please remove later
if (dev) {
if (
validatedCredentials.data.username === "admin" &&
validatedCredentials.data.password === "password"
) {
return {
id: "admin",
username: "admin",
fullname: "Administrator",
email: "admin@localhost",
role: "admin",
};
}
}

const [user, userError] = await wrapResult(
getUserByEmailOrUsername(validatedCredentials.data.username as string),
);
if (user === null || userError !== null) {
return null;
}

const [isPasswordMatch, verifyError] = await wrapResult(
argon2.verify(user.password, validatedCredentials.data.password as string, {
type: argon2.argon2i,
}),
);
if (!isPasswordMatch || verifyError !== null) {
return null;
}

return {
id: user._id.toHexString(),
username: user.username,
fullname: user.fullname,
email: user.email,
role: user.role,
};
},
}),
],
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.sub = user.id;
token.user = {
id: user.id,
username: user.username,
fullname: user.fullname,
email: user.email,
role: user.role,
};
}
return token;
},
async session({ session, token }) {
session.user = token.user;
return session;
},
},
pages: {
signIn: "/sign-in",
},
}),
authorization,
);
13 changes: 13 additions & 0 deletions src/lib/components/ui/alert/alert-description.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>

<div class={cn("text-sm [&_p]:leading-relaxed", className)} {...$$restProps}>
<slot />
</div>
21 changes: 21 additions & 0 deletions src/lib/components/ui/alert/alert-title.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
import type { HeadingLevel } from ".";
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
level?: HeadingLevel;
};
let className: $$Props["class"] = undefined;
export let level: $$Props["level"] = "h5";
export { className as class };
</script>

<svelte:element
this={level}
class={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</svelte:element>
17 changes: 17 additions & 0 deletions src/lib/components/ui/alert/alert.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
import { alertVariants, type Variant } from ".";
type $$Props = HTMLAttributes<HTMLDivElement> & {
variant?: Variant;
};
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export { className as class };
</script>

<div class={cn(alertVariants({ variant }), className)} {...$$restProps} role="alert">
<slot />
</div>
33 changes: 33 additions & 0 deletions src/lib/components/ui/alert/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { tv, type VariantProps } from "tailwind-variants";

import Root from "./alert.svelte";
import Description from "./alert-description.svelte";
import Title from "./alert-title.svelte";

export const alertVariants = tv({
base: "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",

variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive"
}
},
defaultVariants: {
variant: "default"
}
});

export type Variant = VariantProps<typeof alertVariants>["variant"];
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

export {
Root,
Description,
Title,
//
Root as Alert,
Description as AlertDescription,
Title as AlertTitle
};
7 changes: 7 additions & 0 deletions src/lib/components/ui/label/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Root from "./label.svelte";

export {
Root,
//
Root as Label
};
Loading

0 comments on commit 681aac3

Please sign in to comment.