Replace Appwrite with Supabase

This commit is contained in:
Korbs 2024-07-16 23:39:02 -04:00
parent 0ea0d8b73c
commit c2c1acf5c6
16 changed files with 71 additions and 390 deletions

2
.env.example Normal file
View file

@ -0,0 +1,2 @@
SUPABASE_URL=supabase_url
SUPABASE_ANON_KEY=anon_key

View file

@ -1,3 +0,0 @@
PUBLIC_APPWRITE_ENDPOINT="https://example.com/v1"
PUBLIC_APPWRITE_PROJECT_ID=""
APPWRITE_KEY=""

View file

@ -1,13 +0,0 @@
<svg width="160" height="29" viewBox="0 0 160 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M46.7348 23.5568C49.3517 23.5568 50.6746 22.2116 51.2497 21.2957H51.5085C51.6236 22.2688 52.3137 23.1847 53.6653 23.1847H56.2247V20.3226H55.5633C55.1032 20.3226 54.8731 20.0651 54.8731 19.6644V8.18757H51.4798V10.0193H51.221C50.5595 9.10343 49.1792 7.8155 46.6486 7.8155C42.6226 7.8155 39.6318 11.1355 39.6318 15.6861C39.6318 20.2368 42.6801 23.5568 46.7348 23.5568ZM47.3387 20.294C44.9519 20.294 43.0827 18.5482 43.0827 15.7148C43.0827 12.9386 44.8944 11.0496 47.31 11.0496C49.6106 11.0496 51.5373 12.7382 51.5373 15.7148C51.5373 18.262 49.8981 20.294 47.3387 20.294Z" fill="#EDEDF0"/>
<path d="M58.2281 29H61.6215V21.2957H61.8803C62.513 22.2116 63.8645 23.5568 66.539 23.5568C70.565 23.5568 73.4982 20.1796 73.4982 15.6861C73.4982 11.1641 70.3637 7.8155 66.3089 7.8155C63.7208 7.8155 62.4554 9.21791 61.8515 9.99066H61.5927V8.18757H58.2281V29ZM65.82 20.3799C63.4907 20.3799 61.564 18.6627 61.564 15.6861C61.564 13.1389 63.2031 10.9924 65.7625 10.9924C68.1494 10.9924 70.0186 12.8527 70.0186 15.6861C70.0186 18.4623 68.2069 20.3799 65.82 20.3799Z" fill="#EDEDF0"/>
<path d="M75.2492 29H78.6425V21.2957H78.9013C79.534 22.2116 80.8856 23.5568 83.56 23.5568C87.586 23.5568 90.2396 20.1796 90.2396 15.6861C90.2396 11.1641 87.3847 7.8155 83.3299 7.8155C80.7418 7.8155 79.4765 9.21791 78.8726 9.99066H78.6137V8.18757H75.2492V29ZM82.841 20.3799C80.5117 20.3799 78.585 18.6627 78.585 15.6861C78.585 13.1389 80.2242 10.9924 82.7835 10.9924C85.1704 10.9924 87.0396 12.8527 87.0396 15.6861C87.0396 18.4623 85.2279 20.3799 82.841 20.3799Z" fill="#EDEDF0"/>
<path d="M94.7253 23.5329H99.5277L102.26 11.7699H102.432L105.164 23.5329H109.938L113.76 8.53582H110.34L107.608 20.3275H107.35L104.618 8.53582H100.103L97.3422 20.3275H97.0834L94.3802 8.53582H90.7568L94.7253 23.5329Z" fill="#EDEDF0"/>
<path d="M115.48 23.5329H118.873V16.1202C118.873 13.2868 120.196 11.541 122.669 11.541H124.164V8.16376H123.043C121.116 8.16376 119.649 9.4803 119.074 10.7396H118.844V8.53582H115.48V23.5329Z" fill="#EDEDF0"/>
<path d="M141.004 23.5329H143.649V20.5278H141.032C139.997 20.5278 139.566 20.0699 139.566 19.0109V11.5124H143.822V8.53582H139.566V4.32861H136.345V8.53582H133.527V11.5124H136.144V19.0395C136.144 22.2164 138.07 23.5329 141.004 23.5329Z" fill="#EDEDF0"/>
<path d="M152.753 23.5568C155.888 23.5568 158.648 22.0113 159.626 18.8916L156.52 18.1475C155.974 19.8075 154.392 20.6661 152.724 20.6661C150.251 20.6661 148.612 19.0634 148.583 16.5447H160V15.6003C160 11.1355 157.21 7.8155 152.609 7.8155C148.555 7.8155 145.075 10.9924 145.075 15.7148C145.075 20.294 148.152 23.5568 152.753 23.5568ZM148.612 14.0834C148.813 12.2803 150.452 10.7634 152.609 10.7634C154.68 10.7634 156.376 12.0513 156.549 14.0834H148.612Z" fill="#EDEDF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M132.018 23.5329H128.625V11.5124H125.979V8.53582H132.018V23.5329Z" fill="#EDEDF0"/>
<path d="M130.069 6.45465C131.306 6.45465 132.226 5.53879 132.226 4.33673C132.226 3.16329 131.306 2.24744 130.069 2.24744C128.833 2.24744 127.912 3.16329 127.912 4.33673C127.912 5.53879 128.833 6.45465 130.069 6.45465Z" fill="#EDEDF0"/>
<path d="M29.6277 19.8556V26.4741H13.0326C8.19779 26.4741 3.97629 23.8122 1.71769 19.8556C1.38936 19.2803 1.10198 18.6769 0.860926 18.0505C0.387713 16.8231 0.0902487 15.506 0 14.1317V12.3423C0.0195935 12.0361 0.050468 11.7322 0.0908425 11.432C0.173373 10.8159 0.298058 10.213 0.461931 9.62693C2.01219 4.07098 7.05306 0 13.0326 0C19.0122 0 24.0525 4.07098 25.6027 9.62693H18.5069C17.342 7.81586 15.3257 6.61852 13.0326 6.61852C10.7396 6.61852 8.72325 7.81586 7.55833 9.62693C7.20328 10.1775 6.92778 10.7846 6.74728 11.432C6.58697 12.006 6.50147 12.6113 6.50147 13.237C6.50147 15.1341 7.28877 16.8441 8.55107 18.0505C9.72074 19.1702 11.2977 19.8556 13.0326 19.8556H29.6277Z" fill="#FD366E"/>
<path d="M29.6277 11.432V18.0505H17.5142C18.7765 16.8442 19.5638 15.1342 19.5638 13.2371C19.5638 12.6113 19.4783 12.006 19.3179 11.432H29.6277Z" fill="#FD366E"/>
</svg>

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

View file

@ -1 +0,0 @@
<slot />

View file

@ -1,155 +0,0 @@
---
// Properties
const {Title, Type} = Astro.props
// Check if user is already logged in, redirect if so
const { user } = Astro.locals;
if (user) {
return Astro.redirect("/account");
}
---
<img id="bg" src="/login-dark-mode.png"/>
<div class="login-dialog">
<div>
<img src="/login-dark-mode.png"/>
</div>
<div class="ld-main">
<h2>{Title}</h2>
<form method="POST">
{
()=> {
if (Type === "SignUp") {
return <input id="name" name="name" placeholder="Your name" autocomplete="off" type="text"/>
} else {
return null
}
}
}
<input id="email" name="email" type="email" placeholder="Email" autocomplete="off"/>
<input id="password" name="password" type="password" placeholder="Password" minlength="8" maxlength="512"/>
<div style="display: flex; align-items: center; justify-content: space-between;">
{
()=> {
if (Type === "SignIn") {
return <p>Don't have an account? <a href="/signup/">Sign Up</a></p>
<button style="height: max-content;" type="submit">Login</button>
} else if (Type === "SignUp") {
return <p>Already have an account? <a href="/signin/">Sign In</a></p>
<button style="height: max-content;" type="submit">Sign Up</button>
}
}
}
</div>
<!-- <center><p>or</p></center> -->
</form>
<!-- <form method="POST" action="/api/discord">
<center><button style="background: #5865F2"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><path fill="#fff" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></svg> Login with Discord</button></center>
</form> -->
</div>
</div>
<style lang="scss">
body {
color: white;
background: black;
font-family: arial;
overflow: hidden;
*:focus {
outline: none;
}
a {
color: #fb4e72;
}
}
body::before {
content: "";
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
backdrop-filter: blur(24px) contrast(0.8) brightness(0.4);
}
#bg {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.login-dialog {
position: fixed;
top: 6%;
left: 50%;
transform: translate(-50%);
max-width: 800px;
width: 100%;
display: grid;
grid-template-columns: 50% 50%;
background: #070707;
border: 2px #262626 solid;
transition: 0.3s border-color;
border-radius: 12px;
font-size: 14px;
&:hover {
border-color: #fb4e72;
transition: 1s border-color;
}
img {
border-radius: 10px 0px 0px 10px;
width: 100%;
margin-bottom: -3px;
}
.ld-main {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0px 48px;
form {
display: flex;
flex-direction: column;
gap: 6px;
input {
background: transparent;
color: white;
border: none;
border-bottom: 2px #371f25 solid;
padding: 6px 12px;
&:hover {
border-color: #551c28;
}
&:focus {
border-color: #fb4e72;
}
}
button {
background: #fb4e72;
color: white;
border: none;
padding: 6px 12px;
border-radius: 3rem;
display: flex;
max-width: max-content;
cursor: pointer;
svg {
width: 15px;
margin-right: 8px;
}
&:hover {
filter: brightness(0.7)
}
&:focus {
filter: brightness(1.1)
}
}
}
}
}
</style>

14
src/library/supabase.ts Normal file
View file

@ -0,0 +1,14 @@
import { createClient } from "@supabase/supabase-js";
export const supabase = createClient(
import.meta.env.SUPABASE_URL,
import.meta.env.SUPABASE_ANON_KEY,
{
auth: {
flowType: "pkce",
autoRefreshToken: false,
detectSessionInUrl: false,
persistSession: true,
},
},
);

View file

@ -1,11 +0,0 @@
import { defineMiddleware } from "astro:middleware";
import { createSessionClient } from "./server/appwrite";
export const onRequest = defineMiddleware(async ({ request, locals }, next) => {
try {
const { account } = createSessionClient(request);
locals.user = await account.get();
} catch {}
return next();
});

55
src/middleware/index.ts Normal file
View file

@ -0,0 +1,55 @@
import { defineMiddleware } from "astro:middleware"
import { supabase } from "../library/supabase"
const protectedRoutes = ["/dashboard"]
const redirectRoutes = ["/signin", "/register"]
export const onRequest = defineMiddleware(
async ({ locals, url, cookies, redirect }, next) => {
if (protectedRoutes.includes(url.pathname)) {
const accessToken = cookies.get("sb-access-token")
const refreshToken = cookies.get("sb-refresh-token")
if (!accessToken || !refreshToken) {
return redirect("/signin")
}
const { data, error } = await supabase.auth.setSession({
refresh_token: refreshToken.value,
access_token: accessToken.value,
})
if (error) {
cookies.delete("sb-access-token", {
path: "/",
})
cookies.delete("sb-refresh-token", {
path: "/",
})
return redirect("/signin")
}
locals.email = data.user?.email!
cookies.set("sb-access-token", data?.session?.access_token!, {
sameSite: "strict",
path: "/",
secure: true,
})
cookies.set("sb-refresh-token", data?.session?.refresh_token!, {
sameSite: "strict",
path: "/",
secure: true,
})
}
if (redirectRoutes.includes(url.pathname)) {
const accessToken = cookies.get("sb-access-token")
const refreshToken = cookies.get("sb-refresh-token")
if (accessToken && refreshToken) {
return redirect("/dashboard")
}
}
return next()
},
)

View file

@ -1,24 +0,0 @@
import type { APIRoute } from "astro";
import { createAdminClient, SESSION_COOKIE } from "../../server/appwrite";
export const GET: APIRoute = async ({ cookies, redirect, url }) => {
const { account } = createAdminClient();
cookies.set('anonymous', 'true')
const secret = url.searchParams.get("secret");
const session = await account.createAnonymousSession(secret);
if (!session.secret) {
throw new Error("Failed to create session from token");
}
cookies.set(SESSION_COOKIE, session.secret, {
sameSite: "strict",
expires: new Date(session.expire),
secure: true,
httpOnly: true,
path: "/",
});
return redirect("/account");
};

View file

@ -1,15 +0,0 @@
// When given all roles to ID that is used, the system says
// the role "applications" is still a missing scope.
import type { APIRoute } from "astro";
import { createAdminClient, SESSION_COOKIE } from "../../../server/appwrite";
export const GET: APIRoute = async ({ cookies, redirect, url }) => {
const { account } = createAdminClient();
const result = await account.createVerification(
'http://localhost:4321/account/'
);
console.log(result);
return redirect("/account");
};

View file

@ -1,40 +0,0 @@
import type { APIRoute } from "astro";
import { createAdminClient, SESSION_COOKIE } from "../server/appwrite";
export const POST: APIRoute = async ({ redirect, url }) => {
const { account } = createAdminClient();
const redirectUrl = await account.createOAuth2Token(
"discord",
`${url.origin}/oauth`,
`${url.origin}/signin`
);
return redirect(redirectUrl);
};
export const GET: APIRoute = async ({ cookies, redirect, url }) => {
const userId = url.searchParams.get("userId");
const secret = url.searchParams.get("secret");
if (!userId || !secret) {
throw new Error("OAuth2 did not provide userId or secret");
}
const { account } = createAdminClient();
const session = await account.createSession(userId, secret);
if (!session || !session.secret) {
throw new Error("Failed to create session from token");
}
cookies.set(SESSION_COOKIE, session.secret, {
sameSite: "strict",
expires: new Date(session.expire),
secure: true,
httpOnly: true,
path: "/",
});
return redirect("/account");
};

View file

@ -1,34 +0,0 @@
---
import { Users } from "node-appwrite";
import { createAdminClient } from "../../server/appwrite";
if (Astro.request.method === "POST") {
const data = await Astro.request.formData();
const name = data.get("name") as string;
const { account } = createAdminClient();
const promise = await account.updateName(name);
promise.then(function (response) {
console.log(response);
}, function (error) {
console.log(error);
});
return Astro.redirect("/account");
}
---
<form method="POST">
<label for="name">New Name</label>
<input
id="name"
name="name"
placeholder="Your name"
autocomplete="off"
type="text"
/>
<button type="submit">Update Name</button>
</form>

View file

@ -1,6 +0,0 @@
---
Astro.cookies.delete('session-token')
Astro.cookies.delete('anonymous')
---
<script is:inline>location.href = '/'</script>

View file

@ -1,43 +0,0 @@
---
import SignInUp from "../layouts/SignInUp.astro";
import { ID } from "node-appwrite";
import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
if (Astro.request.method === "POST") {
const data = await Astro.request.formData();
const email = data.get("email") as string;
const password = data.get("password") as string;
const name = data.get("name") as string;
const { account } = createAdminClient();
await account.create(ID.unique(), email, password, name);
const session = await account.createEmailPasswordSession(email, password);
const promise = account.createVerification("http://localhost:4321/verify");
promise.then(
function (response) {
console.log('VERI:' + response);
},
function (error) {
console.log(error);
},
);
Astro.cookies.set(SESSION_COOKIE, session.secret, {
path: "/",
expires: new Date(session.expire),
sameSite: "strict",
secure: true,
httpOnly: true,
});
return Astro.redirect("/account");
}
---
<SignInUp Title="Demo Registeration" Type="SignUp"/>

View file

@ -1,45 +0,0 @@
import { Client, Account } from "node-appwrite";
export const SESSION_COOKIE = "session-token";
export function createAdminClient() {
const client = new Client()
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID)
.setKey(import.meta.env.APPWRITE_KEY);
return {
get account() {
return new Account(client);
},
};
}
export function createSessionClient(request: Request) {
const client = new Client()
.setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)
.setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT_ID);
const cookies = parseCookies(request.headers.get("cookie") ?? "");
const session = cookies.get(SESSION_COOKIE);
if (!session) {
throw new Error("No session");
}
client.setSession(session);
return {
get account() {
return new Account(client);
},
};
}
function parseCookies(cookies: string): Map<string, string | null> {
const map = new Map<string, string | null>();
for (const cookie of cookies.split(";")) {
const [name, value] = cookie.split("=");
map.set(name.trim(), value ?? null);
}
return map;
}