apps/api/src/index.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
import { Elysia, t } from "elysia";
import {
RESTGetAPICurrentUserResult,
type RESTPostOAuth2AccessTokenResult,
Routes,
RouteBases,
} from "discord-api-types/v10";
import config, { IS_PROD } from "@stealth-developers/config";
import db, { sessions, upsertUser, getUser, getSession } from "@stealth-developers/db";
import { isError, isOAuth2Error, makeRequest, type OAuth2ErrorResponse } from "./discord";
const CLIENT_ID = config.discord.app_id;
const CLIENT_SECRET = config.discord.client_secret;
const HOST = IS_PROD ? "https://tickets.vt3e.cat" : "http://127.0.0.1:3000";
const REDIRECT_URI = `${HOST}/auth/callback`;
const FRONTEND_URL = IS_PROD ? HOST : "http://127.0.0.1:5173";
const authPlugin = new Elysia({ name: "auth" })
.guard({
as: "global",
cookie: t.Cookie({
session_id: t.Optional(t.String()),
}),
})
.resolve({ as: "global" }, async ({ cookie: { session_id } }) => {
const sessionId = session_id.value as string | undefined;
if (!sessionId) return { session: null };
const session = await getSession(sessionId);
if (!session || session.expiresAt < new Date()) return { session: null };
return { session };
});
const app = new Elysia()
.use(authPlugin)
.onError(({ error, set, request }) => {
set.status = 500;
console.error(request.url, error);
if (error instanceof Error) return { message: error.message ?? "Internal Server Error" };
return { message: "Unknown Error" };
})
.group("/auth", (app) =>
app
.get("/login", ({ redirect }) => {
const redirectUri = `https://discord.com/oauth2/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=identify+guilds`;
return redirect(redirectUri, 302);
})
.get(
"/callback",
async ({ query: { code }, cookie: { session_id }, redirect }) => {
const params = new URLSearchParams({
code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: "authorization_code",
redirect_uri: REDIRECT_URI,
});
const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
method: "POST",
body: params,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
const tokenData = (await tokenResponse.json()) as
| OAuth2ErrorResponse
| RESTPostOAuth2AccessTokenResult;
if (isOAuth2Error(tokenData))
throw new Error(tokenData.error_description ?? tokenData.error);
const userData = await makeRequest<RESTGetAPICurrentUserResult>(
`${RouteBases.api}${Routes.user()}`,
tokenData.access_token,
);
if (isError(userData)) throw new Error(userData.message);
const upsertedUser = await upsertUser({
avatar: userData.avatar,
username: userData.username,
displayName: userData.global_name,
id: userData.id,
});
const sessionId = crypto.randomUUID();
const expiresAt = new Date().getTime() + tokenData.expires_in * 1_000;
await db.insert(sessions).values({
id: sessionId,
userId: upsertedUser.id,
accessToken: tokenData.access_token,
refreshToken: tokenData.refresh_token,
scope: tokenData.scope,
tokenType: tokenData.token_type,
createdAt: new Date(),
expiresAt: new Date(expiresAt),
});
session_id.set({
value: sessionId,
httpOnly: true,
path: "/",
secure: IS_PROD,
sameSite: "lax",
expires: new Date(expiresAt),
});
return redirect(FRONTEND_URL);
},
{
query: t.Object({
code: t.String(),
}),
},
),
)
.group("/api", (app) =>
app
.resolve(({ session, status }) => {
if (!session) return status(401, { message: "unauthorized" });
return { session };
})
.get("/me", async ({ session }) => {
const user = await getUser(session.userId);
return user;
}),
)
.listen(config.api.port);
console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);
|