From 7aee25cb33b2089cb9817511bc65bad52f552279 Mon Sep 17 00:00:00 2001 From: Sanjib Kumar Sen Date: Tue, 7 Jan 2025 20:52:27 +0600 Subject: [PATCH] init with db --- .env.example | 1 + .gitignore | 1 + auth-schema.ts | 51 +++++ bun.lockb | Bin 39216 -> 172058 bytes docker-compose.yaml | 16 ++ drizzle.config.ts | 11 + drizzle/0000_elite_ben_parker.sql | 50 +++++ drizzle/meta/0000_snapshot.json | 319 +++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 13 ++ package.json | 25 ++- src/api/note/note.controller.ts | 25 +++ src/api/note/note.model.ts | 7 + src/api/note/note.route.ts | 44 ++++ src/api/otp/otp.route.ts | 36 ++++ src/api/user/user.model.ts | 23 +++ src/db/index.ts | 4 + src/db/model.ts | 7 + src/db/schema.ts | 51 +++++ src/emails/otp.tsx | 30 +++ src/index.ts | 12 +- src/lib/auth/auth-view.ts | 12 ++ src/lib/auth/auth.ts | 26 +++ src/middlewares/auth-middleware.ts | 29 +++ src/routes/note.ts | 91 -------- src/routes/user.ts | 129 ------------ tsconfig.json | 30 ++- 26 files changed, 799 insertions(+), 244 deletions(-) create mode 100644 .env.example create mode 100644 auth-schema.ts create mode 100644 drizzle.config.ts create mode 100644 drizzle/0000_elite_ben_parker.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 src/api/note/note.controller.ts create mode 100644 src/api/note/note.model.ts create mode 100644 src/api/note/note.route.ts create mode 100644 src/api/otp/otp.route.ts create mode 100644 src/api/user/user.model.ts create mode 100644 src/db/index.ts create mode 100644 src/db/model.ts create mode 100644 src/db/schema.ts create mode 100644 src/emails/otp.tsx create mode 100644 src/lib/auth/auth-view.ts create mode 100644 src/lib/auth/auth.ts create mode 100644 src/middlewares/auth-middleware.ts delete mode 100644 src/routes/note.ts delete mode 100644 src/routes/user.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4cc714a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DATABASE_URL= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 87e5610..996fc0e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env.local .env.development.local .env.test.local diff --git a/auth-schema.ts b/auth-schema.ts new file mode 100644 index 0000000..4214f12 --- /dev/null +++ b/auth-schema.ts @@ -0,0 +1,51 @@ +import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").notNull(), + image: text("image"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const session = pgTable("session", { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id), +}); + +export const account = pgTable("account", { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const verification = pgTable("verification", { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at"), + updatedAt: timestamp("updated_at"), +}); diff --git a/bun.lockb b/bun.lockb index bc19a66a0facaa33847a1cf2e99155d2cd6a7b7b..975d250498cbd04eeabc35d16d3a5fd48f7a369e 100755 GIT binary patch literal 172058 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

x-=D4+%?~hbAl*I*lullAuGN1xAHEuNu|(0Uap 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 A)Bpeg delta 9876 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 statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean NOT NULL, + "image" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp, + "updated_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..df9dd9b --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,319 @@ +{ + "id": "f4717319-16e8-43a1-8b25-12a12964c98f", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..4e586cb --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1736255248333, + "tag": "0000_elite_ben_parker", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 1dc1017..39ae0d1 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,37 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "bun run --watch src/index.ts", + "email": "email dev --dir src/emails", "build": "bun build --compile --minify-whitespace --minify-syntax --target bun --outfile server ./src/index.ts" }, "dependencies": { "@elysiajs/opentelemetry": "^1.2.0", "@elysiajs/server-timing": "^1.2.0", "@elysiajs/swagger": "^1.2.0", - "elysia": "latest" + "@paralleldrive/cuid2": "^2.2.2", + "@react-email/components": "^0.0.31", + "better-auth": "^1.1.10", + "dotenv": "^16.4.7", + "drizzle-orm": "^0.38.3", + "drizzle-typebox": "^0.2.1", + "elysia": "latest", + "nodemailer": "^6.9.16", + "pg": "^8.13.1", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "override": { + "@sinclair/typebox": "0.34.13" }, "devDependencies": { - "bun-types": "latest" + "@types/nodemailer": "^6.4.17", + "@types/pg": "^8.11.10", + "@types/react": "^19.0.3", + "@types/react-dom": "^19.0.2", + "bun-types": "latest", + "drizzle-kit": "^0.30.1", + "react-email": "^3.0.4", + "tsx": "^4.19.2" }, "module": "src/index.js" } \ No newline at end of file diff --git a/src/api/note/note.controller.ts b/src/api/note/note.controller.ts new file mode 100644 index 0000000..9086a64 --- /dev/null +++ b/src/api/note/note.controller.ts @@ -0,0 +1,25 @@ +import { Memo } from "./note.model"; + +export class Note { + constructor( + public data: Memo[] = [ + { + data: "Moonhalo", + }, + ] + ) {} + + add(note: Memo) { + this.data.push(note); + + return this.data; + } + + remove(index: number) { + return this.data.splice(index, 1); + } + + update(index: number, note: Partial) { + return (this.data[index] = { ...this.data[index], ...note }); + } +} diff --git a/src/api/note/note.model.ts b/src/api/note/note.model.ts new file mode 100644 index 0000000..41889b3 --- /dev/null +++ b/src/api/note/note.model.ts @@ -0,0 +1,7 @@ +import { t } from "elysia"; + +export const memoSchema = t.Object({ + data: t.String(), +}); + +export type Memo = typeof memoSchema.static; diff --git a/src/api/note/note.route.ts b/src/api/note/note.route.ts new file mode 100644 index 0000000..c2f624b --- /dev/null +++ b/src/api/note/note.route.ts @@ -0,0 +1,44 @@ +import { Elysia, t } from "elysia"; +import { Note } from "./note.controller"; +import { memoSchema } from "./note.model"; + +export const note = new Elysia({ prefix: "/note" }) + .decorate("note", new Note()) + .model({ + memo: t.Omit(memoSchema, ["author"]), + }) + .get("/", ({ note }) => note.data) + .put("/", ({ note, body: { data } }) => note.add({ data }), { + body: "memo", + }) + .get( + "/:index", + ({ note, params: { index }, error }) => { + return note.data[index] ?? error(404, "Not Found :("); + }, + { + params: t.Object({ + index: t.Number(), + }), + } + ) + .guard({ + params: t.Object({ + index: t.Number(), + }), + }) + .delete("/:index", ({ note, params: { index }, error }) => { + if (index in note.data) return note.remove(index); + + return error(422); + }) + .patch( + "/:index", + ({ note, params: { index }, body: { data }, error }) => { + if (index in note.data) return note.update(index, { data }); + return error(422); + }, + { + body: "memo", + } + ); diff --git a/src/api/otp/otp.route.ts b/src/api/otp/otp.route.ts new file mode 100644 index 0000000..2c35f32 --- /dev/null +++ b/src/api/otp/otp.route.ts @@ -0,0 +1,36 @@ +import { Elysia, t } from "elysia"; +import { renderToStaticMarkup } from "react-dom/server"; +import nodemailer from "nodemailer"; +import OTPEmail from "../../emails/otp"; +import { createElement } from "react"; + +const transporter = nodemailer.createTransport({ + host: "smtp.gehenna.sh", + port: 465, + auth: { + user: "makoto", + pass: "12345678", + }, +}); + +export const otp = new Elysia({ prefix: "/otp" }).get( + "/", + async ({ body }) => { + // Random between 100,000 and 999,999 + const otp = ~~(Math.random() * (900_000 - 1)) + 100_000; + + const html = renderToStaticMarkup(createElement(OTPEmail, { otp })); + + await transporter.sendMail({ + from: "ibuki@gehenna.sh", + to: body, + subject: "Verify your email address", + html, + }); + + return { success: true }; + }, + { + body: t.String({ format: "email" }), + } +); diff --git a/src/api/user/user.model.ts b/src/api/user/user.model.ts new file mode 100644 index 0000000..dbb100d --- /dev/null +++ b/src/api/user/user.model.ts @@ -0,0 +1,23 @@ +import { pgTable, varchar, timestamp } from "drizzle-orm/pg-core"; + +import { createId } from "@paralleldrive/cuid2"; +import { createInsertSchema } from "drizzle-typebox"; +import { t } from "elysia"; + +export const user = pgTable("user", { + id: varchar("id") + .$defaultFn(() => createId()) + .primaryKey(), + username: varchar("username").notNull().unique(), + password: varchar("password").notNull(), + email: varchar("email").notNull().unique(), + salt: varchar("salt", { length: 64 }).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}); + +const _createUser = createInsertSchema(user, { + email: t.String({ format: "email" }), +}); + +// ✅ This works, by referencing the type from `drizzle-typebox` +export const createUserType = t.Omit(_createUser, ["id", "salt", "createdAt"]); diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..eeef779 --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,4 @@ +import "dotenv/config"; +import { drizzle } from "drizzle-orm/node-postgres"; + +const db = drizzle(process.env.DATABASE_URL!); diff --git a/src/db/model.ts b/src/db/model.ts new file mode 100644 index 0000000..d50a294 --- /dev/null +++ b/src/db/model.ts @@ -0,0 +1,7 @@ +import { createUserType } from "../api/user/user.model"; + +export const db = { + insert: { + user: createUserType, + }, +}; diff --git a/src/db/schema.ts b/src/db/schema.ts new file mode 100644 index 0000000..4214f12 --- /dev/null +++ b/src/db/schema.ts @@ -0,0 +1,51 @@ +import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").notNull(), + image: text("image"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const session = pgTable("session", { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id), +}); + +export const account = pgTable("account", { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const verification = pgTable("verification", { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at"), + updatedAt: timestamp("updated_at"), +}); diff --git a/src/emails/otp.tsx b/src/emails/otp.tsx new file mode 100644 index 0000000..1c7e2eb --- /dev/null +++ b/src/emails/otp.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { Tailwind, Section, Text } from '@react-email/components' + +export default function OTPEmail({ otp }: { otp: number }) { + return ( + +

+
+ + Verify your Email Address + + + Use the following code to verify your email address + + {otp} + + This code is valid for 10 minutes + + + Thank you joining us + +
+
+ + ) +} + +OTPEmail.PreviewProps = { + otp: 123456 +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5ad5897..11fd30f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,8 +3,9 @@ import { swagger } from "@elysiajs/swagger"; import { opentelemetry } from "@elysiajs/opentelemetry"; import { serverTiming } from "@elysiajs/server-timing"; -import { note } from "./routes/note"; -import { user } from "./routes/user"; +import { note } from "./api/note/note.route"; +import { betterAuthView } from "./lib/auth/auth-view"; +import { userMiddleware, userInfo } from "./middlewares/auth-middleware"; const app = new Elysia() .use(opentelemetry()) @@ -14,6 +15,9 @@ const app = new Elysia() if (code === "NOT_FOUND") return "Not Found :("; console.error(error); }) - .use(user) .use(note) - .listen(3000); + .derive(({ request }) => userMiddleware(request)) + .all("/api/auth/*", betterAuthView) + .get("/user", ({ user, session }) => userInfo(user, session)); + +app.listen(3000); diff --git a/src/lib/auth/auth-view.ts b/src/lib/auth/auth-view.ts new file mode 100644 index 0000000..5bca005 --- /dev/null +++ b/src/lib/auth/auth-view.ts @@ -0,0 +1,12 @@ +import { Context, Elysia } from "elysia"; +import { auth } from "./auth"; + +export const betterAuthView = (context: Context) => { + const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"]; + // validate request method + if (BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) { + return auth.handler(context.request); + } else { + context.error(405); + } +}; diff --git a/src/lib/auth/auth.ts b/src/lib/auth/auth.ts new file mode 100644 index 0000000..dc116b6 --- /dev/null +++ b/src/lib/auth/auth.ts @@ -0,0 +1,26 @@ +import { betterAuth } from "better-auth"; +import { drizzleAdapter } from "better-auth/adapters/drizzle"; +import { db } from "../../db/model"; +export const auth = betterAuth({ + database: drizzleAdapter(db, { + // We're using Drizzle as our database + provider: "pg", + }), + emailAndPassword: { + enabled: true, // If you want to use email and password auth + }, + socialProviders: { + /* + * We're using Google and Github as our social provider, + * make sure you have set your environment variables + */ + // github: { + // clientId: process.env.GITHUB_CLIENT_ID!, + // clientSecret: process.env.GITHUB_CLIENT_SECRET!, + // }, + // google: { + // clientId: process.env.GOOGLE_CLIENT_ID!, + // clientSecret: process.env.GOOGLE_CLIENT_SECRET!, + // }, + }, +}); diff --git a/src/middlewares/auth-middleware.ts b/src/middlewares/auth-middleware.ts new file mode 100644 index 0000000..1cadaaa --- /dev/null +++ b/src/middlewares/auth-middleware.ts @@ -0,0 +1,29 @@ +import { Session, User } from "better-auth/types"; +import { auth } from "../lib/auth/auth"; + +// user middleware (compute user and session and pass to routes) +export const userMiddleware = async (request: Request) => { + const session = await auth.api.getSession({ headers: request.headers }); + + if (!session) { + return { + user: null, + session: null, + }; + } + + return { + user: session.user, + session: session.session, + }; +}; + +// user info view +// type User can be export from `typeof auth.$Infer.Session.user` +// type Session can be export from `typeof auth.$Infer.Session.session` +export const userInfo = (user: User | null, session: Session | null) => { + return { + user: user, + session: session, + }; +}; diff --git a/src/routes/note.ts b/src/routes/note.ts deleted file mode 100644 index 3ba2538..0000000 --- a/src/routes/note.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Elysia, t } from "elysia"; -import { getUserId, userService } from "./user"; - -const memo = t.Object({ - data: t.String(), - author: t.String(), -}); - -type Memo = typeof memo.static; - -class Note { - constructor( - public data: Memo[] = [ - { - data: "Moonhalo", - author: "saltyaom", - }, - ] - ) {} - - add(note: Memo) { - this.data.push(note); - - return this.data; - } - - remove(index: number) { - return this.data.splice(index, 1); - } - - update(index: number, note: Partial) { - return (this.data[index] = { ...this.data[index], ...note }); - } -} - -export const note = new Elysia({ prefix: "/note" }) - .use(userService) - .decorate("note", new Note()) - .model({ - memo: t.Omit(memo, ["author"]), - }) - .onTransform(function log({ body, params, path, request: { method } }) { - console.log(`${method} ${path}`, { - body, - params, - }); - }) - .get("/", ({ note }) => note.data) - .use(getUserId) - .put( - "/", - ({ note, body: { data }, username }) => - note.add({ data, author: username }), - { - body: "memo", - } - ) - .get( - "/:index", - ({ note, params: { index }, error }) => { - return note.data[index] ?? error(404, "Not Found :("); - }, - { - params: t.Object({ - index: t.Number(), - }), - } - ) - .guard({ - params: t.Object({ - index: t.Number(), - }), - }) - .delete("/:index", ({ note, params: { index }, error }) => { - if (index in note.data) return note.remove(index); - - return error(422); - }) - .patch( - "/:index", - ({ note, params: { index }, body: { data }, error, username }) => { - if (index in note.data) - return note.update(index, { data, author: username }); - - return error(422); - }, - { - isSignIn: true, - body: "memo", - } - ); diff --git a/src/routes/user.ts b/src/routes/user.ts deleted file mode 100644 index b486ac4..0000000 --- a/src/routes/user.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Elysia, t } from "elysia"; - -export const userService = new Elysia({ name: "user/service" }) - .state({ - user: {} as Record, - session: {} as Record, - }) - .model({ - signIn: t.Object({ - username: t.String({ minLength: 1 }), - password: t.String({ minLength: 8 }), - }), - session: t.Cookie( - { - token: t.Number(), - }, - { - secrets: "seia", - } - ), - optionalSession: t.Optional(t.Ref("session")), - }) - .macro({ - isSignIn(enabled: boolean) { - if (!enabled) return; - - return { - beforeHandle({ error, cookie: { token }, store: { session } }) { - if (!token.value) - return error(401, { - success: false, - message: "Unauthorized", - }); - - const username = session[token.value as unknown as number]; - - if (!username) - return error(401, { - success: false, - message: "Unauthorized", - }); - }, - }; - }, - }); - -export const getUserId = new Elysia() - .use(userService) - .guard({ - isSignIn: true, - cookie: "session", - }) - .resolve(({ store: { session }, cookie: { token } }) => ({ - username: session[token.value], - })) - .as("plugin"); - -export const user = new Elysia({ prefix: "/user" }) - .use(userService) - .put( - "/sign-up", - async ({ body: { username, password }, store, error }) => { - if (store.user[username]) - return error(400, { - success: false, - message: "User already exists", - }); - - store.user[username] = await Bun.password.hash(password); - - return { - success: true, - message: "User created", - }; - }, - { - body: "signIn", - } - ) - .post( - "/sign-in", - async ({ - store: { user, session }, - error, - body: { username, password }, - cookie: { token }, - }) => { - if ( - !user[username] || - !(await Bun.password.verify(password, user[username])) - ) - return error(400, { - success: false, - message: "Invalid username or password", - }); - - const key = crypto.getRandomValues(new Uint32Array(1))[0]; - session[key] = username; - token.value = key; - - return { - success: true, - message: `Signed in as ${username}`, - }; - }, - { - body: "signIn", - cookie: "optionalSession", - } - ) - .get( - "/sign-out", - ({ cookie: { token } }) => { - token.remove(); - - return { - success: true, - message: "Signed out", - }; - }, - { - cookie: "optionalSession", - } - ) - .use(getUserId) - .get("/profile", ({ username }) => ({ - success: true, - username, - })); diff --git a/tsconfig.json b/tsconfig.json index 1ca2350..671e128 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ @@ -9,11 +8,10 @@ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ - "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ + "jsx": "react", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ @@ -23,26 +21,25 @@ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ + "module": "ES2022", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ + "types": [ + "bun-types" + ], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ @@ -67,16 +64,14 @@ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -95,9 +90,8 @@ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } -} +} \ No newline at end of file