diff --git a/apps/file-proxy/.gitignore b/apps/file-proxy/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/apps/file-proxy/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/apps/file-proxy/README.md b/apps/file-proxy/README.md new file mode 100644 index 0000000..7c749f5 --- /dev/null +++ b/apps/file-proxy/README.md @@ -0,0 +1,15 @@ +# drive-file-proxy + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/apps/file-proxy/convex.ts b/apps/file-proxy/convex.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/file-proxy/files.ts b/apps/file-proxy/files.ts new file mode 100644 index 0000000..9f95da9 --- /dev/null +++ b/apps/file-proxy/files.ts @@ -0,0 +1,12 @@ +import { Hono } from "hono" + +const h = new Hono().basePath("/files") + +h.get("/:fileId", async (c) => { + const fileId = c.req.param("fileId") + if (!fileId) { + return c.json({ error: "File ID is required" }, 400) + } +}) + +export { h as files } diff --git a/apps/file-proxy/index.ts b/apps/file-proxy/index.ts new file mode 100644 index 0000000..6be95ea --- /dev/null +++ b/apps/file-proxy/index.ts @@ -0,0 +1,10 @@ +import { Hono } from "hono" +import { handleFileRequest } from "./files" + +Bun.serve({ + routes: { + "/files/:fileId": { + GET: handleFileRequest, + }, + }, +}) diff --git a/apps/file-proxy/package.json b/apps/file-proxy/package.json new file mode 100644 index 0000000..f4a9f31 --- /dev/null +++ b/apps/file-proxy/package.json @@ -0,0 +1,17 @@ +{ + "name": "@drexa/file-proxy", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@fileone/convex": "workspace:*", + "convex": "^1.28.0", + "hono": "^4.10.1" + } +} diff --git a/apps/file-proxy/router.ts b/apps/file-proxy/router.ts new file mode 100644 index 0000000..bf79c29 --- /dev/null +++ b/apps/file-proxy/router.ts @@ -0,0 +1,7 @@ +import type { RouterTypes } from "bun" + +function router< + R extends { [K in keyof R]: RouterTypes.RouteValue> }, +>(routes: R): R { + return routes +} diff --git a/apps/file-proxy/tsconfig.json b/apps/file-proxy/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/apps/file-proxy/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/bun.lock b/bun.lock index 7411864..961f31f 100644 --- a/bun.lock +++ b/bun.lock @@ -61,6 +61,29 @@ "vite": "^7.1.10", }, }, + "apps/file-proxy": { + "name": "@drexa/file-proxy", + "dependencies": { + "@fileone/convex": "workspace:*", + "convex": "^1.28.0", + "hono": "^4.10.1", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/auth": { + "name": "@drexa/auth", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, "packages/convex": { "name": "@fileone/convex", "dependencies": { @@ -169,6 +192,10 @@ "@convex-dev/better-auth": ["@convex-dev/better-auth@0.8.9", "", { "dependencies": { "@better-fetch/fetch": "^1.1.18", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "is-network-error": "^1.1.0", "type-fest": "^4.39.1", "zod": "^3.24.4" }, "peerDependencies": { "better-auth": "1.3.8", "convex": "^1.26.2", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-t6x2lYsgv0sGL14xmIsTqxVltkS//mWtIjb+Wm39rWUCgeqmCTqsyhQnmTDQNwaqZS0sH0WfTObNHu3xbCSx1w=="], + "@drexa/auth": ["@drexa/auth@workspace:packages/auth"], + + "@drexa/file-proxy": ["@drexa/file-proxy@workspace:apps/file-proxy"], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], @@ -585,6 +612,8 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "hono": ["hono@4.10.1", "", {}, "sha512-rpGNOfacO4WEPClfkEt1yfl8cbu10uB1lNpiI33AKoiAHwOS8lV748JiLx4b5ozO/u4qLjIvfpFsPXdY5Qjkmg=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], diff --git a/packages/auth/.gitignore b/packages/auth/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/packages/auth/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 0000000..a078b86 --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1,15 @@ +# @drexa/auth + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/packages/auth/index.ts b/packages/auth/index.ts new file mode 100644 index 0000000..c3d23ef --- /dev/null +++ b/packages/auth/index.ts @@ -0,0 +1,64 @@ +/** + * An unhashed api key. + * Always starts with sk, then the prefix specified at time of generation, + * and ends with the base64url-encoded key. + */ +export type UnhashedApiKey = `sk-${ApiKeyPrefix}-${string}` + +export type ApiKeyPrefix = string & { __brand: "ApiKeyPrefix" } + +export type GenerateApiKeyOptions = { + /** + * How long the key should be (excluding prefix) in bytes. + */ + keyByteLength: number + + /** + * Prefix of the api key. Can be a brief name of the consumer of the key. + * For example, if the prefix is "proxy", the key will be "sk-proxy-asdasjdjsdkjd.." + */ + prefix: ApiKeyPrefix + + expiresAt?: Date + description: string +} + +export type GenerateApiKeyResult = { + unhashedKey: UnhashedApiKey + hashedKey: string + expiresAt?: Date + description: string +} + +export function newPrefix(prefix: string): ApiKeyPrefix | null { + if (prefix.includes("-")) { + return null + } + return prefix as ApiKeyPrefix +} + +export async function generateApiKey({ + keyByteLength, + prefix, + expiresAt, + description, +}: GenerateApiKeyOptions): Promise { + const keyContent = new Uint8Array(keyByteLength) + crypto.getRandomValues(keyContent) + + const base64KeyContent = Buffer.from(keyContent).toString("base64url") + const unhashedKey: UnhashedApiKey = `sk-${prefix}-${base64KeyContent}` + + const hashedKey = await Bun.password.hash(unhashedKey, { + algorithm: "argon2id", + memoryCost: 4, // memory usage in kibibytes + timeCost: 3, // the number of iterations + }) + + return { + unhashedKey, + hashedKey, + expiresAt, + description, + } +} diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 0000000..2146dc0 --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,11 @@ +{ + "name": "@drexa/auth", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}