Unfinished
This commit is contained in:
parent
18c6535d1c
commit
6b388729d9
25 changed files with 1598 additions and 2352 deletions
|
@ -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:
|
||||
|
||||
```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_BOT_TOKEN=your_discord_bot_token
|
||||
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
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
#### 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.
|
||||
|
||||
```
|
||||
|
|
|
@ -6,6 +6,6 @@ export default {
|
|||
out: "./src/drizzle/migrations",
|
||||
driver: "pg",
|
||||
dbCredentials: {
|
||||
connectionString: process.env.DATABASE_URL ?? "",
|
||||
connectionString: process.env.DATABASE_URL!,
|
||||
},
|
||||
} satisfies Config;
|
||||
|
|
24
package.json
24
package.json
|
@ -13,9 +13,6 @@
|
|||
"drizzle-studio": "drizzle-kit studio"
|
||||
},
|
||||
"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/pro-duotone-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-thin-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/router": "^0.12.0",
|
||||
"@solidjs/start": "^0.5.4",
|
||||
"@solidjs/router": "^0.12.3",
|
||||
"@solidjs/start": "^0.5.9",
|
||||
"arctic": "^1.1.6",
|
||||
"drizzle-orm": "^0.29.3",
|
||||
"lucia": "^3.0.1",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"openapi-fetch": "^0.8.2",
|
||||
"openapi-fetch": "^0.9.1",
|
||||
"postgres": "^3.4.3",
|
||||
"solid-js": "^1.8.14",
|
||||
"vinxi": "^0.2.1"
|
||||
"solid-js": "^1.8.15",
|
||||
"vinxi": "^0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"dotenv": "^16.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
||||
"dotenv": "^16.4.4",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"drizzle-zod": "^0.5.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-solid": "^0.13.1",
|
||||
"h3": "^1.10.1",
|
||||
"openapi-typescript": "^6.7.4",
|
||||
"pg": "^8.11.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"sass": "^1.70.0",
|
||||
"sass": "^1.71.0",
|
||||
"typescript": "^5.3.3",
|
||||
"zod": "3.22.4"
|
||||
},
|
||||
|
|
3363
pnpm-lock.yaml
3363
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -36,7 +36,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -79,7 +79,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -113,7 +113,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -159,7 +159,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -203,7 +203,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -252,7 +252,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -308,7 +308,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -374,7 +374,7 @@
|
|||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": []
|
||||
"bot_token": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -390,9 +390,6 @@
|
|||
"items": {
|
||||
"$ref": "#/components/schemas/guildConfig"
|
||||
}
|
||||
},
|
||||
"accessToken": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -536,10 +533,10 @@
|
|||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
"in": "header"
|
||||
"bot_token": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,13 @@ import NavUser from "./NavUser";
|
|||
|
||||
export function Li(props: {
|
||||
href: string;
|
||||
action?: () => void;
|
||||
rel?: string;
|
||||
name?: string;
|
||||
children?: JSX.Element;
|
||||
}) {
|
||||
return (
|
||||
<li class="navElem flex-row thick">
|
||||
<a
|
||||
class="flex-row"
|
||||
href={props.href}
|
||||
onClick={() => props.action && props.action()}
|
||||
>
|
||||
<a class="flex-row" href={props.href} rel={props.rel}>
|
||||
{props.children ?? <></>}
|
||||
<Show when={props.name}>
|
||||
<span>{props.name}</span>
|
||||
|
|
|
@ -1,50 +1,37 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import { signIn, signOut } from "@auth/solid-start/client";
|
||||
import {
|
||||
faArrowRightFromBracket,
|
||||
faArrowRightToBracket,
|
||||
faGear,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { User } from "lucia";
|
||||
import { Show, createResource } from "solid-js";
|
||||
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 { Li } from "./NavBar";
|
||||
|
||||
const initialUser = {
|
||||
id: "",
|
||||
name: null as string | null,
|
||||
email: "",
|
||||
emailVerified: null as Date | null,
|
||||
image: null as string | null,
|
||||
};
|
||||
|
||||
async function getUser() {
|
||||
async function getUser(): Promise<
|
||||
| ({
|
||||
success: false;
|
||||
message: string;
|
||||
// user?: undefined;
|
||||
} & Partial<User>)
|
||||
| ({
|
||||
success: true;
|
||||
message?: undefined;
|
||||
} & User)
|
||||
> {
|
||||
"use server";
|
||||
|
||||
const event = getRequestEvent();
|
||||
if (!event)
|
||||
return { success: false, message: "No request event!", ...initialUser };
|
||||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!", ...initialUser };
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
const user = (
|
||||
await db
|
||||
.selectDistinct()
|
||||
.from(users)
|
||||
.where(eq(users.id, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
console.log("userInfo", pathname, "success");
|
||||
|
||||
console.log("userInfo", "success");
|
||||
|
||||
return { success: true, message: "", ...user };
|
||||
return { success: true, ...user };
|
||||
}
|
||||
|
||||
function NavUser() {
|
||||
|
@ -55,16 +42,20 @@ function NavUser() {
|
|||
|
||||
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 (
|
||||
<Show
|
||||
when={user()?.id}
|
||||
fallback={
|
||||
<Li
|
||||
href="#"
|
||||
name="Login"
|
||||
action={() => signIn("discord", { callbackUrl: "/config" })}
|
||||
>
|
||||
<Li href="/api/auth/login" name="Login" rel="external">
|
||||
<FontAwesomeIcon
|
||||
class="secondary"
|
||||
icon={faArrowRightToBracket}
|
||||
|
@ -75,11 +66,11 @@ function NavUser() {
|
|||
>
|
||||
<Li href="/config">
|
||||
<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" />
|
||||
</div>
|
||||
</Li>
|
||||
<Li href="#" action={() => signOut({ callbackUrl: "/" })}>
|
||||
<Li href="/api/auth/logout" rel="external">
|
||||
<FontAwesomeIcon icon={faArrowRightFromBracket} size="xl" />
|
||||
</Li>
|
||||
</Show>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
serial,
|
||||
smallint,
|
||||
text,
|
||||
|
@ -13,56 +11,31 @@ import {
|
|||
} from "drizzle-orm/pg-core";
|
||||
|
||||
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"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
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", {
|
||||
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
userId: text("userId")
|
||||
id: varchar("id", { length: 24 }).primaryKey(),
|
||||
userId: varchar("user_id", { length: 24 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
expiresAt: timestamp("expires_at", {
|
||||
withTimezone: true,
|
||||
mode: "date",
|
||||
}).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
|
||||
}),
|
||||
);
|
||||
export const discordTokens = pgTable("tokens", {
|
||||
userId: varchar("user_id", { length: 24 })
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
refreshToken: text("refresh_token").notNull(),
|
||||
accessToken: text("access_token").notNull(),
|
||||
expiresAt: timestamp("expires_at", { mode: "date" }).notNull(),
|
||||
});
|
||||
|
||||
export const matchPlannings = pgTable("match_planning", {
|
||||
id: serial("id").primaryKey(),
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { mount, StartClient } from "@solidjs/start/client";
|
||||
|
||||
mount(() => <StartClient />, document.getElementById("app"));
|
||||
mount(() => <StartClient />, document.getElementById("app")!);
|
||||
|
|
46
src/lib/auth.ts
Normal file
46
src/lib/auth.ts
Normal 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
54
src/middleware.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
import { SolidAuth } from "@auth/solid-start"
|
||||
import { authOptions } from "~/server/auth"
|
||||
|
||||
export const { GET, POST } = SolidAuth(authOptions)
|
132
src/routes/api/auth/callback/discord.ts
Normal file
132
src/routes/api/auth/callback/discord.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
24
src/routes/api/auth/login.ts
Normal file
24
src/routes/api/auth/login.ts
Normal 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() },
|
||||
});
|
||||
}
|
19
src/routes/api/auth/logout.ts
Normal file
19
src/routes/api/auth/logout.ts
Normal 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: "/" },
|
||||
});
|
||||
};
|
|
@ -4,6 +4,19 @@ import db from "~/drizzle";
|
|||
import { guilds } from "~/drizzle/schema";
|
||||
|
||||
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
|
||||
.findFirst({
|
||||
where: eq(guilds.id, params.guildId),
|
|
@ -1,4 +1,3 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import { faToggleOff, faToggleOn } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { useLocation, useNavigate, useParams } from "@solidjs/router";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
@ -16,8 +15,7 @@ import { getRequestEvent } from "solid-js/web";
|
|||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { accounts } from "~/drizzle/schema";
|
||||
import { authOptions } from "~/server/auth";
|
||||
import { discordTokens } from "~/drizzle/schema";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
|
@ -47,26 +45,21 @@ const getPayload = async (
|
|||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!" };
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
const { DISCORD_ACCESS_TOKEN } = (
|
||||
await db
|
||||
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token })
|
||||
.from(accounts)
|
||||
.where(eq(accounts.userId, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
if (!DISCORD_ACCESS_TOKEN)
|
||||
return { success: false, message: "No discord access token!" };
|
||||
const tokens = await db.query.discordTokens
|
||||
.findFirst({
|
||||
where: eq(discordTokens.userId, user.id),
|
||||
})
|
||||
.execute();
|
||||
if (!tokens) return { success: false, message: "No discord access token!" };
|
||||
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
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", {
|
||||
params: {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { getSession } from "@auth/solid-start";
|
||||
import {
|
||||
faBadgeCheck,
|
||||
faCircleExclamation,
|
||||
|
@ -12,8 +11,7 @@ import { getRequestEvent } from "solid-js/web";
|
|||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon";
|
||||
import Layout from "~/components/Layout";
|
||||
import db from "~/drizzle";
|
||||
import { accounts } from "~/drizzle/schema";
|
||||
import { authOptions } from "~/server/auth";
|
||||
import { discordTokens } from "~/drizzle/schema";
|
||||
import { paths } from "~/types/discord";
|
||||
import "../../styles/pages/config.scss";
|
||||
|
||||
|
@ -36,26 +34,21 @@ const getPayload = async (): Promise<
|
|||
if (!event) return { success: false, message: "No request event!" };
|
||||
|
||||
const pathname = new URL(event.request.url).pathname;
|
||||
const session = await getSession(event.request, authOptions);
|
||||
if (!session?.user?.id)
|
||||
return { success: false, message: "No user with id!" };
|
||||
const { user } = event.nativeEvent.context;
|
||||
if (!user) return { success: false, message: "User not logged in!" };
|
||||
|
||||
const { DISCORD_ACCESS_TOKEN } = (
|
||||
await db
|
||||
.selectDistinct({ DISCORD_ACCESS_TOKEN: accounts.access_token })
|
||||
.from(accounts)
|
||||
.where(eq(accounts.userId, session.user?.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
)[0];
|
||||
if (!DISCORD_ACCESS_TOKEN)
|
||||
return { success: false, message: "No discord access token!" };
|
||||
const tokens = await db.query.discordTokens
|
||||
.findFirst({
|
||||
where: eq(discordTokens.userId, user.id),
|
||||
})
|
||||
.execute();
|
||||
if (!tokens) return { success: false, message: "No discord access token!" };
|
||||
|
||||
const { GET } = createClient<paths>({
|
||||
baseUrl: "https://discord.com/api/v10",
|
||||
});
|
||||
const { data: guilds, error } = await GET("/users/@me/guilds", {
|
||||
headers: { Authorization: `Bearer ${DISCORD_ACCESS_TOKEN}` },
|
||||
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
||||
});
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -1,3 +1,7 @@
|
|||
import { defineConfig } from "@solidjs/start/config";
|
||||
|
||||
export default defineConfig({});
|
||||
export default defineConfig({
|
||||
start: {
|
||||
middleware: "./src/middleware.ts",
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue