1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 05:05:17 +02:00

Update server setup logic and add admin user creation in setup page

This commit is contained in:
Sean Morley 2024-04-18 23:00:35 +00:00
parent 7fe90f615f
commit f626370d3f
9 changed files with 235 additions and 2 deletions

View file

@ -11,3 +11,5 @@ services:
# Only necessary for externaly hosted databases such as NeonDB # Only necessary for externaly hosted databases such as NeonDB
volumes: volumes:
- ./sql:/sql - ./sql:/sql
# docker compose -f ./compose-dev.yml up --build

1
src/app.d.ts vendored
View file

@ -3,6 +3,7 @@ declare global {
interface Locals { interface Locals {
user: import("lucia").User | null; user: import("lucia").User | null;
session: import("lucia").Session | null; session: import("lucia").Session | null;
isServerSetup: boolean;
} }
} }
} }

View file

@ -1,6 +1,9 @@
import { lucia } from "$lib/server/auth"; import { lucia } from "$lib/server/auth";
import type { Handle } from "@sveltejs/kit"; import { redirect, type Handle } from "@sveltejs/kit";
import { sequence } from '@sveltejs/kit/hooks'; import { sequence } from '@sveltejs/kit/hooks';
import { db } from "$lib/db/db.server";
import { userTable } from "$lib/db/schema";
import { eq } from "drizzle-orm";
export const authHook: Handle = async ({ event, resolve }) => { export const authHook: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get(lucia.sessionCookieName); const sessionId = event.cookies.get(lucia.sessionCookieName);
@ -53,4 +56,27 @@ export const themeHook: Handle = async ({ event, resolve }) => {
return await resolve(event); return await resolve(event);
} }
export const handle = sequence(authHook, themeHook); export const setupAdminUser: Handle = async ({ event, resolve }) => {
// Check if an admin user exists
/* let result = await db
.select()
.from(userVisitedAdventures)
.where(eq(userVisitedAdventures.userId, event.locals.user.id))
.execute();*/
let adminUser = await db
.select()
.from(userTable)
.where(eq(userTable.role, 'admin'))
.execute();
// If an admin user exists, return the resolved event
if (adminUser != null && adminUser.length > 0) {
event.locals.isServerSetup = true;
return await resolve(event);
}
console.log("No admin user found");
event.locals.isServerSetup = false;
return await resolve(event);
};
export const handle = sequence(setupAdminUser, authHook, themeHook);

View file

@ -1,12 +1,15 @@
import { goto } from "$app/navigation";
import type { LayoutServerLoad, PageServerLoad } from "./$types"; import type { LayoutServerLoad, PageServerLoad } from "./$types";
export const load: LayoutServerLoad = async (event) => { export const load: LayoutServerLoad = async (event) => {
if (event.locals.user) { if (event.locals.user) {
return { return {
user: event.locals.user, user: event.locals.user,
isServerSetup: event.locals.isServerSetup,
}; };
} }
return { return {
user: null, user: null,
isServerSetup: event.locals.isServerSetup,
}; };
}; };

View file

@ -4,6 +4,21 @@
import Navbar from "$lib/components/Navbar.svelte"; import Navbar from "$lib/components/Navbar.svelte";
import type { SubmitFunction } from "@sveltejs/kit"; import type { SubmitFunction } from "@sveltejs/kit";
import "../app.css"; import "../app.css";
import { goto } from "$app/navigation";
import { onMount } from "svelte";
import { page } from "$app/stores";
let isServerSetup = data.isServerSetup;
onMount(() => {
console.log("isServerSetup", isServerSetup);
if (!isServerSetup && $page.url.pathname !== "/setup") {
goto("/setup");
}
if (isServerSetup && $page.url.pathname == "/setup") {
goto("/");
}
});
</script> </script>
<!-- passes the user object to the navbar component --> <!-- passes the user object to the navbar component -->

View file

@ -4,6 +4,7 @@ import { db } from "$lib/db/db.server";
import { userTable } from "$lib/db/schema"; import { userTable } from "$lib/db/schema";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { Argon2id } from "oslo/password"; import { Argon2id } from "oslo/password";
import type { DatabaseUser } from "$lib/server/auth";
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
if (event.locals.user) if (event.locals.user)
@ -42,6 +43,22 @@ export const actions: Actions = {
}; };
} }
const usernameTaken = await db
.select()
.from(userTable)
.where(eq(userTable.username, username))
.limit(1)
.then((results) => results[0] as unknown as DatabaseUser | undefined);
if (usernameTaken) {
return {
status: 400,
body: {
message: "Username taken!"
}
};
}
if (password) { if (password) {
let hashedPassword = await new Argon2id().hash(password); let hashedPassword = await new Argon2id().hash(password);
console.log(hashedPassword) console.log(hashedPassword)

View file

@ -0,0 +1,121 @@
// routes/signup/+page.server.ts
import { lucia } from "$lib/server/auth";
import { fail, redirect } from "@sveltejs/kit";
import { generateId } from "lucia";
import { Argon2id } from "oslo/password";
import { db } from "$lib/db/db.server";
import type { DatabaseUser } from "$lib/server/auth";
import type { Actions } from "./$types";
import { userTable } from "$lib/db/schema";
import { eq } from "drizzle-orm";
export const actions: Actions = {
default: async (event) => {
const formData = await event.request.formData();
const username = formData.get("username");
const password = formData.get("password");
const firstName = formData.get("first_name");
const lastName = formData.get("last_name");
// username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _
// keep in mind some database (e.g. mysql) are case insensitive
// check all to make sure all fields are provided
if (!username || !password || !firstName || !lastName) {
return fail(400, {
message: "All fields are required",
});
}
if (
typeof username !== "string" ||
username.length < 3 ||
username.length > 31 ||
!/^[a-z0-9_-]+$/.test(username)
) {
return fail(400, {
message: "Invalid username",
});
}
if (
typeof password !== "string" ||
password.length < 6 ||
password.length > 255
) {
return fail(400, {
message: "Invalid password",
});
}
if (
typeof firstName !== "string" ||
firstName.length < 1 ||
firstName.length > 255
) {
return fail(400, {
message: "Invalid first name",
});
}
if (
typeof lastName !== "string" ||
lastName.length < 1 ||
lastName.length > 255
) {
return fail(400, {
message: "Invalid last name",
});
}
const userId = generateId(15);
const hashedPassword = await new Argon2id().hash(password);
const usernameTaken = await db
.select()
.from(userTable)
.where(eq(userTable.username, username))
.limit(1)
.then((results) => results[0] as unknown as DatabaseUser | undefined);
if (usernameTaken) {
return fail(400, {
message: "Username already taken",
});
}
let adminUser = await db
.select()
.from(userTable)
.where(eq(userTable.role, 'admin'))
.execute();
if (adminUser != null && adminUser.length > 0) {
return fail(400, {
message: "Admin user already exists",
});
}
await db
.insert(userTable)
.values({
id: userId,
username: username,
first_name: firstName,
last_name: lastName,
hashed_password: hashedPassword,
signup_date: new Date(),
role: "admin",
last_login: new Date(),
} as DatabaseUser)
.execute();
const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
event.cookies.set(sessionCookie.name, sessionCookie.value, {
path: ".",
...sessionCookie.attributes,
});
redirect(302, "/");
},
};

View file

@ -0,0 +1,44 @@
<script lang="ts">
import { enhance } from "$app/forms";
</script>
<h1 class="text-center font-bold text-4xl">AdventureLog Setup</h1>
<!-- centered text descripton welcomeing the user -->
<p class="text-center mt-4">
Welcome to AdventureLog! Please follow the steps below to setup your server.
</p>
<!-- step 1 create admin user -->
<h2 class="text-center font-bold text-2xl mt-6">Create Admin User</h2>
<div class="flex justify-center">
<form method="post" use:enhance class="w-full max-w-xs">
<label for="username">Username</label>
<input
name="username"
id="username"
class="block mb-2 input input-bordered w-full max-w-xs"
/><br />
<label for="first_name">First Name</label>
<input
name="first_name"
id="first_name"
class="block mb-2 input input-bordered w-full max-w-xs"
/><br />
<label for="last_name">Last Name</label>
<input
name="last_name"
id="last_name"
class="block mb-2 input input-bordered w-full max-w-xs"
/><br />
<label for="password">Password</label>
<input
type="password"
name="password"
id="password"
class="block mb-2 input input-bordered w-full max-w-xs"
/><br />
<button class="py-2 px-4 btn btn-primary">Signup</button>
</form>
</div>

View file

@ -24,6 +24,7 @@ run_sql_scripts() {
echo "Finished running SQL scripts." echo "Finished running SQL scripts."
} }
# Start your application here # Start your application here
# Print message # Print message
echo "Starting AdventureLog" echo "Starting AdventureLog"
@ -33,6 +34,9 @@ if [ -z "$SKIP_DB_WAIT" ] || [ "$SKIP_DB_WAIT" = "false" ]; then
wait_for_db wait_for_db
fi fi
# Wait for the database to start up
setup_admin_user
# generate the schema # generate the schema
# npm run generate # npm run generate