From fed3ac1b61cf6dc75f5c305264f9dac0d6d5a973 Mon Sep 17 00:00:00 2001 From: Shotaro Nakamura Date: Sun, 21 Jun 2026 21:37:07 +0900 Subject: [PATCH] feat: introduce mnie --- .env.example | 26 +- README.md | 35 +- apps/csbie/Dockerfile | 26 - apps/mnie-app/Dockerfile | 29 + apps/{csbie => mnie-app}/package.json | 8 +- apps/{csbie => mnie-app}/src/dev.ts | 12 +- apps/{csbie => mnie-app}/src/index.ts | 10 +- apps/{csbie-server => mnie-app}/tsconfig.json | 0 .../drizzle.config.ts | 2 +- .../package.json | 4 +- apps/{csbie-server => mnie-server}/src/app.ts | 4 +- .../src/config.ts | 14 +- .../src/context.ts | 1 + .../src/db/index.ts | 0 .../src/db/schema.ts | 1 + .../src/index.ts | 2 +- .../src/routes/admin.ts | 2 +- .../src/routes/auth.ts | 23 +- .../src/routes/mcp.ts | 17 +- .../src/routes/oauth.ts | 6 +- .../src/rpc/methods.ts | 2 +- .../src/rpc/sbi-session.ts | 4 +- .../src/rpc/ws.ts | 13 +- .../src/security/api-keys.ts | 10 +- .../src/security/crypto.ts | 0 .../src/security/http-auth.ts | 7 +- .../src/security/keyring.ts | 16 +- .../src/security/oauth-provider.ts | 29 +- .../src/security/sbi-credentials.ts | 0 .../src/security/sessions.ts | 0 .../src/security/setup.ts | 4 +- .../src/security/trade-limits.ts | 0 apps/{csbie => mnie-server}/tsconfig.json | 0 apps/{csbie-ui => mnie-ui}/AGENTS.md | 0 apps/{csbie-ui => mnie-ui}/index.html | 2 +- apps/{csbie-ui => mnie-ui}/package.json | 2 +- apps/{csbie-ui => mnie-ui}/src/App.vue | 5 +- apps/{csbie-ui => mnie-ui}/src/api.ts | 2 + .../src/assets/search-empty.png | Bin .../src/components/ApiKeyPolicyEditor.vue | 21 + .../src/components/layout/AppHeader.vue | 0 .../src/components/layout/AppSidebar.vue | 2 +- .../src/components/ui/Spinner.vue | 0 .../src/components/ui/UiButton.vue | 0 .../src/components/ui/UiCard.vue | 0 .../src/components/ui/UiField.vue | 0 .../src/components/ui/UiModal.vue | 0 .../src/components/ui/UiSegmented.vue | 0 .../src/constants/market.ts | 0 .../src/constants/motion.ts | 0 .../src/constants/nav.ts | 0 .../src/constants/trade.ts | 0 .../src/features/auth/AuthGate.vue | 0 .../src/features/auth/api-key-policy.ts | 1 + .../src/features/auth/useAuthAdmin.ts | 0 .../src/features/history/HistoryView.vue | 0 .../src/features/oauth/OAuthApprovalPanel.vue | 0 .../src/features/oauth/useOAuthApproval.ts | 2 + .../src/features/orders/OrderDialogs.vue | 0 .../src/features/portfolio/PortfolioView.vue | 0 .../src/features/search/SearchDialog.vue | 0 .../src/features/settings/SettingsView.vue | 0 .../src/features/trade/RealtimePriceChart.vue | 0 .../src/features/trade/TradeView.vue | 0 .../src/features/trading/trading-data.ts | 0 .../src/features/trading/useTradingSession.ts | 4 +- apps/{csbie-ui => mnie-ui}/src/main.ts | 0 apps/{csbie-ui => mnie-ui}/src/router.ts | 0 apps/{csbie-ui => mnie-ui}/src/style.css | 0 apps/{csbie-ui => mnie-ui}/src/styles/ui.ts | 0 .../src/types/trading.ts | 0 .../{csbie-ui => mnie-ui}/src/utils/format.ts | 0 apps/{csbie-ui => mnie-ui}/tsconfig.json | 0 apps/{csbie-ui => mnie-ui}/vite.config.ts | 8 +- bun.lock | 72 +- compose.yml | 6 +- flake.nix | 70 +- package.json | 2 +- packages/client-sbi/README.md | 5 + .../{sbi-client => client-sbi}/package.json | 3 +- .../{sbi-client => client-sbi}/src/index.ts | 1 + .../{sbi-client => client-sbi}/src/markets.ts | 0 .../src/methods/error-map.ts | 0 .../src/methods/index.ts | 0 packages/client-sbi/src/methods/types.ts | 1 + .../src/methods/us-stock.ts | 0 .../src/session/index.ts | 2 +- .../{sbi-client => client-sbi}/src/types.ts | 0 .../{sbi-client => client-sbi}/tsconfig.json | 0 packages/mnie-cli/package.json | 22 + packages/mnie-cli/src/index.ts | 312 ++++++ packages/mnie-cli/tsconfig.json | 4 + packages/mnie-sdk/package.json | 26 + packages/mnie-sdk/src/index.ts | 113 +++ packages/mnie-sdk/tsconfig.json | 4 + packages/mnie-types/package.json | 22 + packages/mnie-types/src/index.ts | 2 + .../types.ts => mnie-types/src/methods.ts} | 2 +- packages/mnie-types/src/types.ts | 951 ++++++++++++++++++ packages/mnie-types/tsconfig.json | 4 + packages/sbi-client/README.md | 15 - scripts/check-env.ts | 20 +- scripts/mock-create.js | 2 +- vite.config.ts | 16 +- 104 files changed, 1815 insertions(+), 218 deletions(-) delete mode 100644 apps/csbie/Dockerfile create mode 100644 apps/mnie-app/Dockerfile rename apps/{csbie => mnie-app}/package.json (56%) rename apps/{csbie => mnie-app}/src/dev.ts (60%) rename apps/{csbie => mnie-app}/src/index.ts (87%) rename apps/{csbie-server => mnie-app}/tsconfig.json (100%) rename apps/{csbie-server => mnie-server}/drizzle.config.ts (71%) rename apps/{csbie-server => mnie-server}/package.json (90%) rename apps/{csbie-server => mnie-server}/src/app.ts (95%) rename apps/{csbie-server => mnie-server}/src/config.ts (81%) rename apps/{csbie-server => mnie-server}/src/context.ts (95%) rename apps/{csbie-server => mnie-server}/src/db/index.ts (100%) rename apps/{csbie-server => mnie-server}/src/db/schema.ts (98%) rename apps/{csbie-server => mnie-server}/src/index.ts (83%) rename apps/{csbie-server => mnie-server}/src/routes/admin.ts (97%) rename apps/{csbie-server => mnie-server}/src/routes/auth.ts (91%) rename apps/{csbie-server => mnie-server}/src/routes/mcp.ts (98%) rename apps/{csbie-server => mnie-server}/src/routes/oauth.ts (88%) rename apps/{csbie-server => mnie-server}/src/rpc/methods.ts (98%) rename apps/{csbie-server => mnie-server}/src/rpc/sbi-session.ts (93%) rename apps/{csbie-server => mnie-server}/src/rpc/ws.ts (93%) rename apps/{csbie-server => mnie-server}/src/security/api-keys.ts (92%) rename apps/{csbie-server => mnie-server}/src/security/crypto.ts (100%) rename apps/{csbie-server => mnie-server}/src/security/http-auth.ts (88%) rename apps/{csbie-server => mnie-server}/src/security/keyring.ts (89%) rename apps/{csbie-server => mnie-server}/src/security/oauth-provider.ts (89%) rename apps/{csbie-server => mnie-server}/src/security/sbi-credentials.ts (100%) rename apps/{csbie-server => mnie-server}/src/security/sessions.ts (100%) rename apps/{csbie-server => mnie-server}/src/security/setup.ts (68%) rename apps/{csbie-server => mnie-server}/src/security/trade-limits.ts (100%) rename apps/{csbie => mnie-server}/tsconfig.json (100%) rename apps/{csbie-ui => mnie-ui}/AGENTS.md (100%) rename apps/{csbie-ui => mnie-ui}/index.html (91%) rename apps/{csbie-ui => mnie-ui}/package.json (95%) rename apps/{csbie-ui => mnie-ui}/src/App.vue (98%) rename apps/{csbie-ui => mnie-ui}/src/api.ts (98%) rename apps/{csbie-ui => mnie-ui}/src/assets/search-empty.png (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ApiKeyPolicyEditor.vue (90%) rename apps/{csbie-ui => mnie-ui}/src/components/layout/AppHeader.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/layout/AppSidebar.vue (95%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/Spinner.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/UiButton.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/UiCard.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/UiField.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/UiModal.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/components/ui/UiSegmented.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/constants/market.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/constants/motion.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/constants/nav.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/constants/trade.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/features/auth/AuthGate.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/auth/api-key-policy.ts (92%) rename apps/{csbie-ui => mnie-ui}/src/features/auth/useAuthAdmin.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/features/history/HistoryView.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/oauth/OAuthApprovalPanel.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/oauth/useOAuthApproval.ts (93%) rename apps/{csbie-ui => mnie-ui}/src/features/orders/OrderDialogs.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/portfolio/PortfolioView.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/search/SearchDialog.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/settings/SettingsView.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/trade/RealtimePriceChart.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/trade/TradeView.vue (100%) rename apps/{csbie-ui => mnie-ui}/src/features/trading/trading-data.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/features/trading/useTradingSession.ts (99%) rename apps/{csbie-ui => mnie-ui}/src/main.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/router.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/style.css (100%) rename apps/{csbie-ui => mnie-ui}/src/styles/ui.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/types/trading.ts (100%) rename apps/{csbie-ui => mnie-ui}/src/utils/format.ts (100%) rename apps/{csbie-ui => mnie-ui}/tsconfig.json (100%) rename apps/{csbie-ui => mnie-ui}/vite.config.ts (69%) create mode 100644 packages/client-sbi/README.md rename packages/{sbi-client => client-sbi}/package.json (86%) rename packages/{sbi-client => client-sbi}/src/index.ts (96%) rename packages/{sbi-client => client-sbi}/src/markets.ts (100%) rename packages/{sbi-client => client-sbi}/src/methods/error-map.ts (100%) rename packages/{sbi-client => client-sbi}/src/methods/index.ts (100%) create mode 100644 packages/client-sbi/src/methods/types.ts rename packages/{sbi-client => client-sbi}/src/methods/us-stock.ts (100%) rename packages/{sbi-client => client-sbi}/src/session/index.ts (99%) rename packages/{sbi-client => client-sbi}/src/types.ts (100%) rename packages/{sbi-client => client-sbi}/tsconfig.json (100%) create mode 100644 packages/mnie-cli/package.json create mode 100644 packages/mnie-cli/src/index.ts create mode 100644 packages/mnie-cli/tsconfig.json create mode 100644 packages/mnie-sdk/package.json create mode 100644 packages/mnie-sdk/src/index.ts create mode 100644 packages/mnie-sdk/tsconfig.json create mode 100644 packages/mnie-types/package.json create mode 100644 packages/mnie-types/src/index.ts rename packages/{sbi-client/src/methods/types.ts => mnie-types/src/methods.ts} (99%) create mode 100644 packages/mnie-types/src/types.ts create mode 100644 packages/mnie-types/tsconfig.json delete mode 100644 packages/sbi-client/README.md diff --git a/.env.example b/.env.example index 9079d89..daebb51 100644 --- a/.env.example +++ b/.env.example @@ -2,21 +2,21 @@ # Local development PORT=8787 -CSBIE_SERVER_PORT=8787 -CSBIE_UI_DEV_PORT=5173 -CSBIE_DATABASE_PATH=./data/csbie.sqlite -CSBIE_KEYRING_BACKEND=platform -CSBIE_KEYRING_SQLITE_PATH=./data/csbie.keyring.sqlite -CSBIE_KEYRING_SECRET= -CSBIE_ORIGIN=http://127.0.0.1:5173 -CSBIE_CORS_ORIGIN=http://127.0.0.1:5173 -CSBIE_SESSION_COOKIE=csbie_session -CSBIE_RP_NAME=CSBIE -CSBIE_RP_ID=127.0.0.1 +MNIE_SERVER_PORT=8787 +MNIE_UI_DEV_PORT=5173 +MNIE_DATABASE_PATH=./data/mnie-app.sqlite +MNIE_KEYRING_BACKEND=platform +MNIE_KEYRING_SQLITE_PATH=./data/mnie-app.keyring.sqlite +MNIE_KEYRING_SECRET= +MNIE_ORIGIN=http://127.0.0.1:5173 +MNIE_CORS_ORIGIN=http://127.0.0.1:5173 +MNIE_SESSION_COOKIE=mnie_session +MNIE_RP_NAME=MNIE +MNIE_RP_ID=127.0.0.1 # Initial owner passkey setup. Prefer the hash form outside throwaway local dev. -CSBIE_SETUP_PASSWORD= -CSBIE_SETUP_PASSWORD_HASH= +MNIE_SETUP_PASSWORD= +MNIE_SETUP_PASSWORD_HASH= # SBI endpoints and local SBI session options. Values are loaded at runtime only. SBI_AUTH_BASE_URL= diff --git a/README.md b/README.md index 6147f8e..351c28a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,25 @@ -# cSBI +# Mnie [![技術者倫理 遵守済み](https://img.shields.io/badge/%E6%8A%80%E8%A1%93%E8%80%85%E5%80%AB%E7%90%86-%E9%81%B5%E5%AE%88%E6%B8%88%E3%81%BF-0a0a0a?style=for-the-badge&labelColor=ffffff)](https://技術者倫理.com) -A client for SBI-compatible servers. This project is not intended for use with a real SBI server. +Self-host finance management for running your own portfolio, trading, API key, OAuth, MCP, and CLI workflows. -## cSBI SDK +## Mnie SDK -## cSBIe +`@repo/mnie-sdk` connects to a Mnie server with an API key and exposes typed finance methods from `@repo/mnie-types`. -cSBIe is a web ui and server for managing SBI-compatible API. +## Apps + +- `apps/mnie-app`: bundled server plus UI runtime +- `apps/mnie-server`: API, OAuth, MCP, RPC, storage, and provider session management +- `apps/mnie-ui`: web UI + +## Packages + +- `packages/mnie-types`: shared finance SDK interfaces +- `packages/mnie-sdk`: API-key SDK for Mnie +- `packages/mnie-cli`: CLI using `mnie-sdk` +- `packages/client-sbi`: SBI provider client ### Install @@ -16,15 +27,15 @@ Before running, setup .env, refering `.env.example`. #### With docker -Docker uses `CSBIE_KEYRING_BACKEND=sqlite` because platform keyrings are not available in -headless containers. Set `CSBIE_KEYRING_SECRET` in `.env` before saving SBI passkeys. +Docker uses `MNIE_KEYRING_BACKEND=sqlite` because platform keyrings are not available in +headless containers. Set `MNIE_KEYRING_SECRET` in `.env` before saving SBI passkeys. docker-compose.yml: ```yaml services: - csbie: - image: git.yutakobayashi.com/nakasyou/csbi:latest + mnie: + image: git.yutakobayashi.com/nakasyou/mnie:latest ports: - '18787:8787' env_file: @@ -36,15 +47,15 @@ services: or ```bash -docker run --rm -it --name csbie \ +docker run --rm -it --name mnie \ -p 18787:8787 \ --env-file .env \ -v "$PWD/data:/app/data" \ - git.yutakobayashi.com/nakasyou/csbi:latest + git.yutakobayashi.com/nakasyou/mnie:latest ``` #### With Nix ```bash -nix run 'git+https://git.yutakobayashi.com/nakasyou/csbi.git#csbie' +nix run 'git+https://git.yutakobayashi.com/nakasyou/mnie.git#mnie' ``` diff --git a/apps/csbie/Dockerfile b/apps/csbie/Dockerfile deleted file mode 100644 index a1f9a35..0000000 --- a/apps/csbie/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM oven/bun:1 AS deps -WORKDIR /app -COPY package.json bun.lock tsconfig.json vite.config.ts ./ -COPY packages/sbi-client/package.json packages/sbi-client/package.json -COPY apps/csbie/package.json apps/csbie/package.json -COPY apps/csbie-server/package.json apps/csbie-server/package.json -COPY apps/csbie-ui/package.json apps/csbie-ui/package.json -RUN bun install --frozen-lockfile - -FROM deps AS build -COPY . . -RUN bun --filter @repo/csbie build - -FROM oven/bun:1-slim AS runtime -ENV NODE_ENV=production -ENV CSBIE_DATABASE_PATH=/app/data/csbie.sqlite -ENV CSBIE_KEYRING_BACKEND=sqlite -ENV CSBIE_KEYRING_SQLITE_PATH=/app/data/csbie.keyring.sqlite -WORKDIR /app -COPY --from=build /app/apps/csbie/dist ./apps/csbie/dist -COPY --from=build /app/apps/csbie-ui/dist ./apps/csbie-ui/dist -COPY --from=build /app/node_modules/.bun/@napi-rs+keyring@*/node_modules/@napi-rs/keyring ./node_modules/@napi-rs/keyring -COPY --from=build /app/node_modules/.bun/@napi-rs+keyring-linux-x64-gnu@*/node_modules/@napi-rs/keyring-linux-x64-gnu ./node_modules/@napi-rs/keyring-linux-x64-gnu -WORKDIR /app/apps/csbie/dist -EXPOSE 8787 -CMD ["bun", "index.js"] diff --git a/apps/mnie-app/Dockerfile b/apps/mnie-app/Dockerfile new file mode 100644 index 0000000..1195ef3 --- /dev/null +++ b/apps/mnie-app/Dockerfile @@ -0,0 +1,29 @@ +FROM oven/bun:1 AS deps +WORKDIR /app +COPY package.json bun.lock tsconfig.json vite.config.ts ./ +COPY packages/client-sbi/package.json packages/client-sbi/package.json +COPY packages/mnie-types/package.json packages/mnie-types/package.json +COPY packages/mnie-sdk/package.json packages/mnie-sdk/package.json +COPY packages/mnie-cli/package.json packages/mnie-cli/package.json +COPY apps/mnie-app/package.json apps/mnie-app/package.json +COPY apps/mnie-server/package.json apps/mnie-server/package.json +COPY apps/mnie-ui/package.json apps/mnie-ui/package.json +RUN bun install --frozen-lockfile + +FROM deps AS build +COPY . . +RUN bun --filter @repo/mnie-app build + +FROM oven/bun:1-slim AS runtime +ENV NODE_ENV=production +ENV MNIE_DATABASE_PATH=/app/data/mnie-app.sqlite +ENV MNIE_KEYRING_BACKEND=sqlite +ENV MNIE_KEYRING_SQLITE_PATH=/app/data/mnie-app.keyring.sqlite +WORKDIR /app +COPY --from=build /app/apps/mnie-app/dist ./apps/mnie-app/dist +COPY --from=build /app/apps/mnie-ui/dist ./apps/mnie-ui/dist +COPY --from=build /app/node_modules/.bun/@napi-rs+keyring@*/node_modules/@napi-rs/keyring ./node_modules/@napi-rs/keyring +COPY --from=build /app/node_modules/.bun/@napi-rs+keyring-linux-x64-gnu@*/node_modules/@napi-rs/keyring-linux-x64-gnu ./node_modules/@napi-rs/keyring-linux-x64-gnu +WORKDIR /app/apps/mnie-app/dist +EXPOSE 8787 +CMD ["bun", "index.js"] diff --git a/apps/csbie/package.json b/apps/mnie-app/package.json similarity index 56% rename from apps/csbie/package.json rename to apps/mnie-app/package.json index 7cdc07e..6b8fd39 100644 --- a/apps/csbie/package.json +++ b/apps/mnie-app/package.json @@ -1,16 +1,16 @@ { - "name": "@repo/csbie", + "name": "@repo/mnie-app", "private": true, "type": "module", "scripts": { - "clean": "rm -rf dist ../csbie-ui/dist", + "clean": "rm -rf dist ../mnie-ui/dist", "dev": "bun --env-file=../../.env src/dev.ts", - "build": "bun --filter @repo/csbie-ui build && bun build src/index.ts --target=bun --outdir=dist --external @napi-rs/keyring", + "build": "bun --filter @repo/mnie-ui build && bun build src/index.ts --target=bun --outdir=dist --external @napi-rs/keyring", "start": "bun --env-file=../../.env dist/index.js", "typecheck": "tsc" }, "dependencies": { - "@repo/csbie-server": "workspace:*", + "@repo/mnie-server": "workspace:*", "hono": "^4.8.3" }, "devDependencies": { diff --git a/apps/csbie/src/dev.ts b/apps/mnie-app/src/dev.ts similarity index 60% rename from apps/csbie/src/dev.ts rename to apps/mnie-app/src/dev.ts index b07b3a6..ff491d1 100644 --- a/apps/csbie/src/dev.ts +++ b/apps/mnie-app/src/dev.ts @@ -1,10 +1,10 @@ -const uiPort = Number(process.env.CSBIE_UI_DEV_PORT ?? 5173) -const serverPort = Number(process.env.PORT ?? process.env.CSBIE_SERVER_PORT ?? 8787) +const uiPort = Number(process.env.MNIE_UI_DEV_PORT ?? 5173) +const serverPort = Number(process.env.PORT ?? process.env.MNIE_SERVER_PORT ?? 8787) const uiOrigin = `http://127.0.0.1:${uiPort}` -const serverEntry = new URL('../../csbie-server/src/index.ts', import.meta.url).pathname +const serverEntry = new URL('../../mnie-server/src/index.ts', import.meta.url).pathname const ui = Bun.spawn({ - cmd: ['bun', '--filter', '@repo/csbie-ui', 'dev'], + cmd: ['bun', '--filter', '@repo/mnie-ui', 'dev'], stdout: 'inherit', stderr: 'inherit', stdin: 'inherit', @@ -19,8 +19,8 @@ const server = Bun.spawn({ env: { ...process.env, PORT: String(serverPort), - CSBIE_ORIGIN: uiOrigin, - CSBIE_CORS_ORIGIN: uiOrigin, + MNIE_ORIGIN: uiOrigin, + MNIE_CORS_ORIGIN: uiOrigin, }, }) diff --git a/apps/csbie/src/index.ts b/apps/mnie-app/src/index.ts similarity index 87% rename from apps/csbie/src/index.ts rename to apps/mnie-app/src/index.ts index bea8d2f..f54ba92 100644 --- a/apps/csbie/src/index.ts +++ b/apps/mnie-app/src/index.ts @@ -1,15 +1,15 @@ import { existsSync } from 'node:fs' import { extname, isAbsolute, join, relative, resolve } from 'node:path' import { Hono } from 'hono' -import { createServerApp } from '@repo/csbie-server/app' -import { loadConfig } from '@repo/csbie-server/config' -import { createDb } from '@repo/csbie-server/db' +import { createServerApp } from '@repo/mnie-server/app' +import { loadConfig } from '@repo/mnie-server/config' +import { createDb } from '@repo/mnie-server/db' const config = loadConfig() const db = createDb(config.databasePath) const api = createServerApp(db, config) const app = new Hono() -const uiDist = resolve(import.meta.dir, '../../csbie-ui/dist') +const uiDist = resolve(import.meta.dir, '../../mnie-ui/dist') const contentTypes: Record = { '.css': 'text/css; charset=utf-8', @@ -63,4 +63,4 @@ const server = Bun.serve({ websocket: api.websocket, }) -console.log(`csbie listening on http://localhost:${server.port}`) +console.log(`mnie listening on http://localhost:${server.port}`) diff --git a/apps/csbie-server/tsconfig.json b/apps/mnie-app/tsconfig.json similarity index 100% rename from apps/csbie-server/tsconfig.json rename to apps/mnie-app/tsconfig.json diff --git a/apps/csbie-server/drizzle.config.ts b/apps/mnie-server/drizzle.config.ts similarity index 71% rename from apps/csbie-server/drizzle.config.ts rename to apps/mnie-server/drizzle.config.ts index 283c02a..07d0c4d 100644 --- a/apps/csbie-server/drizzle.config.ts +++ b/apps/mnie-server/drizzle.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ out: './drizzle', dialect: 'sqlite', dbCredentials: { - url: process.env.CSBIE_DATABASE_PATH ?? './data/csbie.sqlite', + url: process.env.MNIE_DATABASE_PATH ?? './data/mnie-app.sqlite', }, }) diff --git a/apps/csbie-server/package.json b/apps/mnie-server/package.json similarity index 90% rename from apps/csbie-server/package.json rename to apps/mnie-server/package.json index c18674e..ade7b5b 100644 --- a/apps/csbie-server/package.json +++ b/apps/mnie-server/package.json @@ -1,5 +1,5 @@ { - "name": "@repo/csbie-server", + "name": "@repo/mnie-server", "private": true, "type": "module", "exports": { @@ -17,7 +17,7 @@ "@hono/mcp": "^0.3.0", "@modelcontextprotocol/sdk": "^1.29.0", "@napi-rs/keyring": "^1.2.0", - "@repo/sbi-client": "workspace:*", + "@repo/client-sbi": "workspace:*", "@simplewebauthn/server": "^13.1.2", "drizzle-orm": "^0.44.2", "hono": "^4.8.3", diff --git a/apps/csbie-server/src/app.ts b/apps/mnie-server/src/app.ts similarity index 95% rename from apps/csbie-server/src/app.ts rename to apps/mnie-server/src/app.ts index 5a120a9..d5bf338 100644 --- a/apps/csbie-server/src/app.ts +++ b/apps/mnie-server/src/app.ts @@ -48,8 +48,8 @@ export const createServerApp = (db: Db, config: ServerConfig) => { issuerUrl: new URL(config.origin), baseUrl: new URL(config.origin), resourceServerUrl: new URL('/api/mcp', config.origin), - resourceName: 'CSBIE MCP', - scopesSupported: ['mcp'], + resourceName: 'Mnie finance management', + scopesSupported: ['read', 'write', 'trade', 'mcp'], provider: oauthProvider, authorizationOptions: { rateLimit: false }, tokenOptions: { rateLimit: false }, diff --git a/apps/csbie-server/src/config.ts b/apps/mnie-server/src/config.ts similarity index 81% rename from apps/csbie-server/src/config.ts rename to apps/mnie-server/src/config.ts index aa40990..cbfd854 100644 --- a/apps/csbie-server/src/config.ts +++ b/apps/mnie-server/src/config.ts @@ -32,19 +32,19 @@ const optionalUrl = (value: string | undefined) => { } export const loadConfig = (): ServerConfig => { - const port = Number(process.env.PORT ?? process.env.CSBIE_SERVER_PORT ?? 8787) - const databasePath = resolve(process.env.CSBIE_DATABASE_PATH ?? './data/csbie.sqlite') + const port = Number(process.env.PORT ?? process.env.MNIE_SERVER_PORT ?? 8787) + const databasePath = resolve(process.env.MNIE_DATABASE_PATH ?? './data/mnie-app.sqlite') mkdirSync(dirname(databasePath), { recursive: true }) - const origin = process.env.CSBIE_ORIGIN ?? `http://localhost:${port}` - const rpId = process.env.CSBIE_RP_ID ?? new URL(origin).hostname + const origin = process.env.MNIE_ORIGIN ?? `http://localhost:${port}` + const rpId = process.env.MNIE_RP_ID ?? new URL(origin).hostname return { port, databasePath, - corsOrigin: process.env.CSBIE_CORS_ORIGIN ?? origin, - sessionCookieName: process.env.CSBIE_SESSION_COOKIE ?? 'csbie_session', - rpName: process.env.CSBIE_RP_NAME ?? 'CSBIE', + corsOrigin: process.env.MNIE_CORS_ORIGIN ?? origin, + sessionCookieName: process.env.MNIE_SESSION_COOKIE ?? 'mnie_session', + rpName: process.env.MNIE_RP_NAME ?? 'MNIE', rpId, origin, authBaseUrl: optionalUrl(process.env.SBI_AUTH_BASE_URL), diff --git a/apps/csbie-server/src/context.ts b/apps/mnie-server/src/context.ts similarity index 95% rename from apps/csbie-server/src/context.ts rename to apps/mnie-server/src/context.ts index fb667fa..4cc0cdd 100644 --- a/apps/csbie-server/src/context.ts +++ b/apps/mnie-server/src/context.ts @@ -24,4 +24,5 @@ export type AuthContext = type: 'apiKey' authenticated: true apiKeyId: string + scopes: string[] } diff --git a/apps/csbie-server/src/db/index.ts b/apps/mnie-server/src/db/index.ts similarity index 100% rename from apps/csbie-server/src/db/index.ts rename to apps/mnie-server/src/db/index.ts diff --git a/apps/csbie-server/src/db/schema.ts b/apps/mnie-server/src/db/schema.ts similarity index 98% rename from apps/csbie-server/src/db/schema.ts rename to apps/mnie-server/src/db/schema.ts index 35d6fa1..f1e1336 100644 --- a/apps/csbie-server/src/db/schema.ts +++ b/apps/mnie-server/src/db/schema.ts @@ -50,6 +50,7 @@ export const apiKeys = sqliteTable( maxOrderPriceJpy: integer('max_order_price_jpy'), maxOrderAmountJpy: integer('max_order_amount_jpy'), allowedMethods: text('allowed_methods', { mode: 'json' }).$type(), + scopes: text('scopes', { mode: 'json' }).$type(), createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), lastUsedAt: integer('last_used_at', { mode: 'timestamp_ms' }), revokedAt: integer('revoked_at', { mode: 'timestamp_ms' }), diff --git a/apps/csbie-server/src/index.ts b/apps/mnie-server/src/index.ts similarity index 83% rename from apps/csbie-server/src/index.ts rename to apps/mnie-server/src/index.ts index e23d946..28b4eac 100644 --- a/apps/csbie-server/src/index.ts +++ b/apps/mnie-server/src/index.ts @@ -14,4 +14,4 @@ const server = Bun.serve({ websocket, }) -console.log(`csbie-server listening on http://localhost:${server.port}`) +console.log(`mnie-server listening on http://localhost:${server.port}`) diff --git a/apps/csbie-server/src/routes/admin.ts b/apps/mnie-server/src/routes/admin.ts similarity index 97% rename from apps/csbie-server/src/routes/admin.ts rename to apps/mnie-server/src/routes/admin.ts index 6c3d72c..ed61567 100644 --- a/apps/csbie-server/src/routes/admin.ts +++ b/apps/mnie-server/src/routes/admin.ts @@ -1,7 +1,7 @@ import { eq } from 'drizzle-orm' import { Hono } from 'hono' import type { MiddlewareHandler } from 'hono' -import type { PlaintextStoredWebAuthnCredential } from '@repo/sbi-client' +import type { PlaintextStoredWebAuthnCredential } from '@repo/client-sbi' import type { AppBindings } from '../context' import { sbiPasskeys } from '../db/schema' import { diff --git a/apps/csbie-server/src/routes/auth.ts b/apps/mnie-server/src/routes/auth.ts similarity index 91% rename from apps/csbie-server/src/routes/auth.ts rename to apps/mnie-server/src/routes/auth.ts index b78e09b..0c8458c 100644 --- a/apps/csbie-server/src/routes/auth.ts +++ b/apps/mnie-server/src/routes/auth.ts @@ -74,6 +74,21 @@ const passkeyForCredential = async (db: AppBindings['Variables']['db'], credenti return row } +const loopbackHosts = new Set(['localhost', '127.0.0.1']) + +const expectedWebAuthnOrigins = (origin: string) => { + const url = new URL(origin) + if (!loopbackHosts.has(url.hostname)) return origin + return [...loopbackHosts].map((hostname) => { + const candidate = new URL(origin) + candidate.hostname = hostname + return candidate.origin + }) +} + +const expectedWebAuthnRpIds = (rpId: string) => + loopbackHosts.has(rpId) ? [...loopbackHosts] : rpId + export const createAuthRoutes = () => { const app = new Hono() @@ -125,8 +140,8 @@ export const createAuthRoutes = () => { const verification = await verifyRegistrationResponse({ response: response as never, expectedChallenge, - expectedOrigin: config.origin, - expectedRPID: config.rpId, + expectedOrigin: expectedWebAuthnOrigins(config.origin), + expectedRPID: expectedWebAuthnRpIds(config.rpId), requireUserVerification: true, }) @@ -186,8 +201,8 @@ export const createAuthRoutes = () => { const verification = await verifyAuthenticationResponse({ response: response as never, expectedChallenge, - expectedOrigin: config.origin, - expectedRPID: config.rpId, + expectedOrigin: expectedWebAuthnOrigins(config.origin), + expectedRPID: expectedWebAuthnRpIds(config.rpId), credential: { id: passkey.credentialId, publicKey: Buffer.from(passkey.publicKey, 'base64url'), diff --git a/apps/csbie-server/src/routes/mcp.ts b/apps/mnie-server/src/routes/mcp.ts similarity index 98% rename from apps/csbie-server/src/routes/mcp.ts rename to apps/mnie-server/src/routes/mcp.ts index b932552..fa19010 100644 --- a/apps/csbie-server/src/routes/mcp.ts +++ b/apps/mnie-server/src/routes/mcp.ts @@ -25,6 +25,9 @@ const textResult = (value: unknown) => ({ const requireAuthenticated = (auth: AuthContext) => { if (!auth.authenticated) throw new Error('unauthorized') + if (auth.type === 'apiKey' && !auth.scopes.includes('mcp')) { + throw new Error('missing OAuth scope: mcp') + } } const ORDER_SUBMIT_TICKET_TTL_MS = 10 * 60 * 1000 @@ -835,7 +838,7 @@ const createMcpServer = (c: Context) => { const auth = c.get('auth') const server = new McpServer({ - name: 'csbie', + name: 'mnie', version: '0.1.0', }) @@ -869,6 +872,10 @@ const createMcpServer = (c: Context) => { requireAuthenticated(auth) if (auth.type === 'apiKey') { + const requiredScope = isTradingMethod(method) ? 'trade' : 'read' + if (!auth.scopes.includes(requiredScope)) { + throw new Error(`missing OAuth scope: ${requiredScope}`) + } await assertApiKeyMethodAllowed(db, auth.apiKeyId, method) } @@ -943,7 +950,7 @@ const createMcpServer = (c: Context) => { } server.registerTool( - 'csbie-get', + 'mnie-get', { title: 'Get SBI Data', description: `Read SBI data through a small abstract action API. This tool never places, corrects, cancels, or otherwise changes real orders. ${getActionDescription}`, @@ -958,7 +965,7 @@ const createMcpServer = (c: Context) => { return textResult({ getActions, changeActions, - changeTool: 'csbie-request-change', + changeTool: 'mnie-request-change', confirmTool: 'confirm-request', }) } @@ -976,7 +983,7 @@ const createMcpServer = (c: Context) => { ) server.registerTool( - 'csbie-request-change', + 'mnie-request-change', { title: 'Create SBI Change Request', description: `Prepare a real SBI change by running the corresponding estimate/preview and returning a UUID. This tool never submits the change; pass the UUID to confirm-request. ${changeActionDescription}`, @@ -999,7 +1006,7 @@ const createMcpServer = (c: Context) => { description: 'Submit a previously prepared SBI change request by UUID. The UUID expires shortly, is single-use, and is bound to the same authenticated caller.', inputSchema: { - uuid: z.string().uuid().describe('UUID returned by csbie-request-change'), + uuid: z.string().uuid().describe('UUID returned by mnie-request-change'), }, }, async ({ uuid }) => { diff --git a/apps/csbie-server/src/routes/oauth.ts b/apps/mnie-server/src/routes/oauth.ts similarity index 88% rename from apps/csbie-server/src/routes/oauth.ts rename to apps/mnie-server/src/routes/oauth.ts index d20a168..d6e237d 100644 --- a/apps/csbie-server/src/routes/oauth.ts +++ b/apps/mnie-server/src/routes/oauth.ts @@ -7,6 +7,7 @@ import type { ApiKeySettings } from '../security/api-keys' import { createOAuthAuthorizationCode } from '../security/oauth-provider' const loopbackHosts = new Set(['localhost', '127.0.0.1', '[::1]']) +const supportedScopes = new Set(['read', 'write', 'trade', 'mcp']) const redirectUriAllowed = (redirectUri: string, registeredUris: string[]) => { if (registeredUris.includes(redirectUri)) return true @@ -62,6 +63,9 @@ export const createOAuthRoutes = () => { if (!body.clientId || !body.redirectUri || !body.codeChallenge) { return c.json({ error: 'clientId, redirectUri and codeChallenge are required' }, 400) } + const requestedScopes = body.scope?.split(' ').filter(Boolean) ?? [] + const unsupportedScope = requestedScopes.find((scope) => !supportedScopes.has(scope)) + if (unsupportedScope) return c.json({ error: `unsupported scope: ${unsupportedScope}` }, 400) const [client] = await c .get('db') @@ -78,7 +82,7 @@ export const createOAuthRoutes = () => { clientId: body.clientId, redirectUri: body.redirectUri, codeChallenge: body.codeChallenge, - scopes: body.scope?.split(' ').filter(Boolean) ?? [], + scopes: requestedScopes.length ? requestedScopes : ['read'], resource: body.resource, apiKeySettings: body.settings, }) diff --git a/apps/csbie-server/src/rpc/methods.ts b/apps/mnie-server/src/rpc/methods.ts similarity index 98% rename from apps/csbie-server/src/rpc/methods.ts rename to apps/mnie-server/src/rpc/methods.ts index 1333822..e4f163a 100644 --- a/apps/csbie-server/src/rpc/methods.ts +++ b/apps/mnie-server/src/rpc/methods.ts @@ -1,4 +1,4 @@ -import type { SbiClientMethods } from '@repo/sbi-client' +import type { SbiClientMethods } from '@repo/client-sbi' export const RPC_METHODS = [ 'session.profile', diff --git a/apps/csbie-server/src/rpc/sbi-session.ts b/apps/mnie-server/src/rpc/sbi-session.ts similarity index 93% rename from apps/csbie-server/src/rpc/sbi-session.ts rename to apps/mnie-server/src/rpc/sbi-session.ts index f93311a..37afa85 100644 --- a/apps/csbie-server/src/rpc/sbi-session.ts +++ b/apps/mnie-server/src/rpc/sbi-session.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm' -import { loginWithPasskey } from '@repo/sbi-client' -import type { SbiClientMethods, SbiClientOptions } from '@repo/sbi-client' +import { loginWithPasskey } from '@repo/client-sbi' +import type { SbiClientMethods, SbiClientOptions } from '@repo/client-sbi' import type { ServerConfig } from '../config' import type { Db } from '../db' import { sbiPasskeys } from '../db/schema' diff --git a/apps/csbie-server/src/rpc/ws.ts b/apps/mnie-server/src/rpc/ws.ts similarity index 93% rename from apps/csbie-server/src/rpc/ws.ts rename to apps/mnie-server/src/rpc/ws.ts index 7efdf3d..8586642 100644 --- a/apps/csbie-server/src/rpc/ws.ts +++ b/apps/mnie-server/src/rpc/ws.ts @@ -1,4 +1,4 @@ -import type { MarketCode, SbiClientMethods } from '@repo/sbi-client' +import type { MarketCode, SbiClientMethods } from '@repo/client-sbi' import { createBunWebSocket } from 'hono/bun' import type { WSContext } from 'hono/ws' import { randomUUID } from 'node:crypto' @@ -22,6 +22,7 @@ type RpcSocketState = { client?: SbiClientMethods sbiPasskeyId?: string apiKeyId?: string + scopes?: string[] boardPollingSubscriptions: Map } @@ -103,6 +104,12 @@ const stopBoardPollingSubscriptions = (state: RpcSocketState) => { } } +const assertScope = (state: RpcSocketState, scope: 'read' | 'trade') => { + if (!state.apiKeyId) return + const scopes = state.scopes ?? ['read', 'write', 'trade', 'mcp'] + if (!scopes.includes(scope)) throw new Error(`missing OAuth scope: ${scope}`) +} + const subscribeBoardPolling = async ( db: Db, state: RpcSocketState, @@ -110,6 +117,7 @@ const subscribeBoardPolling = async ( request: JsonRpcRequest, ) => { if (!state.client) return error(request.id, 4001, 'SBI session is not connected') + assertScope(state, 'read') if (state.apiKeyId) { await assertApiKeyMethodAllowed(db, state.apiKeyId, 'market.issue.board') @@ -159,6 +167,7 @@ const handleRpc = async ( } if (request.method === 'sbi.connect') { + assertScope(state, 'read') const passkeyId = request.params && typeof request.params === 'object' ? (request.params as { passkeyId?: string }).passkeyId @@ -190,6 +199,7 @@ const handleRpc = async ( } if (!state.client) return error(request.id, 4001, 'SBI session is not connected') + assertScope(state, isTradingMethod(request.method) ? 'trade' : 'read') if (state.apiKeyId) { await assertApiKeyMethodAllowed(db, state.apiKeyId, request.method) @@ -228,6 +238,7 @@ export const createRpcWebSocket = (db: Db, config: ServerConfig) => { const auth = c.get('auth') const state: RpcSocketState = { apiKeyId: auth.type === 'apiKey' ? auth.apiKeyId : undefined, + scopes: auth.type === 'apiKey' ? auth.scopes : undefined, boardPollingSubscriptions: new Map(), } diff --git a/apps/csbie-server/src/security/api-keys.ts b/apps/mnie-server/src/security/api-keys.ts similarity index 92% rename from apps/csbie-server/src/security/api-keys.ts rename to apps/mnie-server/src/security/api-keys.ts index 46e16ef..15de3f8 100644 --- a/apps/csbie-server/src/security/api-keys.ts +++ b/apps/mnie-server/src/security/api-keys.ts @@ -10,6 +10,7 @@ export type ApiKeySettings = { maxOrderPriceJpy?: number | null maxOrderAmountJpy?: number | null allowedMethods?: string[] | null + scopes?: string[] | null } const normalizeLimit = (value: unknown) => { @@ -31,6 +32,12 @@ export const normalizeApiKeySettings = (settings: ApiKeySettings = {}) => ({ : settings.allowedMethods?.length ? [...new Set(settings.allowedMethods)].sort() : null, + scopes: + settings.scopes === undefined + ? null + : settings.scopes?.length + ? [...new Set(settings.scopes)].sort() + : null, }) export const listApiKeys = async (db: Db) => @@ -44,6 +51,7 @@ export const listApiKeys = async (db: Db) => maxOrderPriceJpy: apiKeys.maxOrderPriceJpy, maxOrderAmountJpy: apiKeys.maxOrderAmountJpy, allowedMethods: apiKeys.allowedMethods, + scopes: apiKeys.scopes, createdAt: apiKeys.createdAt, lastUsedAt: apiKeys.lastUsedAt, revokedAt: apiKeys.revokedAt, @@ -53,7 +61,7 @@ export const listApiKeys = async (db: Db) => .orderBy(apiKeys.createdAt) export const createApiKey = async (db: Db, label: string, settings: ApiKeySettings = {}) => { - const token = `csbie_${randomToken()}` + const token = `mnie_${randomToken()}` const now = new Date() const row = { id: randomId('key'), diff --git a/apps/csbie-server/src/security/crypto.ts b/apps/mnie-server/src/security/crypto.ts similarity index 100% rename from apps/csbie-server/src/security/crypto.ts rename to apps/mnie-server/src/security/crypto.ts diff --git a/apps/csbie-server/src/security/http-auth.ts b/apps/mnie-server/src/security/http-auth.ts similarity index 88% rename from apps/csbie-server/src/security/http-auth.ts rename to apps/mnie-server/src/security/http-auth.ts index 3bf5aaf..ca0bf52 100644 --- a/apps/csbie-server/src/security/http-auth.ts +++ b/apps/mnie-server/src/security/http-auth.ts @@ -22,7 +22,12 @@ const readQueryApiKey = (request: Request) => { const apiKeyAuth = async (db: Db, token: string) => { const apiKey = await verifyApiKey(db, token) return apiKey - ? ({ type: 'apiKey', authenticated: true, apiKeyId: apiKey.id } satisfies AuthContext) + ? ({ + type: 'apiKey', + authenticated: true, + apiKeyId: apiKey.id, + scopes: apiKey.scopes?.length ? apiKey.scopes : ['read', 'write', 'trade', 'mcp'], + } satisfies AuthContext) : ({ type: 'none', authenticated: false } satisfies AuthContext) } diff --git a/apps/csbie-server/src/security/keyring.ts b/apps/mnie-server/src/security/keyring.ts similarity index 89% rename from apps/csbie-server/src/security/keyring.ts rename to apps/mnie-server/src/security/keyring.ts index d56be44..d164da9 100644 --- a/apps/csbie-server/src/security/keyring.ts +++ b/apps/mnie-server/src/security/keyring.ts @@ -3,7 +3,7 @@ import { mkdirSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { Database } from 'bun:sqlite' -const SERVICE = 'csbie' +const SERVICE = 'mnie' const SQLITE_BACKEND = 'sqlite' const PLATFORM_BACKEND = 'platform' @@ -15,9 +15,9 @@ let keytar: Keytar | undefined let sqlite: Database | undefined const keyringBackend = (): KeyringBackend => { - const backend = process.env.CSBIE_KEYRING_BACKEND ?? PLATFORM_BACKEND + const backend = process.env.MNIE_KEYRING_BACKEND ?? PLATFORM_BACKEND if (backend === PLATFORM_BACKEND || backend === SQLITE_BACKEND) return backend - throw new Error(`unsupported CSBIE_KEYRING_BACKEND: ${backend}`) + throw new Error(`unsupported MNIE_KEYRING_BACKEND: ${backend}`) } const loadKeytar = async () => { @@ -27,15 +27,15 @@ const loadKeytar = async () => { const sqlitePath = () => resolve( - process.env.CSBIE_KEYRING_SQLITE_PATH ?? - process.env.CSBIE_DATABASE_PATH?.replace(/\.sqlite$/u, '.keyring.sqlite') ?? - './data/csbie.keyring.sqlite', + process.env.MNIE_KEYRING_SQLITE_PATH ?? + process.env.MNIE_DATABASE_PATH?.replace(/\.sqlite$/u, '.keyring.sqlite') ?? + './data/mnie-app.keyring.sqlite', ) const sqliteKey = () => { - const secret = process.env.CSBIE_KEYRING_SECRET + const secret = process.env.MNIE_KEYRING_SECRET if (!secret) { - throw new Error('CSBIE_KEYRING_SECRET is required when CSBIE_KEYRING_BACKEND=sqlite') + throw new Error('MNIE_KEYRING_SECRET is required when MNIE_KEYRING_BACKEND=sqlite') } return createHash('sha256').update(secret).digest() } diff --git a/apps/csbie-server/src/security/oauth-provider.ts b/apps/mnie-server/src/security/oauth-provider.ts similarity index 89% rename from apps/csbie-server/src/security/oauth-provider.ts rename to apps/mnie-server/src/security/oauth-provider.ts index 51096cb..b0a3b77 100644 --- a/apps/csbie-server/src/security/oauth-provider.ts +++ b/apps/mnie-server/src/security/oauth-provider.ts @@ -16,6 +16,7 @@ import { randomToken, sha256 } from './crypto' const CODE_TTL_MS = 10 * 60 * 1000 const ACCESS_TOKEN_TTL_SECONDS = 60 * 60 const REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000 +const DEFAULT_SCOPES = ['read', 'write', 'trade', 'mcp'] as const export const createOAuthAuthorizationCode = async ( db: Db, @@ -54,7 +55,11 @@ export const createOAuthServerProvider = (db: Db, config: ServerConfig): OAuthSe registerClient: async ( client: Omit, ) => { - const clientInfo = client as OAuthClientInformationFull + const clientInfo = { + ...client, + client_id: randomToken(), + client_id_issued_at: Math.floor(Date.now() / 1000), + } satisfies OAuthClientInformationFull await db .insert(oauthClients) .values({ @@ -117,14 +122,13 @@ export const createOAuthServerProvider = (db: Db, config: ServerConfig): OAuthSe .delete(oauthAuthorizationCodes) .where(eq(oauthAuthorizationCodes.code, authorizationCode)) - const key = await createApiKey( - db, - `OAuth: ${client.client_name ?? client.client_id}`, - (code.apiKeySettings ?? {}) as ApiKeySettings, - ) - const refreshToken = `csbie_refresh_${randomToken()}` - const now = new Date() const scopes = code.scopes + const key = await createApiKey(db, `OAuth: ${client.client_name ?? client.client_id}`, { + ...((code.apiKeySettings ?? {}) as ApiKeySettings), + scopes, + }) + const refreshToken = `mnie_refresh_${randomToken()}` + const now = new Date() await db.insert(oauthRefreshTokens).values({ tokenHash: sha256(refreshToken), clientId: client.client_id, @@ -157,12 +161,15 @@ export const createOAuthServerProvider = (db: Db, config: ServerConfig): OAuthSe ) if (!token || token.expiresAt <= new Date()) throw new Error('refresh token expired') - const newAccess = await createApiKey(db, `OAuth: ${client.client_name ?? client.client_id}`) + const nextScopes = scopes ?? token.scopes + const newAccess = await createApiKey(db, `OAuth: ${client.client_name ?? client.client_id}`, { + scopes: nextScopes, + }) return { access_token: newAccess.token, token_type: 'Bearer', expires_in: ACCESS_TOKEN_TTL_SECONDS, - scope: (scopes ?? token.scopes).join(' '), + scope: nextScopes.join(' '), refresh_token: refreshToken, } satisfies OAuthTokens }, @@ -173,7 +180,7 @@ export const createOAuthServerProvider = (db: Db, config: ServerConfig): OAuthSe return { token, clientId: apiKey.id, - scopes: ['mcp'], + scopes: apiKey.scopes?.length ? apiKey.scopes : [...DEFAULT_SCOPES], expiresAt: Math.floor(Date.now() / 1000) + ACCESS_TOKEN_TTL_SECONDS, } satisfies AuthInfo }, diff --git a/apps/csbie-server/src/security/sbi-credentials.ts b/apps/mnie-server/src/security/sbi-credentials.ts similarity index 100% rename from apps/csbie-server/src/security/sbi-credentials.ts rename to apps/mnie-server/src/security/sbi-credentials.ts diff --git a/apps/csbie-server/src/security/sessions.ts b/apps/mnie-server/src/security/sessions.ts similarity index 100% rename from apps/csbie-server/src/security/sessions.ts rename to apps/mnie-server/src/security/sessions.ts diff --git a/apps/csbie-server/src/security/setup.ts b/apps/mnie-server/src/security/setup.ts similarity index 68% rename from apps/csbie-server/src/security/setup.ts rename to apps/mnie-server/src/security/setup.ts index 0e694c2..afd515e 100644 --- a/apps/csbie-server/src/security/setup.ts +++ b/apps/mnie-server/src/security/setup.ts @@ -1,10 +1,10 @@ import { sha256, safeEqual } from './crypto' export const verifySetupPassword = (password: string) => { - const hash = process.env.CSBIE_SETUP_PASSWORD_HASH + const hash = process.env.MNIE_SETUP_PASSWORD_HASH if (hash) return safeEqual(sha256(password), hash) - const plaintext = process.env.CSBIE_SETUP_PASSWORD + const plaintext = process.env.MNIE_SETUP_PASSWORD if (plaintext) return safeEqual(password, plaintext) return false diff --git a/apps/csbie-server/src/security/trade-limits.ts b/apps/mnie-server/src/security/trade-limits.ts similarity index 100% rename from apps/csbie-server/src/security/trade-limits.ts rename to apps/mnie-server/src/security/trade-limits.ts diff --git a/apps/csbie/tsconfig.json b/apps/mnie-server/tsconfig.json similarity index 100% rename from apps/csbie/tsconfig.json rename to apps/mnie-server/tsconfig.json diff --git a/apps/csbie-ui/AGENTS.md b/apps/mnie-ui/AGENTS.md similarity index 100% rename from apps/csbie-ui/AGENTS.md rename to apps/mnie-ui/AGENTS.md diff --git a/apps/csbie-ui/index.html b/apps/mnie-ui/index.html similarity index 91% rename from apps/csbie-ui/index.html rename to apps/mnie-ui/index.html index 7eea365..8ca8c4c 100644 --- a/apps/csbie-ui/index.html +++ b/apps/mnie-ui/index.html @@ -3,7 +3,7 @@ - cSBIe + Mnie
diff --git a/apps/csbie-ui/package.json b/apps/mnie-ui/package.json similarity index 95% rename from apps/csbie-ui/package.json rename to apps/mnie-ui/package.json index 487e40e..9d2b58e 100644 --- a/apps/csbie-ui/package.json +++ b/apps/mnie-ui/package.json @@ -1,5 +1,5 @@ { - "name": "@repo/csbie-ui", + "name": "@repo/mnie-ui", "private": true, "type": "module", "scripts": { diff --git a/apps/csbie-ui/src/App.vue b/apps/mnie-ui/src/App.vue similarity index 98% rename from apps/csbie-ui/src/App.vue rename to apps/mnie-ui/src/App.vue index 28f3140..f90f873 100644 --- a/apps/csbie-ui/src/App.vue +++ b/apps/mnie-ui/src/App.vue @@ -25,6 +25,7 @@ const router = useRouter() const activeTab = computed(() => { return routeNames.includes(route.name as RouteName) ? (route.name as RouteName) : 'portfolio' }) +const isOAuthRoute = computed(() => route.name === 'oauthAuthorize') const showAuthGate = computed(() => true) const decodedRouteParam = (value: string) => { try { @@ -208,10 +209,10 @@ onMounted(async () => {