mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 13:15:18 +02:00
Update server setup logic and add admin user creation in setup page
This commit is contained in:
parent
7fe90f615f
commit
f626370d3f
9 changed files with 235 additions and 2 deletions
|
@ -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
1
src/app.d.ts
vendored
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -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 -->
|
||||||
|
|
|
@ -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)
|
||||||
|
|
121
src/routes/setup/+page.server.ts
Normal file
121
src/routes/setup/+page.server.ts
Normal 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, "/");
|
||||||
|
},
|
||||||
|
};
|
44
src/routes/setup/+page.svelte
Normal file
44
src/routes/setup/+page.svelte
Normal 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>
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue