Unfinished

This commit is contained in:
Aron Malcher 2024-02-18 22:02:52 +01:00
parent 18c6535d1c
commit 6b388729d9
Signed by: aronmal
GPG key ID: 816B7707426FC612
25 changed files with 1598 additions and 2352 deletions

View file

@ -46,7 +46,7 @@ To get started with li'l Judd, follow the instructions below.
Create a `.env` file in the root directory and add the following variables: Create a `.env` file in the root directory and add the following variables:
```env ```env
VITE_DISCORD_CLIENT=your_discord_client_id VITE_DISCORD_CLIENT_ID=your_discord_client_id
VITE_DISCORD_CLIENT_SECRET=your_discord_client_secret VITE_DISCORD_CLIENT_SECRET=your_discord_client_secret
VITE_DISCORD_BOT_TOKEN=your_discord_bot_token VITE_DISCORD_BOT_TOKEN=your_discord_bot_token
VITE_DISCORD_BOT_PERMISSIONS=18977581952080 VITE_DISCORD_BOT_PERMISSIONS=18977581952080
@ -56,16 +56,14 @@ To get started with li'l Judd, follow the instructions below.
VITE_DATABASE_URL=your_database_url VITE_DATABASE_URL=your_database_url
``` ```
Recieve your discord applications client id & secret, as well as the bot token from the [Applications dashboard](https://discord.com/developers/applications/). Recieve your discord applications `CLIENT_ID` & `CLIENT_SECRET`, as well as the bot token from the [Applications dashboard](https://discord.com/developers/applications/).
How to generate your `VITE_AUTH_SECRET` with [`openssl rand -base64 32`](https://authjs.dev/reference/core#secret). Your `VITE_AUTH_REDIRECT_URL` should look like this: `https://<hostname>/api/auth/callback/discord`.
Composite your `VITE_DATABASE_URL` like [`postgres://postgres:adminadmin@0.0.0.0:5432/db`](https://orm.drizzle.team/docs/get-started-postgresql#postgresjs). Composite your `VITE_DATABASE_URL` like [`postgres://postgres:adminadmin@0.0.0.0:5432/db`](https://orm.drizzle.team/docs/get-started-postgresql#postgresjs).
#### Development #### Development
Specify `VITE_AUTH_REDIRECT_PROXY_URL` only if necessary, particularly when setting up a reverse proxy to test authentication with callbacks to your development box. [Auth.js Docs Reference](https://authjs.dev/reference/nextjs/#redirectproxyurl)
The duplicate `DATABASE_URL` is only needed for Drizzle Studio. The duplicate `DATABASE_URL` is only needed for Drizzle Studio.
``` ```

View file

@ -6,6 +6,6 @@ export default {
out: "./src/drizzle/migrations", out: "./src/drizzle/migrations",
driver: "pg", driver: "pg",
dbCredentials: { dbCredentials: {
connectionString: process.env.DATABASE_URL ?? "", connectionString: process.env.DATABASE_URL!,
}, },
} satisfies Config; } satisfies Config;

View file

@ -13,9 +13,6 @@
"drizzle-studio": "drizzle-kit studio" "drizzle-studio": "drizzle-kit studio"
}, },
"dependencies": { "dependencies": {
"@auth/core": "^0.19.0",
"@auth/drizzle-adapter": "^0.6.3",
"@auth/solid-start": "0.1.2",
"@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/pro-duotone-svg-icons": "^6.5.1", "@fortawesome/pro-duotone-svg-icons": "^6.5.1",
"@fortawesome/pro-light-svg-icons": "^6.5.1", "@fortawesome/pro-light-svg-icons": "^6.5.1",
@ -23,29 +20,34 @@
"@fortawesome/pro-solid-svg-icons": "^6.5.1", "@fortawesome/pro-solid-svg-icons": "^6.5.1",
"@fortawesome/pro-thin-svg-icons": "^6.5.1", "@fortawesome/pro-thin-svg-icons": "^6.5.1",
"@fortawesome/sharp-solid-svg-icons": "^6.5.1", "@fortawesome/sharp-solid-svg-icons": "^6.5.1",
"@lucia-auth/adapter-drizzle": "^1.0.2",
"@paralleldrive/cuid2": "^2.2.2",
"@solidjs/meta": "^0.29.3", "@solidjs/meta": "^0.29.3",
"@solidjs/router": "^0.12.0", "@solidjs/router": "^0.12.3",
"@solidjs/start": "^0.5.4", "@solidjs/start": "^0.5.9",
"arctic": "^1.1.6",
"drizzle-orm": "^0.29.3", "drizzle-orm": "^0.29.3",
"lucia": "^3.0.1",
"moment-timezone": "^0.5.45", "moment-timezone": "^0.5.45",
"openapi-fetch": "^0.8.2", "openapi-fetch": "^0.9.1",
"postgres": "^3.4.3", "postgres": "^3.4.3",
"solid-js": "^1.8.14", "solid-js": "^1.8.15",
"vinxi": "^0.2.1" "vinxi": "^0.3.3"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/eslint-plugin": "^7.0.1",
"dotenv": "^16.4.2", "dotenv": "^16.4.4",
"drizzle-kit": "^0.20.14", "drizzle-kit": "^0.20.14",
"drizzle-zod": "^0.5.1", "drizzle-zod": "^0.5.1",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-solid": "^0.13.1", "eslint-plugin-solid": "^0.13.1",
"h3": "^1.10.1",
"openapi-typescript": "^6.7.4", "openapi-typescript": "^6.7.4",
"pg": "^8.11.3", "pg": "^8.11.3",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4", "prettier-plugin-organize-imports": "^3.2.4",
"sass": "^1.70.0", "sass": "^1.71.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"zod": "3.22.4" "zod": "3.22.4"
}, },

File diff suppressed because it is too large Load diff

View file

@ -36,7 +36,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -79,7 +79,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
}, },
@ -113,7 +113,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -159,7 +159,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
}, },
@ -203,7 +203,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -252,7 +252,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -308,7 +308,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -374,7 +374,7 @@
}, },
"security": [ "security": [
{ {
"api_key": [] "bot_token": []
} }
] ]
} }
@ -390,9 +390,6 @@
"items": { "items": {
"$ref": "#/components/schemas/guildConfig" "$ref": "#/components/schemas/guildConfig"
} }
},
"accessToken": {
"type": "string"
} }
} }
}, },
@ -536,10 +533,10 @@
} }
}, },
"securitySchemes": { "securitySchemes": {
"api_key": { "bot_token": {
"type": "apiKey", "type": "http",
"name": "api_key", "scheme": "bearer",
"in": "header" "bearerFormat": "JWT"
} }
} }
} }

View file

@ -6,17 +6,13 @@ import NavUser from "./NavUser";
export function Li(props: { export function Li(props: {
href: string; href: string;
action?: () => void; rel?: string;
name?: string; name?: string;
children?: JSX.Element; children?: JSX.Element;
}) { }) {
return ( return (
<li class="navElem flex-row thick"> <li class="navElem flex-row thick">
<a <a class="flex-row" href={props.href} rel={props.rel}>
class="flex-row"
href={props.href}
onClick={() => props.action && props.action()}
>
{props.children ?? <></>} {props.children ?? <></>}
<Show when={props.name}> <Show when={props.name}>
<span>{props.name}</span> <span>{props.name}</span>

View file

@ -1,50 +1,37 @@
import { getSession } from "@auth/solid-start";
import { signIn, signOut } from "@auth/solid-start/client";
import { import {
faArrowRightFromBracket, faArrowRightFromBracket,
faArrowRightToBracket, faArrowRightToBracket,
faGear, faGear,
} from "@fortawesome/pro-regular-svg-icons"; } from "@fortawesome/pro-regular-svg-icons";
import { eq } from "drizzle-orm"; import { User } from "lucia";
import { Show, createResource } from "solid-js"; import { Show, createResource } from "solid-js";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import db from "~/drizzle";
import { users } from "~/drizzle/schema";
import { authOptions } from "~/server/auth";
import { FontAwesomeIcon } from "./FontAwesomeIcon"; import { FontAwesomeIcon } from "./FontAwesomeIcon";
import { Li } from "./NavBar"; import { Li } from "./NavBar";
const initialUser = { async function getUser(): Promise<
id: "", | ({
name: null as string | null, success: false;
email: "", message: string;
emailVerified: null as Date | null, // user?: undefined;
image: null as string | null, } & Partial<User>)
}; | ({
success: true;
async function getUser() { message?: undefined;
} & User)
> {
"use server"; "use server";
const event = getRequestEvent(); const event = getRequestEvent();
if (!event) if (!event) return { success: false, message: "No request event!" };
return { success: false, message: "No request event!", ...initialUser };
const session = await getSession(event.request, authOptions); const pathname = new URL(event.request.url).pathname;
if (!session?.user?.id) const { user } = event.nativeEvent.context;
return { success: false, message: "No user with id!", ...initialUser }; if (!user) return { success: false, message: "User not logged in!" };
const user = ( console.log("userInfo", pathname, "success");
await db
.selectDistinct()
.from(users)
.where(eq(users.id, session.user?.id))
.limit(1)
.execute()
)[0];
console.log("userInfo", "success"); return { success: true, ...user };
return { success: true, message: "", ...user };
} }
function NavUser() { function NavUser() {
@ -55,16 +42,20 @@ function NavUser() {
return user; return user;
}); });
const pfp = () => {
const thisUser = user();
if (!thisUser?.success) return "";
return thisUser.image
? `https://cdn.discordapp.com/avatars/${thisUser.discord_id}/${thisUser.image}.png`
: `https://cdn.discordapp.com/embed/avatars/${(parseInt(thisUser.discord_id) >> 22) % 6}.png`;
};
return ( return (
<Show <Show
when={user()?.id} when={user()?.id}
fallback={ fallback={
<Li <Li href="/api/auth/login" name="Login" rel="external">
href="#"
name="Login"
action={() => signIn("discord", { callbackUrl: "/config" })}
>
<FontAwesomeIcon <FontAwesomeIcon
class="secondary" class="secondary"
icon={faArrowRightToBracket} icon={faArrowRightToBracket}
@ -75,11 +66,11 @@ function NavUser() {
> >
<Li href="/config"> <Li href="/config">
<div class="swap lower"> <div class="swap lower">
<img class="primary" src={user()?.image ?? ""} alt="User pfp" /> <img class="primary" src={pfp()} alt="User pfp" />
<FontAwesomeIcon class="secondary" icon={faGear} size="xl" /> <FontAwesomeIcon class="secondary" icon={faGear} size="xl" />
</div> </div>
</Li> </Li>
<Li href="#" action={() => signOut({ callbackUrl: "/" })}> <Li href="/api/auth/logout" rel="external">
<FontAwesomeIcon icon={faArrowRightFromBracket} size="xl" /> <FontAwesomeIcon icon={faArrowRightFromBracket} size="xl" />
</Li> </Li>
</Show> </Show>

View file

@ -1,10 +1,8 @@
import type { AdapterAccount } from "@auth/core/adapters";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
import { import {
boolean, boolean,
integer, integer,
pgTable, pgTable,
primaryKey,
serial, serial,
smallint, smallint,
text, text,
@ -13,56 +11,31 @@ import {
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
export const users = pgTable("user", { export const users = pgTable("user", {
id: text("id").notNull().primaryKey(), id: varchar("id", { length: 24 }).primaryKey(),
discord_id: text("discord_id").notNull(),
name: text("name"), name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"), image: text("image"),
}); });
export const accounts = pgTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
}),
);
export const sessions = pgTable("session", { export const sessions = pgTable("session", {
sessionToken: text("sessionToken").notNull().primaryKey(), id: varchar("id", { length: 24 }).primaryKey(),
userId: text("userId") userId: varchar("user_id", { length: 24 })
.notNull() .notNull()
.references(() => users.id, { onDelete: "cascade" }), .references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(), expiresAt: timestamp("expires_at", {
withTimezone: true,
mode: "date",
}).notNull(),
}); });
export const verificationTokens = pgTable( export const discordTokens = pgTable("tokens", {
"verificationToken", userId: varchar("user_id", { length: 24 })
{ .primaryKey()
identifier: text("identifier").notNull(), .references(() => users.id, { onDelete: "cascade" }),
token: text("token").notNull(), refreshToken: text("refresh_token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(), accessToken: text("access_token").notNull(),
}, expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
(vt) => ({ });
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
}),
);
export const matchPlannings = pgTable("match_planning", { export const matchPlannings = pgTable("match_planning", {
id: serial("id").primaryKey(), id: serial("id").primaryKey(),

View file

@ -1,3 +1,3 @@
import { mount, StartClient } from "@solidjs/start/client"; import { mount, StartClient } from "@solidjs/start/client";
mount(() => <StartClient />, document.getElementById("app")); mount(() => <StartClient />, document.getElementById("app")!);

46
src/lib/auth.ts Normal file
View file

@ -0,0 +1,46 @@
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { Discord } from "arctic";
import { PgColumn, PgTableWithColumns } from "drizzle-orm/pg-core";
import { Lucia } from "lucia";
import db from "~/drizzle";
import { sessions, users } from "~/drizzle/schema";
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
// set to `true` when using HTTPS
secure: import.meta.env.PROD,
},
},
getUserAttributes: (attributes) => attributes,
});
declare module "lucia" {
// eslint-disable-next-line no-unused-vars
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: DatabaseUserAttributes;
}
}
type GetColumns<T> =
T extends PgTableWithColumns<infer First> ? First["columns"] : never;
type ExtractDataTypes<T> = {
[K in keyof T]: T[K] extends PgColumn<infer DataType, any, any>
? DataType["data"]
: never;
};
interface DatabaseUserAttributes
extends ExtractDataTypes<GetColumns<typeof users>> {
warst: string;
}
export const discord = new Discord(
import.meta.env.VITE_DISCORD_CLIENT_ID,
import.meta.env.VITE_DISCORD_CLIENT_SECRET,
import.meta.env.VITE_AUTH_REDIRECT_URL,
);

54
src/middleware.ts Normal file
View file

@ -0,0 +1,54 @@
import { createMiddleware } from "@solidjs/start/middleware";
import { Session, User, verifyRequestOrigin } from "lucia";
import { appendHeader, getCookie, getHeader } from "vinxi/http";
import { lucia } from "./lib/auth";
export default createMiddleware({
onRequest: async (event) => {
if (event.nativeEvent.node.req.method !== "GET") {
const originHeader = getHeader(event, "Origin") ?? null;
const hostHeader = getHeader(event, "Host") ?? null;
if (
!originHeader ||
!hostHeader ||
!verifyRequestOrigin(originHeader, [hostHeader])
) {
event.nativeEvent.node.res.writeHead(403).end();
return;
}
}
const sessionId = getCookie(event, lucia.sessionCookieName) ?? null;
if (!sessionId) {
event.nativeEvent.context.session = null;
event.nativeEvent.context.user = null;
return;
}
const { session, user } = await lucia.validateSession(sessionId);
if (session && session.fresh) {
appendHeader(
event,
"Set-Cookie",
lucia.createSessionCookie(session.id).serialize(),
);
}
if (!session) {
appendHeader(
event,
"Set-Cookie",
lucia.createBlankSessionCookie().serialize(),
);
}
event.nativeEvent.context.session = session;
event.nativeEvent.context.user = user;
},
});
declare module "h3" {
// eslint-disable-next-line no-unused-vars
interface H3EventContext {
user: User | null;
session: Session | null;
}
}

View file

@ -1,4 +0,0 @@
import { SolidAuth } from "@auth/solid-start"
import { authOptions } from "~/server/auth"
export const { GET, POST } = SolidAuth(authOptions)

View file

@ -0,0 +1,132 @@
import { createId } from "@paralleldrive/cuid2";
import { APIEvent } from "@solidjs/start/server/types";
import { OAuth2RequestError } from "arctic";
import { eq } from "drizzle-orm";
import createClient from "openapi-fetch";
import { getCookie, setCookie } from "vinxi/http";
import db from "~/drizzle";
import { discordTokens, users } from "~/drizzle/schema";
import { discord, lucia } from "~/lib/auth";
import { paths } from "~/types/discord";
export async function GET(event: APIEvent): Promise<Response> {
const code = new URL(event.request.url).searchParams.get("code");
const state = new URL(event.request.url).searchParams.get("state");
const error = new URL(event.request.url).searchParams.get("error");
const error_description = new URL(event.request.url).searchParams.get(
"error_description",
);
if (error)
switch (error) {
case "access_denied":
return new Response(null, {
status: 302,
headers: { Location: "/" },
});
default:
console.log("Discord oauth error:", error_description);
return new Response(decodeURI(error_description ?? ""), {
status: 400,
});
}
const storedState = getCookie("discord_oauth_state") ?? null;
if (!code || !state || !storedState || state !== storedState) {
return new Response(null, {
status: 400,
});
}
try {
const tokens = await discord.validateAuthorizationCode(code);
const { GET } = createClient<paths>({
baseUrl: "https://discord.com/api/v10",
});
const discordUserResponse = await GET("/users/@me", {
headers: { Authorization: `Bearer ${tokens.accessToken}` },
});
if (discordUserResponse.error) throw discordUserResponse.error;
const discordUser = discordUserResponse.data;
const existingUser = await db.query.users
.findFirst({
where: eq(users.discord_id, discordUser.id),
})
.execute();
if (existingUser) {
const session = await lucia.createSession(
existingUser.id,
{},
{ sessionId: createId() },
);
const sessionCookie = lucia.createSessionCookie(session.id);
console.log(sessionCookie);
setCookie(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes,
);
await db
.update(users)
.set({
name: discordUser.global_name,
image: discordUser.avatar,
})
.where(eq(users.discord_id, discordUser.id))
.returning()
.execute();
return new Response(null, {
status: 302,
headers: { Location: "/config" },
});
}
const userId = createId();
await db.insert(users).values({
id: userId,
discord_id: discordUser.id,
name: discordUser.global_name,
image: discordUser.avatar,
});
await db
.insert(discordTokens)
.values({
userId,
accessToken: tokens.accessToken,
expiresAt: tokens.accessTokenExpiresAt,
refreshToken: tokens.refreshToken,
})
.returning()
.execute();
console.log(createId(), createId(), { warst: createId() });
const session = await lucia.createSession(
userId,
{},
{ sessionId: createId() },
);
const sessionCookie = lucia.createSessionCookie(session.id);
setCookie(
sessionCookie.name,
sessionCookie.value,
sessionCookie.attributes,
);
return new Response(null, {
status: 302,
headers: { Location: "/config" },
});
} catch (e) {
// the specific error message depends on the provider
if (e instanceof OAuth2RequestError) {
// invalid code
return new Response(null, {
status: 400,
});
}
console.error("Unknown error on callback.");
console.error(e);
return new Response(null, {
status: 500,
});
}
}

View file

@ -0,0 +1,24 @@
import { APIEvent } from "@solidjs/start/server/types";
import { generateState } from "arctic";
import { setCookie } from "vinxi/http";
import { discord } from "~/lib/auth";
export async function GET(event: APIEvent) {
const state = generateState();
const url = await discord.createAuthorizationURL(state, {
scopes: ["identify", "guilds", "guilds.members.read"],
});
setCookie(event, "discord_oauth_state", state, {
path: "/",
secure: import.meta.env.PROD,
httpOnly: true,
maxAge: 60 * 10,
sameSite: "lax",
});
return new Response(null, {
status: 302,
headers: { Location: url.toString() },
});
}

View file

@ -0,0 +1,19 @@
import { APIEvent } from "@solidjs/start/server/types";
import { appendHeader } from "vinxi/http";
import { lucia } from "~/lib/auth";
export const GET = async (event: APIEvent) => {
if (!event.nativeEvent.context.session) {
return new Error("Unauthorized");
}
await lucia.invalidateSession(event.nativeEvent.context.session.id);
appendHeader(
event,
"Set-Cookie",
lucia.createBlankSessionCookie().serialize(),
);
return new Response(null, {
status: 302,
headers: { Location: "/" },
});
};

View file

@ -4,6 +4,19 @@ import db from "~/drizzle";
import { guilds } from "~/drizzle/schema"; import { guilds } from "~/drizzle/schema";
export const GET = async ({ params }: APIEvent) => { export const GET = async ({ params }: APIEvent) => {
if (params.guildId === "boot") {
const guilds = await db.query.guilds
.findMany({
with: {
timePlanning: { with: { messages: true } },
matches: true,
},
})
.execute();
return { guilds };
}
const guild = await db.query.guilds const guild = await db.query.guilds
.findFirst({ .findFirst({
where: eq(guilds.id, params.guildId), where: eq(guilds.id, params.guildId),

View file

@ -1,4 +1,3 @@
import { getSession } from "@auth/solid-start";
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons"; import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
import { useLocation, useNavigate, useParams } from "@solidjs/router"; import { useLocation, useNavigate, useParams } from "@solidjs/router";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@ -16,8 +15,7 @@ import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; import Layout from "~/components/Layout";
import db from "~/drizzle"; import db from "~/drizzle";
import { accounts } from "~/drizzle/schema"; import { discordTokens } from "~/drizzle/schema";
import { authOptions } from "~/server/auth";
import { paths } from "~/types/discord"; import { paths } from "~/types/discord";
import "../../styles/pages/config.scss"; import "../../styles/pages/config.scss";
@ -47,26 +45,21 @@ const getPayload = async (
if (!event) return { success: false, message: "No request event!" }; if (!event) return { success: false, message: "No request event!" };
const pathname = new URL(event.request.url).pathname; const pathname = new URL(event.request.url).pathname;
const session = await getSession(event.request, authOptions); const { user } = event.nativeEvent.context;
if (!session?.user?.id) if (!user) return { success: false, message: "User not logged in!" };
return { success: false, message: "No user with id!" };
const { DISCORD_ACCESS_TOKEN } = ( const tokens = await db.query.discordTokens
await db .findFirst({
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token }) where: eq(discordTokens.userId, user.id),
.from(accounts) })
.where(eq(accounts.userId, session.user?.id)) .execute();
.limit(1) if (!tokens) return { success: false, message: "No discord access token!" };
.execute()
)[0];
if (!DISCORD_ACCESS_TOKEN)
return { success: false, message: "No discord access token!" };
const { GET } = createClient<paths>({ const { GET } = createClient<paths>({
baseUrl: "https://discord.com/api/v10", baseUrl: "https://discord.com/api/v10",
}); });
const guildsRequest = await GET("/users/@me/guilds", { const guildsRequest = await GET("/users/@me/guilds", {
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` }, headers: { Authorization: `Bearer ${tokens.accessToken}` },
}); });
const channelsRequest = await GET("/guilds/{guild_id}/channels", { const channelsRequest = await GET("/guilds/{guild_id}/channels", {
params: { params: {

View file

@ -1,4 +1,3 @@
import { getSession } from "@auth/solid-start";
import { import {
faBadgeCheck, faBadgeCheck,
faCircleExclamation, faCircleExclamation,
@ -12,8 +11,7 @@ import { getRequestEvent } from "solid-js/web";
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"; import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
import Layout from "~/components/Layout"; import Layout from "~/components/Layout";
import db from "~/drizzle"; import db from "~/drizzle";
import { accounts } from "~/drizzle/schema"; import { discordTokens } from "~/drizzle/schema";
import { authOptions } from "~/server/auth";
import { paths } from "~/types/discord"; import { paths } from "~/types/discord";
import "../../styles/pages/config.scss"; import "../../styles/pages/config.scss";
@ -36,26 +34,21 @@ const getPayload = async (): Promise<
if (!event) return { success: false, message: "No request event!" }; if (!event) return { success: false, message: "No request event!" };
const pathname = new URL(event.request.url).pathname; const pathname = new URL(event.request.url).pathname;
const session = await getSession(event.request, authOptions); const { user } = event.nativeEvent.context;
if (!session?.user?.id) if (!user) return { success: false, message: "User not logged in!" };
return { success: false, message: "No user with id!" };
const { DISCORD_ACCESS_TOKEN } = ( const tokens = await db.query.discordTokens
await db .findFirst({
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token }) where: eq(discordTokens.userId, user.id),
.from(accounts) })
.where(eq(accounts.userId, session.user?.id)) .execute();
.limit(1) if (!tokens) return { success: false, message: "No discord access token!" };
.execute()
)[0];
if (!DISCORD_ACCESS_TOKEN)
return { success: false, message: "No discord access token!" };
const { GET } = createClient<paths>({ const { GET } = createClient<paths>({
baseUrl: "https://discord.com/api/v10", baseUrl: "https://discord.com/api/v10",
}); });
const { data: guilds, error } = await GET("/users/@me/guilds", { const { data: guilds, error } = await GET("/users/@me/guilds", {
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` }, headers: { Authorization: `Bearer ${tokens.accessToken}` },
}); });
if (error) { if (error) {

View file

@ -1,38 +0,0 @@
import Discord from "@auth/core/providers/discord";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { type SolidAuthConfig } from "@auth/solid-start";
import db from "~/drizzle";
export const authOptions: SolidAuthConfig = {
providers: [
{
...Discord({
clientId: import.meta.env.VITE_DISCORD_CLIENT_ID,
clientSecret: import.meta.env.VITE_DISCORD_CLIENT_SECRET,
}),
authorization:
"https://discord.com/api/oauth2/authorize?scope=identify+email+guilds+guilds.members.read",
},
],
adapter: DrizzleAdapter(db),
secret: import.meta.env.VITE_AUTH_SECRET,
callbacks: {
// @ts-ignore
session: ({ session, user }) => {
if (session?.user) {
session.user.id = user.id;
}
return session;
},
},
pages: {
// signIn: "/signin",
// signOut: "/signout",
// error: '/auth/error', // Error code passed in query string as ?error=
// verifyRequest: '/auth/verify-request', // (used for check email message)
// newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
},
redirectProxyUrl: import.meta.env.DEV
? import.meta.env.VITE_AUTH_REDIRECT_PROXY_URL
: undefined,
};

View file

@ -1,3 +1,7 @@
import { defineConfig } from "@solidjs/start/config"; import { defineConfig } from "@solidjs/start/config";
export default defineConfig({}); export default defineConfig({
start: {
middleware: "./src/middleware.ts",
},
});