Init
This commit is contained in:
commit
ea1438363b
22 changed files with 529 additions and 0 deletions
3
.env.sample
Normal file
3
.env.sample
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
PUBLIC_APPWRITE_ENDPOINT="https://example.com/v1"
|
||||||
|
PUBLIC_APPWRITE_PROJECT_ID=""
|
||||||
|
APPWRITE_KEY=""
|
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
12
astro.config.mjs
Normal file
12
astro.config.mjs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineConfig } from "astro/config";
|
||||||
|
import node from "@astrojs/node";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
output: "server",
|
||||||
|
adapter: node({
|
||||||
|
mode: "standalone",
|
||||||
|
}),
|
||||||
|
security: {
|
||||||
|
checkOrigin: true
|
||||||
|
}
|
||||||
|
});
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "server-side-rendering",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"start": "astro dev",
|
||||||
|
"build": "astro check && astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@appwrite.io/pink": "^0.3.0",
|
||||||
|
"@appwrite.io/pink-icons": "^0.3.0",
|
||||||
|
"@astrojs/check": "^0.4.1",
|
||||||
|
"@astrojs/node": "^8.1.0",
|
||||||
|
"appwrite": "^15.0.0",
|
||||||
|
"astro": "^4.11.5",
|
||||||
|
"node-appwrite": "^12.0.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
18
src/env.d.ts
vendored
Normal file
18
src/env.d.ts
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
|
||||||
|
declare namespace App {
|
||||||
|
interface Locals {
|
||||||
|
user?: import("node-appwrite").Models.User <import("node-appwrite").Models.Preferences<{}>>;
|
||||||
|
users?: import("node-appwrite").Service.UserList <import("node-appwrite").Service.getPrefs<{}>>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly PUBLIC_APPWRITE_ENDPOINT: string;
|
||||||
|
readonly PUBLIC_APPWRITE_PROJECT_ID: string;
|
||||||
|
readonly APPWRITE_KEY: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
13
src/images/appwrite-logo-dark.svg
Normal file
13
src/images/appwrite-logo-dark.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 4 KiB |
BIN
src/images/login-dark-mode.png
Normal file
BIN
src/images/login-dark-mode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 621 KiB |
1
src/layouts/Layout.astro
Normal file
1
src/layouts/Layout.astro
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<slot />
|
11
src/middleware.ts
Normal file
11
src/middleware.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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();
|
||||||
|
});
|
16
src/oauth.ts
Normal file
16
src/oauth.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export async function signInWithDiscord() {
|
||||||
|
const url = new URL(
|
||||||
|
`${import.meta.env.PUBLIC_APPWRITE_ENDPOINT}/account/sessions/oauth2/discord`
|
||||||
|
);
|
||||||
|
|
||||||
|
const origin = window.location.origin;
|
||||||
|
|
||||||
|
url.searchParams.set("project", import.meta.env.PUBLIC_APPWRITE_PROJECT_ID!);
|
||||||
|
url.searchParams.set("success", `${origin}/account`);
|
||||||
|
url.searchParams.set("failure", `${origin}/signin`);
|
||||||
|
|
||||||
|
/* Important: For SSR we set token=true to get a auth token in the success URL */
|
||||||
|
url.searchParams.set("token", `true`);
|
||||||
|
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
45
src/pages/account.astro
Normal file
45
src/pages/account.astro
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
---
|
||||||
|
const { user, users } = Astro.locals
|
||||||
|
if (!user) {return Astro.redirect("/signin")}
|
||||||
|
import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
|
||||||
|
|
||||||
|
if (user.emailVerification) {var EmailIsVerified = true}
|
||||||
|
else {var EmailIsVerified = false}
|
||||||
|
|
||||||
|
if (Astro.cookies.get('anonymous')) {var AN = true}
|
||||||
|
else {var AN = false}
|
||||||
|
|
||||||
|
const { account } = createAdminClient();
|
||||||
|
// const session = await account.createMagicURLToken(user.$id,user.email);
|
||||||
|
---
|
||||||
|
|
||||||
|
<p>Email: {AN ? <span>None</span> : <span>{user.email}</span>} <span><button><a style="color: white; text-decoration: none;" href="/settings/update-username">Change</a></button></span></p>
|
||||||
|
<p>ID: {user.$id}</p>
|
||||||
|
<!-- <p>Phone: {user.phone}</p> -->
|
||||||
|
<p>Name: {AN ? <span>Anonymous</span> : <span>{user.name}</span>} <span><button><a style="color: white; text-decoration: none;" href="/settings/update-username">Change</a></button></span></p>
|
||||||
|
<p>Created at: {user.$createdAt}</p>
|
||||||
|
<p>Last login: {user.accessedAt}</p>
|
||||||
|
<p>Last updated: {user.$updatedAt}</p>
|
||||||
|
<p>Labels:
|
||||||
|
{user.labels.map((data) =>
|
||||||
|
<ul>
|
||||||
|
<li>{data}</li>
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Theme: {user.prefs.darkTheme}
|
||||||
|
|
||||||
|
{EmailIsVerified ?
|
||||||
|
<p>Your account is verified</p>
|
||||||
|
:
|
||||||
|
<p style="color: rgb(255, 113, 113)">Your account is not verified<!-- [<a href="/api/email/send-verification">Send Verification Email</a>] --></p>
|
||||||
|
}
|
||||||
|
|
||||||
|
{AN ?
|
||||||
|
<p>This is an anonmyous account.</p>
|
||||||
|
:
|
||||||
|
<p style="color: rgb(255, 113, 113)">Not an anonmyous account.</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<a href="/signout">Sign Out</a>
|
24
src/pages/api/an.ts
Normal file
24
src/pages/api/an.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
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");
|
||||||
|
};
|
15
src/pages/api/email/send-verification.ts
Normal file
15
src/pages/api/email/send-verification.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// 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");
|
||||||
|
};
|
40
src/pages/api/oauth/discord.ts
Normal file
40
src/pages/api/oauth/discord.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
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}/api/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");
|
||||||
|
};
|
8
src/pages/index.astro
Normal file
8
src/pages/index.astro
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
const { user } = Astro.locals;
|
||||||
|
if (user) {
|
||||||
|
return Astro.redirect("/account");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Astro.redirect("/signin");
|
||||||
|
---
|
34
src/pages/settings/update-username.astro
Normal file
34
src/pages/settings/update-username.astro
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
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>
|
105
src/pages/signin.astro
Normal file
105
src/pages/signin.astro
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
|
||||||
|
|
||||||
|
const { user } = Astro.locals;
|
||||||
|
if (user) {
|
||||||
|
return Astro.redirect("/account");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 { account } = createAdminClient();
|
||||||
|
const session = await account.createEmailPasswordSession(email, password);
|
||||||
|
|
||||||
|
Astro.cookies.set(SESSION_COOKIE, session.secret, {
|
||||||
|
path: "/",
|
||||||
|
expires: new Date(session.expire),
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: true,
|
||||||
|
httpOnly: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Astro.redirect("/account");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Sign in | Server side rendering with Appwrite and Astro">
|
||||||
|
<div class="u-max-width-500 u-width-full-line">
|
||||||
|
<h1 class="heading-level-2 u-margin-block-start-auto">Demo sign in</h1>
|
||||||
|
<div class="u-margin-block-start-24">
|
||||||
|
<form class="form common-section" method="POST">
|
||||||
|
<ul class="form-list" style="--form-list-gap: 1.5rem;">
|
||||||
|
<li class="form-item">
|
||||||
|
<p>
|
||||||
|
This is a demo project for <a href="https://appwrite.io"
|
||||||
|
>Appwrite</a
|
||||||
|
> server side rendering. View the source code on the
|
||||||
|
<a
|
||||||
|
class="link"
|
||||||
|
href="https://github.com/appwrite/demos-for-svelte"
|
||||||
|
>GitHub repository</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li class="form-item">
|
||||||
|
<label class="label is-required" for="email">Email</label>
|
||||||
|
<div class="input-text-wrapper">
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Email"
|
||||||
|
type="email"
|
||||||
|
class="input-text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="form-item">
|
||||||
|
<label class="label is-required" for="password">Password</label>
|
||||||
|
<div class="input-text-wrapper" style="--amount-of-buttons: 1">
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Password"
|
||||||
|
minlength="8"
|
||||||
|
type="password"
|
||||||
|
class="input-text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="show-password-button"
|
||||||
|
aria-label="show password"
|
||||||
|
><span aria-hidden="true" class="icon-eye"></span></button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="form-item">
|
||||||
|
<button class="button is-full-width" type="submit"> Sign in</button>
|
||||||
|
</li>
|
||||||
|
<span class="with-separators eyebrow-heading-3">or</span>
|
||||||
|
<li class="form-item"></li>
|
||||||
|
</ul>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="/api/oauth/discord">
|
||||||
|
<button class="button is-discord is-full-width" type="submit">
|
||||||
|
<span class="icon-discord" aria-hidden="true"></span>
|
||||||
|
<span class="text">Sign in with discord</span></button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<ul class="inline-links is-center is-with-sep u-margin-block-start-32">
|
||||||
|
<li class="inline-links-item">
|
||||||
|
<span class="text"
|
||||||
|
>Don't have an account? <a class="link" href="/signup">Sign up</a
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
6
src/pages/signout.astro
Normal file
6
src/pages/signout.astro
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
Astro.cookies.delete('session-token')
|
||||||
|
Astro.cookies.delete('anonymous')
|
||||||
|
---
|
||||||
|
|
||||||
|
<script is:inline>location.href = '/'</script>
|
88
src/pages/signup.astro
Normal file
88
src/pages/signup.astro
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
---
|
||||||
|
import { ID } from "node-appwrite";
|
||||||
|
import { SESSION_COOKIE, createAdminClient } from "../server/appwrite";
|
||||||
|
|
||||||
|
const { user } = Astro.locals;
|
||||||
|
if (user) {
|
||||||
|
return Astro.redirect("/account");
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder="Your name"
|
||||||
|
autocomplete="off"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label class="label is-required" for="email">Email</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="Your email"
|
||||||
|
type="email"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Your password"
|
||||||
|
minlength="8"
|
||||||
|
type="password"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button type="submit">Create Account</button>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
|
||||||
|
<form method="POST" action="/api/oauth">
|
||||||
|
<button class="button is-discord is-full-width" type="submit">
|
||||||
|
<span class="icon-discord" aria-hidden="true"></span>
|
||||||
|
<span class="text">Sign in with discord</span></button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<a href="/api/an">Create Anonymous Session</a>
|
45
src/server/appwrite.ts
Normal file
45
src/server/appwrite.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
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;
|
||||||
|
}
|
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict"
|
||||||
|
}
|
Loading…
Reference in a new issue