Redirect users to landing on (app) routes, game page

This commit is contained in:
I-Have-An-Issue 2022-10-11 17:29:12 -04:00
parent d4180e5e4f
commit 05d0364f36
No known key found for this signature in database
GPG Key ID: E55435DEA0825091
13 changed files with 244 additions and 76 deletions

View File

@ -8,11 +8,16 @@ export async function handle({ event, resolve }) {
}
const cookie = event.cookies.get(COOKIE_NAME);
if (!cookie) return await resolve(event);
if (!cookie) {
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
return await resolve(event);
}
let user = await getUserFromSession(cookie, event.getClientAddress());
if (!user) event.cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
else
let user = await getUserFromSession(cookie, event.request.headers.get("x-forwarded-for") || event.getClientAddress());
if (!user) {
event.cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
if (event.routeId.startsWith("(app)")) return new Response("", { status: 302, headers: { Location: "/landing" } });
} else
event.locals.user = {
_id: user._id,
username: user.username,

View File

@ -0,0 +1,61 @@
<!-- https://github.com/BillBuilt/sveltekit-hcaptcha -->
<script>
import { onMount, onDestroy } from "svelte";
import { browser } from "$app/environment";
import { HCAPTCHA_SITEKEY } from "$lib/constants";
let hcaptcha;
let hcaptchaWidgetID;
export let token = null;
// forces 1-way binding
let captchaToken;
$: token = captchaToken;
export let isValid = false;
// forces 1-way binding
let captchaValid;
$: isValid = captchaValid;
onMount(() => {
setTimeout(function () {
if (browser) {
hcaptcha = window.hcaptcha;
if (hcaptcha.render) {
hcaptchaWidgetID = hcaptcha.render("hcaptcha", {
sitekey: HCAPTCHA_SITEKEY,
size: "normal",
callback: onValidCaptcha,
"error-callback": onErrorCaptcha,
theme: "light"
});
}
}
}, 500);
});
onDestroy(() => {
if (browser) {
hcaptcha = null;
}
});
function onValidCaptcha(e) {
//console.log('verified event', e)
captchaToken = e;
captchaValid = true;
}
function onErrorCaptcha(e) {
//console.log('error event', {error: e.error})
captchaToken = null;
captchaValid = false;
hcaptcha.reset(hcaptchaWidgetID);
}
</script>
<svelte:head>
<script src="https://js.hcaptcha.com/1/api.js?render=explicit" async defer></script>
</svelte:head>
<div id="hcaptcha" class="h-captcha" />

View File

@ -2,6 +2,7 @@ export const COOKIE_NAME = ".ROWBLOX_SESSION_DO_NOT_SHARE";
export const USERNAME_REGEX = /[^A-Za-z0-9\-_ ]/g;
export const MIN_USERNAME_LENGTH = 3;
export const MAX_USERNAME_LENGTH = 16;
export const MIN_PASSWORD_LENGTH = 0;
export const MIN_PASSWORD_LENGTH = 3;
export const INVITE_KEY_PREFIX = "rowblox-";
export const SESSION_EXPIRE = 604800000;
export const HCAPTCHA_SITEKEY = "be5c40c1-13db-423c-878e-f3428e9fc841";

View File

@ -16,6 +16,7 @@ const assets = await db.collection("assets");
const invites = await db.collection("invites");
const avatars = await db.collection("avatars");
const sessions = await db.collection("sessions");
const friends = await db.collection("friends");
async function inc(collection) {
await collection.updateOne({ _id: "_inc" }, { $inc: { _inc: 1 } });
@ -50,7 +51,13 @@ export async function createUser(username, password, lastip) {
username,
password: await hashPassword(password),
lastip,
currency: 100
currency: 100,
joinDate: Date.now(),
lastOnline: Date.now(),
rank: 1,
activity: 1,
pendingFriendRequests: [],
bio: ""
};
const avatar = {
@ -63,7 +70,8 @@ export async function createUser(username, password, lastip) {
LeftLegColor: 1,
RightLegColor: 1
},
Assets: {}
Wearing: [],
Inventory: []
};
await Promise.all([avatars.insertOne(avatar), await users.insertOne(user)]);
@ -104,12 +112,12 @@ export async function deleteSession(session, lastip) {
const sessionDocument = await sessions.findOne({ _id: session });
if (!sessionDocument) return false;
const user = await users.findOne({ _id: sessionDocument._id });
const user = await users.findOne({ _id: sessionDocument.owner });
if (!user) return false;
await users.updateOne({ _id: user._id }, { $set: { lastip } });
return await sessions.deleteOne({ session });
return await sessions.deleteOne({ _id: session });
}
export async function getUser(query, projection) {

View File

@ -1,9 +1,15 @@
<script>
import Alert from "$lib/components/Alert.svelte";
import Banner from "$lib/components/AdBanner.svelte";
import { goto } from "$app/navigation";
/** @type {import('./$types').LayoutData} */
export let data;
async function logout() {
const response = await fetch("/logout", { method: "GET" });
await goto("/landing");
}
</script>
<div class="navbar scrolling-background text-lg py-1 text-white">
@ -31,7 +37,7 @@
<a class="block px-4 py-0.5 text-gray-700" href="/my/profile">Profile</a>
<a class="block px-4 py-0.5 text-gray-700" href="/my/settings">Settings</a>
</div>
<a class="block px-4 py-0.5 text-red-700" href="/logout">Logout</a>
<span class="block px-4 py-0.5 text-red-700 hover:cursor-pointer" on:click={logout}>Logout</span>
</div>
</div>
</div>

View File

@ -1,38 +1,105 @@
<script>
import UserListed from "$lib/components/UserListed.svelte";
export let game;
let state = 0;
function updateState(newState) {
state = newState;
}
</script>
<svelte:head>
<title>Games - Rowblox</title>
<title>{game?.name} - Rowblox</title>
</svelte:head>
<div class="mx-auto rounded-lg shadow border border-gray-300 p-4 divide-y">
<div class="flex">
<img class="float-left h-[22.5rem]" alt="" src="/img/background.png" />
<div class="w-full flex flex-col pl-4">
<div class="max-w-[22.9rem]">
<p class="text-3xl font-bold truncate">{game?.name}</p>
<p class="text-zinc-500">By <a class="font-bold text-blue-500" href="/users/{game?.creator?._id}">{game?.creator?.username}</a></p>
</div>
<span class="flex-grow" />
<div class="flex">
<a class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 mr-0.5 mb-1 text-center" href="/games/{game?.id}/edit">
Edit
</a>
<button class="flex-auto text-lg text-blue-500 px-4 py-0.5 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500 ml-0.5 mb-1">Host</button>
</div>
<button class="text-2xl text-blue-500 px-4 py-2 rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Play</button>
</div>
</div>
</div>
<div class="accent py-0.5 flex-none shadow-md"></div>
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<div class="flex flex-row text-xl text-center text-black">
<button
class="flex-auto {state == 0 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(0);
}}>About</button
>
<button
class="flex-auto {state == 1 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(1);
}}>Store</button
>
<button
class="flex-auto {state == 2 && 'font-bold text-black text-opacity-30'}"
on:click={() => {
updateState(2);
}}>Players</button
>
</div>
</div>
<div class="my-4 flex-grow">
<div class="container"><div class="carbon-gradient rounded shadow-lg p-4"><div class="flex mb-4"><img class="w-[38rem] float-left" alt="">
<div class="pl-4 w-full flex flex-col place-content-between"><div class="max-w-[22.9rem]"><p class="text-3xl font-bold text-ellipsis overflow-clip">Test Game</p>
<p class="text-zinc-500">By <a class="font-bold text-blue-500 hover:text-zinc-300" href="/users/1">Rowblox</a></p></div>
<div class="flex flex-col"><div class="w-full flex flex-col mb-4 mx-auto"><div class="flex flex-row place-content-between mb-0.5 px-1"><div class="flex flex-row text-green-200"><i class="fa fa-thumbs-up text-4xl hover:text-green-300" aria-hidden="true"></i>
<p class="my-auto text-lg px-2 select-none font-bold">0</p></div>
<div class="flex flex-row text-red-200"><p class="my-auto text-lg px-2 select-none font-bold">0</p>
<i class="fa fa-thumbs-down text-4xl hover:text-red-300" aria-hidden="true"></i></div></div>
<div class="relative"><div class="py-0.5 bg-red-400 rounded-lg absolute left-0 w-full "></div>
<div class="py-0.5 bg-green-400 rounded-lg absolute left-0" style="width: 50%;"></div></div></div>
<a class=" text-blue-500 accent shadow-lg rounded hover:text-zinc-300 mb-2 p-1 text-center flex-grow ml-1 border-[3px] border-blue-500" href="/games/1/edit">Edit Game</a></div>
<button class="text-xl bg-white hover:text-zinc-300 py-3 w-full rounded shadow-lg accent text-blue-500 border-blue-500 border-[3px]">Play</button></div></div></div>
<div class="divider-0"></div>
<p class="py-2">Test Game</p>
<div class="divider-0"></div>
<div class="flex flex-row"><div class="m-4 text-center flex-grow"><p class="text-zinc-500 inline-block">Players</p>
<p class="">0</p></div>
<div class="m-4 text-center flex-grow"><p class="text-zinc-500 inline-block">Visits</p>
<p class="">0</p></div>
<div class="m-4 text-center flex-grow"><p class="text-zinc-500 inline-block">Created</p>
<p class="">10/10/22</p></div>
<div class="m-4 text-center flex-grow"><p class="text-zinc-500 inline-block">Updated</p>
<p class="">10/10/22</p></div>
<div class="m-4 text-center flex-grow"><p class="text-zinc-500 inline-block">Max Players</p>
<p class="">All</p></div></div>
<div class="divider-0"></div></div>
<div class="carbon-gradient rounded shadow-lg p-2 mt-4"><div class="flex flex-row text-xl text-center"><a href="#players" class="flex-auto text-zinc-600">Players</a>
<a href="#gamepasses" class="flex-auto hover:cursor-pointer hover:text-zinc-300">Gamepasses</a></div></div>
<div class="carbon-gradient rounded shadow-lg p-[0.7rem] mt-2"><p class="p-1.5 text-base">No players connected</p></div></div>
{#if state == 0}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4 divide-y">
<div class="pb-2">
<p class="px-2 py-1 font-bold text-xl">Description</p>
<p class="px-2 text-black text-opacity-80">Test Game</p>
</div>
<div class="flex flex-row pb-2">
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Players</p>
<p class="">0</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Visits</p>
<p class="">0</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Created</p>
<p class="">10/10/22</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Updated</p>
<p class="">10/10/22</p>
</div>
<div class="mx-4 mt-4 text-center flex-grow">
<p class="text-zinc-500 inline-block">Max Players</p>
<p class="">No Limit</p>
</div>
</div>
</div>
{:else if state == 1}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl">Gamepasses</p>
<p class="px-2 text-black text-opacity-80">This store does not have any gamepasses for sale.</p>
</div>
{:else if state == 2}
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl">Players</p>
<p class="px-2 text-black text-opacity-80">This game does not have any players.</p>
</div>
<div class="rounded-lg shadow border border-gray-300 p-2 mt-4">
<p class="px-2 py-1 font-bold text-xl mb-1">Players</p>
<UserListed />
<UserListed />
<UserListed />
<UserListed />
</div>
{/if}

View File

@ -14,18 +14,3 @@
<button class="px-3 py-1 rounded border-2 border-blue-500 text-blue-500 shadow-lg hover:bg-blue-200">Add Friend</button>
</div>
</div>
<div class="shadow-lg w-96 h-96 p-2 rounded-lg border-2 border-gray-300 mt-2 mx-1 inline-block">
<div class="font-bold text-bold mt-72 mx-1">
<p class="font-bold text-[18px] mx-20">This is my description!</p>
</div>
</div>
<div class="flex place-content-between m">
<div class="w-full rounded-lg flex shadow border border-gray-300 p-2 mt-2 mr-1">
<p class="font-bold text-2xl">Friends</p>
</div>
<div class="w-full rounded-lg flex shadow border border-gray-300 p-2 mt-2 ml-1">
<p class="font-bold text-2xl">Games</p>
</div>
</div>

View File

@ -18,7 +18,7 @@ export const actions = {
const correctPassword = await compareHash(password, user.password);
if (!correctPassword) return invalid(400, { error: "password" });
cookies.set(COOKIE_NAME, await createSession(user._id, getClientAddress()), { secure: !!process.env.PRODUCTION });
cookies.set(COOKIE_NAME, await createSession(user._id, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
throw redirect(302, "/");
}
};

View File

@ -10,7 +10,7 @@
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
<div class="flex flex-col">
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
type="username"
name="username"
id="username"
@ -19,7 +19,7 @@
value={form?.username ?? ""}
/>
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
type="password"
name="password"
id="password"
@ -28,8 +28,8 @@
/>
</div>
{#if form?.message}
<p class="rounded w-72 px-4 border-[3px] border-red-500 text-red-500 mx-auto my-1.5 py-1.5 hover:bg-opacity-50 hover:bg-red-500">{form?.message}</p>
<p class="rounded w-[19rem] px-4 border-[3px] border-red-500 text-red-500 mx-auto my-1.5 py-1.5 hover:bg-opacity-50 hover:bg-red-500">{form?.message}</p>
{/if}
<div class="h-captcha" data-sitekey="30000000-ffff-ffff-ffff-000000000003" />
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-full rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Login</button>
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Login</button>
</form>

View File

@ -2,7 +2,7 @@ import { deleteSession } from "$lib/database";
import { COOKIE_NAME } from "$lib/constants";
/** @type {import('./$types').RequestHandler} */
export async function GET({ cookies, getClientAddress }) {
export async function GET({ request, cookies, getClientAddress }) {
const session = cookies.get(COOKIE_NAME);
if (!session)
return new Response("", {
@ -11,7 +11,7 @@ export async function GET({ cookies, getClientAddress }) {
});
cookies.delete(COOKIE_NAME, { secure: !!process.env.PRODUCTION });
await deleteSession(session, getClientAddress());
await deleteSession(session, request.headers.get("x-forwarded-for") || getClientAddress());
return new Response("", {
status: 302,

View File

@ -1,6 +1,6 @@
import { invalid, redirect } from "@sveltejs/kit";
import { createUser, createSession } from "$lib/database";
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME } from "$lib/constants";
import { createUser, createSession, getUser } from "$lib/database";
import { MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH, USERNAME_REGEX, MIN_PASSWORD_LENGTH, INVITE_KEY_PREFIX, COOKIE_NAME, HCAPTCHA_SITEKEY } from "$lib/constants";
/** @type {import('./$types').Actions} */
export const actions = {
@ -13,6 +13,7 @@ export const actions = {
const password = data.get("password");
const confirm_password = data.get("confirm_password");
const invite_key = data.get("invite_key");
const hcaptcha_response = data.get("h-captcha-response");
if (username.length < MIN_USERNAME_LENGTH || username.length > MAX_USERNAME_LENGTH || new RegExp(USERNAME_REGEX).test(username))
return invalid(400, {
@ -35,8 +36,37 @@ export const actions = {
error: "invite_key"
});
const user = await createUser(username, password, getClientAddress());
cookies.set(COOKIE_NAME, await createSession(user, getClientAddress()), { secure: !!process.env.PRODUCTION });
if (!hcaptcha_response)
return invalid(400, {
username,
invite_key,
error: "hcaptcha"
});
const existingUser = await getUser({ username }, { _id: true });
if (existingUser)
return invalid(400, {
username,
invite_key,
error: "username"
});
const hcaptcha = await fetch("https://hcaptcha.com/siteverify", {
method: "POST",
body: `response=${hcaptcha_response}&secret=${process.env.HCAPTCHA_SECRET}`,
headers: { "content-type": "application/x-www-form-urlencoded" }
});
const hcaptchaBody = await hcaptcha.json();
if (!hcaptchaBody.success)
return invalid(400, {
username,
invite_key,
error: "hcaptcha"
});
const user = await createUser(username, password, request.headers.get("x-forwarded-for") || getClientAddress());
cookies.set(COOKIE_NAME, await createSession(user, request.headers.get("x-forwarded-for") || getClientAddress()), { secure: !!process.env.PRODUCTION });
throw redirect(302, "/");
}
};

View File

@ -1,17 +1,20 @@
<script>
import HCaptcha from "../../../lib/components/hCaptcha.svelte";
/** @type {import('./$types').ActionData} */ export let form;
let captchaValid = false;
let captchaToken;
</script>
<svelte:head>
<title>Register - Rowblox</title>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
</svelte:head>
<form method="POST" class="max-w-lg mx-auto text-center text-white">
<img class="mx-auto mb-2" src="/favicon.png" alt="" />
<div class="flex flex-col">
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'username' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="username"
name="username"
@ -20,7 +23,7 @@
value={form?.username ?? ""}
/>
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="password"
name="password"
@ -28,7 +31,7 @@
placeholder="Password"
/>
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'password' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
required
type="password"
name="confirm_password"
@ -36,7 +39,7 @@
placeholder="Confirm Password"
/>
<input
class="rounded-lg w-72 px-4 border-2 {form?.error == 'invite_key' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto my-0.5 py-2"
class="rounded-lg w-[19rem] px-4 border-2 {form?.error == 'invite_key' ? 'border-red-500' : 'border-blue-500'} text-black mx-auto mt-0.5 mb-1.5 py-2"
required
type="text"
name="invite_key"
@ -46,8 +49,10 @@
/>
</div>
{#if form?.message}
<p class="rounded w-72 px-4 border-2 border-red-500 text-red-500 bg-red-500 mx-auto my-1.5 py-1.5 bg-opacity-50">{form?.message}</p>
<p class="rounded w-[19rem] px-4 border-2 border-red-500 text-red-500 bg-red-500 mx-auto my-1.5 py-1.5 bg-opacity-50">{form?.message}</p>
{/if}
<div class="h-captcha" data-sitekey="30000000-ffff-ffff-ffff-000000000003" />
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-full rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Register</button>
<div class={form?.error == "hcaptcha" && "border-2 border-red-500"}>
<HCaptcha bind:token={captchaToken} bind:isValid={captchaValid} />
</div>
<button class="my-1.5 text-xl text-white px-4 py-1.5 w-[19rem] rounded shadow-lg border-2 border-blue-500 hover:bg-opacity-50 hover:bg-blue-500">Register</button>
</form>