diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7c3fb9d --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +OAUTH_CLIENT_ID= +OAUTH_CLIENT_SECRET= diff --git a/.gitignore b/.gitignore index 87e5610..919c740 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,10 @@ yarn-error.log* **/*.tgz **/*.log package-lock.json -**/*.bun \ No newline at end of file +**/*.bun + +.vscode + +.env + +db.sqlite diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5c85638 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "client"] + path = client + url = git@code.incremental.social:thepaperpilot/chromatic-lattice.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2909a35 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM oven/bun + +WORKDIR /app + +COPY package.json . +COPY bun.lockb . + +RUN bun install --production + +COPY src src +COPY tsconfig.json . +# COPY public public + +ENV NODE_ENV production +CMD ["bun", "src/index.ts"] + +EXPOSE 3000 diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..de1405c Binary files /dev/null and b/bun.lockb differ diff --git a/client b/client new file mode 160000 index 0000000..337d394 --- /dev/null +++ b/client @@ -0,0 +1 @@ +Subproject commit 337d394fd33496d9aff23ef847ce56b1464deee6 diff --git a/common/events.ts b/common/events.ts new file mode 100644 index 0000000..60efe40 --- /dev/null +++ b/common/events.ts @@ -0,0 +1,22 @@ +import { t } from "elysia"; + +interface ServerToClientEvents { + "server version": (semver: string) => void; + info: (message: string) => void; + "set cursor position": (user: string, pos: { x: number; y: number }) => void; + chat: (user: string, message: string) => void; +} + +export const clientToServerEvents = t.Object({ + message: t.Union([ + t.Object({ + type: t.Literal("set cursor position"), + x: t.Number(), + y: t.Number() + }), + t.Object({ + type: t.Literal("chat"), + messageq: t.String() + }) + ]) +}); diff --git a/package.json b/package.json index 4e2df44..a942226 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,24 @@ { - "name": "@bun-examples/elysia", - "version": "1.0.50", + "name": "chromatic-lattice", + "version": "1.0.0", + "private": true, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "dev": "bun run --watch src/index.ts" + "start": "bun run server/index.ts", + "dev": "conc npm:dev:server npm:dev:client", + "dev:server": "bun run --watch server/index.ts", + "dev:client": "bunx --bun vite client", + "build": "bunx --bun vite build client", + "test": "vitest run -r client", + "testw": "vitest -r client" }, "dependencies": { - "elysia": "latest" + "@bogeychan/elysia-oauth2": "^0.0.19", + "elysia": "latest", + "profectus": "file:./client" }, "devDependencies": { - "bun-types": "latest" + "bun-types": "latest", + "concurrently": "^8.2.2" }, - "module": "src/index.js", - "bun-create": { - "start": "bun run src/index.ts" - } + "module": "server/index.js" } diff --git a/server/index.ts b/server/index.ts new file mode 100644 index 0000000..e8b7ff1 --- /dev/null +++ b/server/index.ts @@ -0,0 +1,109 @@ +import { Elysia, t } from "elysia"; +import oauth2 from "@bogeychan/elysia-oauth2"; +import { Database } from "bun:sqlite"; +import { randomBytes } from "crypto"; + + +import { migrateDatabase } from "./migrations"; +import { clientToServerEvents } from "../common/events"; + +const db = new Database("db.sqlite", { create: true }); +migrateDatabase(db); + +const states = new Set(); + +const auth = oauth2({ + profiles: { + incsoc: { + provider: { + clientId: Bun.env.OAUTH_CLIENT_ID!, + clientSecret: Bun.env.OAUTH_CLIENT_SECRET!, + + auth: { + url: 'https://auth.incremental.social/auth/v1', + params: {} + }, + + token: { + url: 'https://auth.incremental.social/oauth/v2/token', + params: {} + } + }, + scope: ['email', 'profile'] + } + }, + state: { + check(ctx, id, state) { + if (states.has(state)) { + states.delete(state); + return true; + } + + return false; + }, + generate(ctx, id) { + const state = randomBytes(8).toString('hex'); + states.add(state); + return state; + } + }, + storage: { + get(ctx, id) { + console.log(`get token: ${id}`); + + // const token = ( + // db + // .query('SELECT token FROM storage WHERE uuid = ? AND id = ?') + // .get(uuid, id) as { token: string } + // )?.token; + + // if (!token) { + // return; + // } + + // return JSON.parse(token); + return undefined; + }, + set(ctx, id, token) { + console.log(`new token: ${id}`); + + // db.run( + // 'INSERT OR REPLACE INTO storage (id, token) VALUES (?, ?)', + // [id, JSON.stringify(token)] + // ); + }, + delete(ctx, id) { + // db.run('DELETE FROM storage WHERE id = ?', [id]); + } + } +}); + +const app = new Elysia() + .use(auth) + .ws('/ws', { + body: t.Object({ + message: clientToServerEvents + }), + message(ws, { message }) { + console.log(message); + }, + beforeHandle: async function({ set, authorized, tokenHeaders }) { + // Check auth + if (!(await authorized("incsoc"))) { + return (set.status = 'Unauthorized'); + } + + const user = await fetch('https://auth.incremental.social/oidc/v1/userinfo', { + headers: await tokenHeaders("incsoc") + }); + + console.log(JSON.stringify(user)); + + // Update avatar and display name from mbin, fallback to userinfo + } + }) + .listen(3000); + +console.log( + `🦊 Chromatic Lattice server is running at ${app.server?.url.href}` +); diff --git a/server/migrations.ts b/server/migrations.ts new file mode 100644 index 0000000..c295e38 --- /dev/null +++ b/server/migrations.ts @@ -0,0 +1,12 @@ +import { Database } from "bun:sqlite"; + +export function migrateDatabase(db: Database) { + const version = (db.query("PRAGMA user_version").get() as { user_version: number }).user_version; + + if (version < 1) { + console.log("Applying migration 0 -> 1") + db.run("CREATE TABLE tokens (id TEXT, token TEXT, PRIMARY KEY(id))") + } + + db.run("PRAGMA user_version = 1;"); +} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 9c1f7a1..0000000 --- a/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Elysia } from "elysia"; - -const app = new Elysia().get("/", () => "Hello Elysia").listen(3000); - -console.log( - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` -);