From cbc3632519d1821843cac1044a67abd0bb7997f9 Mon Sep 17 00:00:00 2001 From: Grant Date: Mon, 1 Jun 2026 13:20:30 -0600 Subject: [PATCH 01/10] update node to v24.13.0 --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 821e395..e8416a1 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v24.12.0 +v24.13.0 -- GitLab From a9b7ed0161fa4add853956f7867818a4f92522fe Mon Sep 17 00:00:00 2001 From: Grant Date: Mon, 1 Jun 2026 14:29:06 -0600 Subject: [PATCH 02/10] update dependencies & add dev instructions --- .dockerignore | 32 +- Dockerfile | 95 +-- README.md | 15 + backend/package.json | 48 +- docker-compose.yml | 2 +- frontend/package.json | 38 +- package.json | 10 +- scripts/setup-jwks.ts | 40 ++ yarn.lock | 1474 +++++++++++++++++++++++++++++++---------- 9 files changed, 1294 insertions(+), 460 deletions(-) create mode 100644 scripts/setup-jwks.ts diff --git a/.dockerignore b/.dockerignore index 4d422ed..68e06a6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,12 +1,24 @@ -**/node_modules -build -data -**/dist -packages/build +.git +.gitignore +node_modules +backend/node_modules +frontend/node_modules + +backend/dist +frontend/dist + +.env +.env.* +!.env.example + Dockerfile -secrets +docker-compose*.yml + +coverage +.cache +.tmp +.DS_Store +npm-debug.log* +yarn-error.log* -# dotfiles -.git* -.vscode -**/.env* \ No newline at end of file +scripts \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 62f3d2c..6ba9bcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,74 +1,83 @@ +# syntax=docker/dockerfile:1.7 + FROM node:24-alpine AS base + RUN apk add --no-cache openssl -RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app WORKDIR /home/node/app -# --- dependencies --- +RUN mkdir -p /home/node/app \ + && chown -R node:node /home/node/app + COPY --chown=node:node .yarn ./.yarn COPY --chown=node:node package.json yarn.lock .yarnrc.yml ./ -COPY --chown=node:node backend/package.json ./backend/ -COPY --chown=node:node frontend/package.json ./frontend/ -RUN corepack enable && corepack prepare +COPY --chown=node:node backend/package.json ./backend/package.json +COPY --chown=node:node frontend/package.json ./frontend/package.json + +RUN corepack enable USER node -FROM base AS dev_dep -RUN yarn workspaces focus --all +ENV YARN_CACHE_FOLDER=/home/node/.yarn-cache -FROM base AS dep -RUN yarn workspaces focus --production --all +FROM base AS dev-deps -# -# === BUILDER === -# +RUN --mount=type=cache,target=/home/node/.yarn-cache,uid=1000,gid=1000 \ + yarn workspaces focus --all -FROM dev_dep AS build -COPY --from=dev_dep /home/node/app/ ./ +FROM base AS prod-deps -COPY --chown=node:node . . +RUN --mount=type=cache,target=/home/node/.yarn-cache,uid=1000,gid=1000 \ + yarn workspaces focus --all --production -# --- build frontend --- -RUN yarn workspace @fediverse-auth/frontend run build +FROM dev-deps AS build -# --- build backend --- +COPY --chown=node:node . . + +RUN yarn workspace @fediverse-auth/frontend run build \ + && yarn workspace @fediverse-auth/backend prisma generate \ + && yarn workspace @fediverse-auth/backend run build -RUN yarn workspace @fediverse-auth/backend prisma generate -RUN yarn workspace @fediverse-auth/backend run build -# -# === RUNNER === -# +FROM node:24-alpine AS run + +RUN apk add --no-cache openssl -FROM base AS run -COPY --from=dep /home/node/app/ ./ -COPY docker-start.sh ./ +WORKDIR /home/node/app -# --- prepare frontend --- +ENV PORT=3000 +ENV NODE_ENV=production +ENV SERVE_FRONTEND=/home/node/app/frontend -RUN mkdir -p frontend -COPY --from=build /home/node/app/frontend/dist ./frontend/ +RUN mkdir -p /home/node/app \ + && chown -R node:node /home/node/app -# --- prepare server --- +USER node -RUN mkdir -p backend -COPY --from=build /home/node/app/backend/package.json ./backend/ -COPY --from=build /home/node/app/backend/prisma ./backend/prisma -COPY --from=build /home/node/app/backend/dist ./backend/dist +# Production dependencies only. +COPY --chown=node:node --from=prod-deps /home/node/app/package.json ./package.json +COPY --chown=node:node --from=prod-deps /home/node/app/yarn.lock ./yarn.lock +COPY --chown=node:node --from=prod-deps /home/node/app/.yarnrc.yml ./.yarnrc.yml +COPY --chown=node:node --from=prod-deps /home/node/app/.yarn ./.yarn +COPY --chown=node:node --from=prod-deps /home/node/app/node_modules ./node_modules +COPY --chown=node:node --from=prod-deps /home/node/app/backend/package.json ./backend/package.json +COPY --chown=node:node --from=prod-deps /home/node/app/frontend/package.json ./frontend/package.json -# --- finalize --- +# Built frontend. +COPY --chown=node:node --from=build /home/node/app/frontend/dist ./frontend -COPY --from=build /home/node/app/node_modules/.prisma ./node_modules/.prisma -COPY --from=build /home/node/app/node_modules/@prisma ./node_modules/@prisma +# Built backend. +COPY --chown=node:node --from=build /home/node/app/backend/dist ./backend/dist +COPY --chown=node:node --from=build /home/node/app/backend/prisma ./backend/prisma -# set runtime env variables +# Prisma generated client from the build stage. +COPY --chown=node:node --from=build /home/node/app/node_modules/.prisma ./node_modules/.prisma +COPY --chown=node:node --from=build /home/node/app/node_modules/@prisma ./node_modules/@prisma -ENV PORT=3000 -ENV NODE_ENV=production -ENV SERVE_FRONTEND=/home/node/app/frontend +COPY --chown=node:node docker-start.sh ./docker-start.sh EXPOSE 3000 -ENTRYPOINT [ "/bin/sh" ] -CMD [ "./docker-start.sh" ] \ No newline at end of file + +ENTRYPOINT ["/bin/sh", "./docker-start.sh"] \ No newline at end of file diff --git a/README.md b/README.md index cd54ff5..4c2cc17 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,18 @@ Providing a central OpenID Connect service for Fediverse identification Leverages OpenID Connect Auto Discovery & Dynamic Client Registration + +## Development Environment + +- frontend: copy `frontend/example.env` to `frontend/.env` +- backend: copy `backend/example.env` to `backend/.env` +- backend: run `yarn workspace @fediverse-auth/backend prisma migrate dev` +- `yarn start frontend:dev` +- `yarn start backend:dev` + +## Deployment + +- copy `example.env.local` to `.env.local` (and configure) +- create `secrets/cookies.json` and put a random string in an array (eg `["super secret string do not share me or use this"]`) +- `yarn script:setup-jwks` +- `docker compose up -d --build` diff --git a/backend/package.json b/backend/package.json index c4ceb2a..3b6c128 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,38 +6,38 @@ "private": true, "type": "module", "dependencies": { - "@fedify/express": "^0.2.0", + "@fedify/express": "^0.2.1", "@fedify/fedify": "patch:@fedify/fedify@npm%3A1.10.0#~/.yarn/patches/@fedify-fedify-npm-1.10.0-eb96e7364a.patch", - "@fedify/redis": "^1.9.1", + "@fedify/redis": "^1.10.10", "@js-temporal/polyfill": "^0.5.1", - "@logtape/logtape": "^0.9.1", - "@prisma/client": "^5.13.0", - "@sc07/fedi-testkit": "^1.0.3", - "@tsconfig/recommended": "^1.0.6", - "body-parser": "^1.20.2", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "cron": "^4.3.0", - "express": "^4.22.1", - "express-session": "^1.18.0", - "ioredis": "^5.6.1", + "@logtape/logtape": "^0.9.2", + "@prisma/client": "^5.22.0", + "@sc07/fedi-testkit": "^1.0.4", + "@tsconfig/recommended": "^1.0.13", + "body-parser": "^1.20.5", + "cookie-parser": "^1.4.7", + "cors": "^2.8.6", + "cron": "^4.4.0", + "express": "^4.22.2", + "express-session": "^1.19.0", + "ioredis": "^5.11.0", "oidc-provider": "^8.8.1", - "openid-client": "^5.6.5", - "string-strip-html": "^13.4.12" + "openid-client": "^5.7.1", + "string-strip-html": "^13.5.3" }, "devDependencies": { "@hongminhee/localtunnel": "^0.3.0", - "@types/cookie-parser": "^1.4.7", - "@types/cors": "^2.8.17", + "@types/cookie-parser": "^1.4.10", + "@types/cors": "^2.8.19", "@types/express": "^4.17.25", - "@types/express-session": "^1.18.2", - "@types/node": "^20.12.10", - "@types/oidc-provider": "^8.4.4", - "dotenv": "^16.4.5", - "prisma": "^5.13.0", - "tsx": "^4.9.3", + "@types/express-session": "^1.19.0", + "@types/node": "^20.19.41", + "@types/oidc-provider": "^8.8.1", + "dotenv": "^16.6.1", + "prisma": "^5.22.0", + "tsx": "^4.22.4", "typescript": "^5.9.3", - "vitest": "^4.0.15" + "vitest": "^4.1.8" }, "scripts": { "dev": "tsx watch -r dotenv/config src/index.ts", diff --git a/docker-compose.yml b/docker-compose.yml index 0aabb47..8ef7d97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - fedi-auth: + app: image: sc07/fediverse-auth build: . ports: diff --git a/frontend/package.json b/frontend/package.json index f8492e1..3cdf314 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,29 +10,29 @@ "preview": "vite preview" }, "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "^5.15.16", - "@mui/lab": "^5.0.0-alpha.170", - "@mui/material": "^5.15.16", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@fontsource/roboto": "^5.2.10", + "@mui/icons-material": "^5.18.0", + "@mui/lab": "^5.0.0-alpha.177", + "@mui/material": "^5.18.0", "localforage": "^1.10.0", "match-sorter": "^6.3.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.23.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.30.4", "sort-by": "^0.0.2" }, "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", - "@vitejs/plugin-react-swc": "^3.5.0", - "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "typescript": "^5.2.2", - "vite": "^7.3.0" + "@types/react": "^18.3.30", + "@types/react-dom": "^18.3.7", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "@vitejs/plugin-react-swc": "^3.11.0", + "eslint": "^8.57.1", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.26", + "typescript": "^5.9.3", + "vite": "^7.3.5" } } diff --git a/package.json b/package.json index 9c87b04..a45dc1a 100644 --- a/package.json +++ b/package.json @@ -10,5 +10,13 @@ "frontend", "backend" ], - "packageManager": "yarn@4.12.0" + "packageManager": "yarn@4.12.0", + "scripts": { + "frontend:dev": "yarn workspace @fediverse-auth/frontend run dev", + "backend:dev": "yarn workspace @fediverse-auth/backend run dev", + "script:setup-jwks": "tsx scripts/setup-jwks.ts" + }, + "devDependencies": { + "tsx": "^4.22.4" + } } diff --git a/scripts/setup-jwks.ts b/scripts/setup-jwks.ts new file mode 100644 index 0000000..ec112b1 --- /dev/null +++ b/scripts/setup-jwks.ts @@ -0,0 +1,40 @@ +import { generateKeyPairSync, createPrivateKey } from "node:crypto"; +import { existsSync, writeFileSync } from "node:fs"; +import path from "node:path"; + +const TARGET = path.join(__dirname, "../secrets/jwks.json"); + +if (existsSync(TARGET)) { + console.error(`${TARGET} already exists`); + process.exit(1); +} + +const { privateKey } = generateKeyPairSync("rsa", { + modulusLength: 2048, + privateKeyEncoding: { + type: "pkcs8", + format: "pem", + }, +}); + +const privateJwk = createPrivateKey(privateKey).export({ + format: "jwk", +}); + +const jwks = [ + { + p: privateJwk.p, + kty: privateJwk.kty, + q: privateJwk.q, + d: privateJwk.d, + e: privateJwk.e, + qi: privateJwk.qi, + dp: privateJwk.dp, + dq: privateJwk.dq, + n: privateJwk.n, + }, +]; + +writeFileSync(TARGET, JSON.stringify(jwks, null, 2)); + +console.log(`Created ${TARGET}`); diff --git a/yarn.lock b/yarn.lock index bc8e915..59c546f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -131,6 +131,34 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/core@npm:1.10.0" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.1" + tslib: "npm:^2.4.0" + checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb + languageName: node + linkType: hard + +"@emnapi/runtime@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/runtime@npm:1.10.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.2.1": + version: 1.2.1 + resolution: "@emnapi/wasi-threads@npm:1.2.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 + languageName: node + linkType: hard + "@emotion/babel-plugin@npm:^11.13.5": version: 11.13.5 resolution: "@emotion/babel-plugin@npm:11.13.5" @@ -186,7 +214,7 @@ __metadata: languageName: node linkType: hard -"@emotion/react@npm:^11.11.4": +"@emotion/react@npm:^11.14.0": version: 11.14.0 resolution: "@emotion/react@npm:11.14.0" dependencies: @@ -227,7 +255,7 @@ __metadata: languageName: node linkType: hard -"@emotion/styled@npm:^11.11.5": +"@emotion/styled@npm:^11.14.1": version: 11.14.1 resolution: "@emotion/styled@npm:11.14.1" dependencies: @@ -284,6 +312,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/aix-ppc64@npm:0.28.0" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm64@npm:0.27.2" @@ -291,6 +326,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-arm64@npm:0.28.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm@npm:0.27.2" @@ -298,6 +340,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-arm@npm:0.28.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-x64@npm:0.27.2" @@ -305,6 +354,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/android-x64@npm:0.28.0" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-arm64@npm:0.27.2" @@ -312,6 +368,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/darwin-arm64@npm:0.28.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-x64@npm:0.27.2" @@ -319,6 +382,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/darwin-x64@npm:0.28.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-arm64@npm:0.27.2" @@ -326,6 +396,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/freebsd-arm64@npm:0.28.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-x64@npm:0.27.2" @@ -333,6 +410,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/freebsd-x64@npm:0.28.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm64@npm:0.27.2" @@ -340,6 +424,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-arm64@npm:0.28.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm@npm:0.27.2" @@ -347,6 +438,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-arm@npm:0.28.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ia32@npm:0.27.2" @@ -354,6 +452,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-ia32@npm:0.28.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-loong64@npm:0.27.2" @@ -361,6 +466,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-loong64@npm:0.28.0" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-mips64el@npm:0.27.2" @@ -368,6 +480,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-mips64el@npm:0.28.0" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ppc64@npm:0.27.2" @@ -375,6 +494,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-ppc64@npm:0.28.0" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-riscv64@npm:0.27.2" @@ -382,6 +508,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-riscv64@npm:0.28.0" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-s390x@npm:0.27.2" @@ -389,6 +522,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-s390x@npm:0.28.0" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-x64@npm:0.27.2" @@ -396,6 +536,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/linux-x64@npm:0.28.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-arm64@npm:0.27.2" @@ -403,6 +550,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/netbsd-arm64@npm:0.28.0" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-x64@npm:0.27.2" @@ -410,6 +564,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/netbsd-x64@npm:0.28.0" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-arm64@npm:0.27.2" @@ -417,6 +578,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openbsd-arm64@npm:0.28.0" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-x64@npm:0.27.2" @@ -424,6 +592,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openbsd-x64@npm:0.28.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openharmony-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openharmony-arm64@npm:0.27.2" @@ -431,6 +606,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openharmony-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/openharmony-arm64@npm:0.28.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/sunos-x64@npm:0.27.2" @@ -438,6 +620,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/sunos-x64@npm:0.28.0" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-arm64@npm:0.27.2" @@ -445,6 +634,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-arm64@npm:0.28.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-ia32@npm:0.27.2" @@ -452,6 +648,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-ia32@npm:0.28.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-x64@npm:0.27.2" @@ -459,6 +662,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.28.0": + version: 0.28.0 + resolution: "@esbuild/win32-x64@npm:0.28.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -501,7 +711,7 @@ __metadata: languageName: node linkType: hard -"@fedify/express@npm:^0.2.0": +"@fedify/express@npm:^0.2.1": version: 0.2.1 resolution: "@fedify/express@npm:0.2.1" peerDependencies: @@ -566,16 +776,16 @@ __metadata: languageName: node linkType: hard -"@fedify/redis@npm:^1.9.1": - version: 1.10.0 - resolution: "@fedify/redis@npm:1.10.0" +"@fedify/redis@npm:^1.10.10": + version: 1.10.10 + resolution: "@fedify/redis@npm:1.10.10" dependencies: "@js-temporal/polyfill": "npm:^0.5.1" "@logtape/logtape": "npm:^1.2.2" peerDependencies: - "@fedify/fedify": ^1.10.0 + "@fedify/fedify": ^1.10.10 ioredis: ^5.6.1 - checksum: 10c0/4594d6b999b78094e1a05052a8ee406549d5b476277a7841dc8c69cb1e38c5ef0cd25a863cf630f7c64b4f42f6f83f7ceba2c514c999c6f0a8cf53e1ea55443d + checksum: 10c0/7bc5daf8f24f5e52d90bddb1721def9d1f59aa3a7ced264d93d19e1a974b89adf187f1010f638824a51fd9f478be2cc07c8b5ac31b2a67b356c5403a8401a8ba languageName: node linkType: hard @@ -583,36 +793,36 @@ __metadata: version: 0.0.0-use.local resolution: "@fediverse-auth/backend@workspace:backend" dependencies: - "@fedify/express": "npm:^0.2.0" + "@fedify/express": "npm:^0.2.1" "@fedify/fedify": "patch:@fedify/fedify@npm%3A1.10.0#~/.yarn/patches/@fedify-fedify-npm-1.10.0-eb96e7364a.patch" - "@fedify/redis": "npm:^1.9.1" + "@fedify/redis": "npm:^1.10.10" "@hongminhee/localtunnel": "npm:^0.3.0" "@js-temporal/polyfill": "npm:^0.5.1" - "@logtape/logtape": "npm:^0.9.1" - "@prisma/client": "npm:^5.13.0" - "@sc07/fedi-testkit": "npm:^1.0.3" - "@tsconfig/recommended": "npm:^1.0.6" - "@types/cookie-parser": "npm:^1.4.7" - "@types/cors": "npm:^2.8.17" + "@logtape/logtape": "npm:^0.9.2" + "@prisma/client": "npm:^5.22.0" + "@sc07/fedi-testkit": "npm:^1.0.4" + "@tsconfig/recommended": "npm:^1.0.13" + "@types/cookie-parser": "npm:^1.4.10" + "@types/cors": "npm:^2.8.19" "@types/express": "npm:^4.17.25" - "@types/express-session": "npm:^1.18.2" - "@types/node": "npm:^20.12.10" - "@types/oidc-provider": "npm:^8.4.4" - body-parser: "npm:^1.20.2" - cookie-parser: "npm:^1.4.6" - cors: "npm:^2.8.5" - cron: "npm:^4.3.0" - dotenv: "npm:^16.4.5" - express: "npm:^4.22.1" - express-session: "npm:^1.18.0" - ioredis: "npm:^5.6.1" + "@types/express-session": "npm:^1.19.0" + "@types/node": "npm:^20.19.41" + "@types/oidc-provider": "npm:^8.8.1" + body-parser: "npm:^1.20.5" + cookie-parser: "npm:^1.4.7" + cors: "npm:^2.8.6" + cron: "npm:^4.4.0" + dotenv: "npm:^16.6.1" + express: "npm:^4.22.2" + express-session: "npm:^1.19.0" + ioredis: "npm:^5.11.0" oidc-provider: "npm:^8.8.1" - openid-client: "npm:^5.6.5" - prisma: "npm:^5.13.0" - string-strip-html: "npm:^13.4.12" - tsx: "npm:^4.9.3" + openid-client: "npm:^5.7.1" + prisma: "npm:^5.22.0" + string-strip-html: "npm:^13.5.3" + tsx: "npm:^4.22.4" typescript: "npm:^5.9.3" - vitest: "npm:^4.0.15" + vitest: "npm:^4.1.8" languageName: unknown linkType: soft @@ -620,28 +830,28 @@ __metadata: version: 0.0.0-use.local resolution: "@fediverse-auth/frontend@workspace:frontend" dependencies: - "@emotion/react": "npm:^11.11.4" - "@emotion/styled": "npm:^11.11.5" - "@fontsource/roboto": "npm:^5.0.13" - "@mui/icons-material": "npm:^5.15.16" - "@mui/lab": "npm:^5.0.0-alpha.170" - "@mui/material": "npm:^5.15.16" - "@types/react": "npm:^18.2.66" - "@types/react-dom": "npm:^18.2.22" - "@typescript-eslint/eslint-plugin": "npm:^7.2.0" - "@typescript-eslint/parser": "npm:^7.2.0" - "@vitejs/plugin-react-swc": "npm:^3.5.0" - eslint: "npm:^8.57.0" - eslint-plugin-react-hooks: "npm:^4.6.0" - eslint-plugin-react-refresh: "npm:^0.4.6" + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.1" + "@fontsource/roboto": "npm:^5.2.10" + "@mui/icons-material": "npm:^5.18.0" + "@mui/lab": "npm:^5.0.0-alpha.177" + "@mui/material": "npm:^5.18.0" + "@types/react": "npm:^18.3.30" + "@types/react-dom": "npm:^18.3.7" + "@typescript-eslint/eslint-plugin": "npm:^7.18.0" + "@typescript-eslint/parser": "npm:^7.18.0" + "@vitejs/plugin-react-swc": "npm:^3.11.0" + eslint: "npm:^8.57.1" + eslint-plugin-react-hooks: "npm:^4.6.2" + eslint-plugin-react-refresh: "npm:^0.4.26" localforage: "npm:^1.10.0" match-sorter: "npm:^6.3.4" - react: "npm:^18.2.0" - react-dom: "npm:^18.2.0" - react-router-dom: "npm:^6.23.0" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + react-router-dom: "npm:^6.30.4" sort-by: "npm:^0.0.2" - typescript: "npm:^5.2.2" - vite: "npm:^7.3.0" + typescript: "npm:^5.9.3" + vite: "npm:^7.3.5" languageName: unknown linkType: soft @@ -683,10 +893,10 @@ __metadata: languageName: node linkType: hard -"@fontsource/roboto@npm:^5.0.13": - version: 5.2.9 - resolution: "@fontsource/roboto@npm:5.2.9" - checksum: 10c0/8280ab6504ab7da105c77afc1231236be86f7cd02a708e25b6cfc2871975699a44be7a42491e59934f2a84c874a8109bb2babfbc6b1986bcad9f3ac1de3980ca +"@fontsource/roboto@npm:^5.2.10": + version: 5.2.10 + resolution: "@fontsource/roboto@npm:5.2.10" + checksum: 10c0/abadfcaefbe2196bbd36d9258b336063f721894a3624a9639da00792583c565bfd0748ef978f91a38c3c0de8d247615c9be92a763184c085c630fa43f04b1cd9 languageName: node linkType: hard @@ -724,10 +934,10 @@ __metadata: languageName: node linkType: hard -"@ioredis/commands@npm:1.4.0": - version: 1.4.0 - resolution: "@ioredis/commands@npm:1.4.0" - checksum: 10c0/99afe21fba794f84a2b84cceabcc370a7622e7b8b97a6589456c07c9fa62a15d54c5546f6f7214fb9a2458b1fa87579d5c531aaf48e06cc9be156d5923892c8d +"@ioredis/commands@npm:1.10.0": + version: 1.10.0 + resolution: "@ioredis/commands@npm:1.10.0" + checksum: 10c0/baf91e62d0e64ef2b5f7ca4413dc2456fe250e87483beac4a1c8ef1fe5ad0d2fcdeb9b89d4556d8ef6c7455c64a964359d729601fdb06b2f4c76c35dd59afa99 languageName: node linkType: hard @@ -820,7 +1030,7 @@ __metadata: languageName: node linkType: hard -"@logtape/logtape@npm:^0.9.1": +"@logtape/logtape@npm:^0.9.2": version: 0.9.2 resolution: "@logtape/logtape@npm:0.9.2" checksum: 10c0/ca548987a4d2bbccba7ce8d8bb22b3071d72a84ea6c453882eccbb8fa2e7e45f200f01bb85b7b82e582d9ff361dba28b3e0fd4a0d3fb26626d665d26f7e9b2bc @@ -863,7 +1073,7 @@ __metadata: languageName: node linkType: hard -"@mui/icons-material@npm:^5.15.16": +"@mui/icons-material@npm:^5.18.0": version: 5.18.0 resolution: "@mui/icons-material@npm:5.18.0" dependencies: @@ -879,7 +1089,7 @@ __metadata: languageName: node linkType: hard -"@mui/lab@npm:^5.0.0-alpha.170": +"@mui/lab@npm:^5.0.0-alpha.177": version: 5.0.0-alpha.177 resolution: "@mui/lab@npm:5.0.0-alpha.177" dependencies: @@ -908,7 +1118,7 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:^5.15.16": +"@mui/material@npm:^5.18.0": version: 5.18.0 resolution: "@mui/material@npm:5.18.0" dependencies: @@ -1047,6 +1257,18 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^1.1.4": + version: 1.1.4 + resolution: "@napi-rs/wasm-runtime@npm:1.1.4" + dependencies: + "@tybys/wasm-util": "npm:^0.10.1" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/2e88e1955258949ccf2d18c79975821ad38071b465ef126a5e14110977b97868867b016c1ad046e963cccc42c0bd9db6c8ff5fd1ebb61b87bb3487f339041658 + languageName: node + linkType: hard + "@noble/hashes@npm:1.4.0": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" @@ -1160,6 +1382,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.133.0": + version: 0.133.0 + resolution: "@oxc-project/types@npm:0.133.0" + checksum: 10c0/70c57ba58644f7ec217b670c301801f4d06995f4ccdba6b2bd106ea3e5ee49d616573e6ef8d55530b87571a960696543687f3850e87ad173d3f88965c30cdd63 + languageName: node + linkType: hard + "@phensley/language-tag@npm:^1.9.0": version: 1.14.0 resolution: "@phensley/language-tag@npm:1.14.0" @@ -1176,7 +1405,7 @@ __metadata: languageName: node linkType: hard -"@prisma/client@npm:^5.13.0": +"@prisma/client@npm:^5.22.0": version: 5.22.0 resolution: "@prisma/client@npm:5.22.0" peerDependencies: @@ -1234,27 +1463,136 @@ __metadata: languageName: node linkType: hard -"@puppeteer/browsers@npm:2.11.0": - version: 2.11.0 - resolution: "@puppeteer/browsers@npm:2.11.0" +"@puppeteer/browsers@npm:2.13.2": + version: 2.13.2 + resolution: "@puppeteer/browsers@npm:2.13.2" dependencies: debug: "npm:^4.4.3" extract-zip: "npm:^2.0.1" progress: "npm:^2.0.3" proxy-agent: "npm:^6.5.0" - semver: "npm:^7.7.3" + semver: "npm:^7.7.4" tar-fs: "npm:^3.1.1" yargs: "npm:^17.7.2" bin: browsers: lib/cjs/main-cli.js - checksum: 10c0/4c8cb0de98c9f2c1f4e987fbb1f95fe500a89845927610af2de23e969912fab5a9791c4729d2d86686812564e00ba4efaff900997ad2a5a2e1c57fb41286b8ca + checksum: 10c0/d4f7a6b160a87598f9bb6524d475e4bb16effb7ee39e82a731e51af3454858e95d80c1cfe2030dfadfc407b77d7ef60f0f8c1a2dcf4532dce30f09c09df29082 + languageName: node + linkType: hard + +"@remix-run/router@npm:1.23.3": + version: 1.23.3 + resolution: "@remix-run/router@npm:1.23.3" + checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17 + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-android-arm64@npm:1.0.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.3" + conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@remix-run/router@npm:1.23.1": - version: 1.23.1 - resolution: "@remix-run/router@npm:1.23.1" - checksum: 10c0/94ac9632c0070199b8275cd6dffe78eb4c02e8926328937c65561c5c30d7ddf842743df3c8f7df302f00a593dd204846d93667fbbbe3c3641437d7b8f333ed90 +"@rolldown/binding-linux-ppc64-gnu@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.3" + dependencies: + "@emnapi/core": "npm:1.10.0" + "@emnapi/runtime": "npm:1.10.0" + "@napi-rs/wasm-runtime": "npm:^1.1.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.0.3": + version: 1.0.3 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.3" + conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -1265,6 +1603,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.54.0": version: 4.54.0 resolution: "@rollup/rollup-android-arm-eabi@npm:4.54.0" @@ -1419,13 +1764,13 @@ __metadata: languageName: node linkType: hard -"@sc07/fedi-testkit@npm:^1.0.3": - version: 1.0.3 - resolution: "@sc07/fedi-testkit@npm:1.0.3::__archiveUrl=https%3A%2F%2Fsc07.dev%2Fapi%2Fv4%2Fprojects%2F78%2Fpackages%2Fnpm%2F%40sc07%2Ffedi-testkit%2F-%2F%40sc07%2Ffedi-testkit-1.0.3.tgz" +"@sc07/fedi-testkit@npm:^1.0.4": + version: 1.0.4 + resolution: "@sc07/fedi-testkit@npm:1.0.4::__archiveUrl=https%3A%2F%2Fsc07.dev%2Fapi%2Fv4%2Fprojects%2F78%2Fpackages%2Fnpm%2F%40sc07%2Ffedi-testkit%2F-%2F%40sc07%2Ffedi-testkit-1.0.4.tgz" dependencies: - puppeteer: "npm:^24.33.0" + puppeteer: "npm:^24.35.0" tsx: "npm:^4.21.0" - checksum: 10c0/5a46994aaefef017353cad66ed6aaf9577fb73f32a982486e45658036c7d0b618d4ccd26dc99e1c6a5b8db089162ca1dab78a0f9c8b8309ce796239246500dbb + checksum: 10c0/ec87d4b7304ff2155dd2f102800ca27f7237cc71daa56f8473f4abf4e97bad1809bcd4ae06ba0c95c5c57c8e2ba71ec6e03de2d7ffd6ca38379a914a79da3819 languageName: node linkType: hard @@ -1436,7 +1781,7 @@ __metadata: languageName: node linkType: hard -"@standard-schema/spec@npm:^1.0.0": +"@standard-schema/spec@npm:^1.1.0": version: 1.1.0 resolution: "@standard-schema/spec@npm:1.1.0" checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 @@ -1591,13 +1936,22 @@ __metadata: languageName: node linkType: hard -"@tsconfig/recommended@npm:^1.0.6": +"@tsconfig/recommended@npm:^1.0.13": version: 1.0.13 resolution: "@tsconfig/recommended@npm:1.0.13" checksum: 10c0/b0fae7ff8243621da68cd7a259499e1fd536e1aa3b3e3be233d30ab8d5bf046ec3dcc4ff68fc640435f421575e41e56bc735f61a1fe8a47b4475f3f2bf5ed882 languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.2 + resolution: "@tybys/wasm-util@npm:0.10.2" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/26165bcd1fd7269f42d7fbe3de318f854a8968de8397e89fc9a423bb3e2da35a52150f382e6323b3367595beb16d9800a6f35971a5599daf76da1742ec3afc25 + languageName: node + linkType: hard + "@types/accepts@npm:*": version: 1.3.7 resolution: "@types/accepts@npm:1.3.7" @@ -1643,7 +1997,7 @@ __metadata: languageName: node linkType: hard -"@types/cookie-parser@npm:^1.4.7": +"@types/cookie-parser@npm:^1.4.10": version: 1.4.10 resolution: "@types/cookie-parser@npm:1.4.10" peerDependencies: @@ -1664,7 +2018,7 @@ __metadata: languageName: node linkType: hard -"@types/cors@npm:^2.8.17": +"@types/cors@npm:^2.8.19": version: 2.8.19 resolution: "@types/cors@npm:2.8.19" dependencies: @@ -1711,12 +2065,12 @@ __metadata: languageName: node linkType: hard -"@types/express-session@npm:^1.18.2": - version: 1.18.2 - resolution: "@types/express-session@npm:1.18.2" +"@types/express-session@npm:^1.19.0": + version: 1.19.0 + resolution: "@types/express-session@npm:1.19.0" dependencies: "@types/express": "npm:*" - checksum: 10c0/5d5aa134ce8990920b35f2dd0aa55168af44faaf14789b6921d361ce016c43bdc66feba287753981a2fee33fd95b8a829c4418c3ca480b03961724b8bc13e453 + checksum: 10c0/523035f476c84623d6ee4ec81452066d0112f66a41ba9488003c8898f26c85c46951e093bb1cb81447571a467b4a7bba87bd52c08e519a2af254c9de48a25d5f languageName: node linkType: hard @@ -1835,16 +2189,16 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^20.12.10": - version: 20.19.27 - resolution: "@types/node@npm:20.19.27" +"@types/node@npm:^20.19.41": + version: 20.19.41 + resolution: "@types/node@npm:20.19.41" dependencies: undici-types: "npm:~6.21.0" - checksum: 10c0/7599c24d80465c1aa6e29b53581fc20ad8862ff33e6eef31d05c1c706868476ee57319c89b802ea972dd4d64ce86d18020aa5344f851fb59b730ea509a63300f + checksum: 10c0/aa2a07317bbd700bea68d5784b403a738dbcebadbe2d8ef05649f7953065120d5d37f7edfdd7881df3a3bd15328c8a4dc46fdd69732ab540d552c505378c585b languageName: node linkType: hard -"@types/oidc-provider@npm:^8.4.4": +"@types/oidc-provider@npm:^8.8.1": version: 8.8.1 resolution: "@types/oidc-provider@npm:8.8.1" dependencies: @@ -1883,7 +2237,7 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.2.22": +"@types/react-dom@npm:^18.3.7": version: 18.3.7 resolution: "@types/react-dom@npm:18.3.7" peerDependencies: @@ -1901,13 +2255,13 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^18.2.66": - version: 18.3.27 - resolution: "@types/react@npm:18.3.27" +"@types/react@npm:^18.3.30": + version: 18.3.30 + resolution: "@types/react@npm:18.3.30" dependencies: "@types/prop-types": "npm:*" csstype: "npm:^3.2.2" - checksum: 10c0/a761d2f58de03d0714806cc65d32bb3d73fb33a08dd030d255b47a295e5fff2a775cf1c20b786824d8deb6454eaccce9bc6998d9899c14fc04bbd1b0b0b72897 + checksum: 10c0/7d5dcb00d2b71f081997e2a881c3f9af0300456f7fca8579766b4187c5b83bbe9fd02f3eaa1837cc25f1ad43e8b045cee6de9925a474c55750b222fdd9b74437 languageName: node linkType: hard @@ -1960,7 +2314,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^7.2.0": +"@typescript-eslint/eslint-plugin@npm:^7.18.0": version: 7.18.0 resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" dependencies: @@ -1983,7 +2337,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^7.2.0": +"@typescript-eslint/parser@npm:^7.18.0": version: 7.18.0 resolution: "@typescript-eslint/parser@npm:7.18.0" dependencies: @@ -2085,7 +2439,7 @@ __metadata: languageName: node linkType: hard -"@vitejs/plugin-react-swc@npm:^3.5.0": +"@vitejs/plugin-react-swc@npm:^3.11.0": version: 3.11.0 resolution: "@vitejs/plugin-react-swc@npm:3.11.0" dependencies: @@ -2097,83 +2451,85 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/expect@npm:4.0.16" +"@vitest/expect@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/expect@npm:4.1.8" dependencies: - "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/spec": "npm:^1.1.0" "@types/chai": "npm:^5.2.2" - "@vitest/spy": "npm:4.0.16" - "@vitest/utils": "npm:4.0.16" - chai: "npm:^6.2.1" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/add4dde3548b6f65b6d7d364607713f9db258642add248a23805fa1172e48d76a7822080249efb882120b408772684569b2e78581ed3d5f282e215d7f21be183 + "@vitest/spy": "npm:4.1.8" + "@vitest/utils": "npm:4.1.8" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/f7bf6c720d2427c3bd0b35472ebd84d963be7d09ecf52a0fb05e8c4d5d0c9ee164a8c28eee6360947be1b245b47faefab54560cb98e5cb678c1c1074260b9149 languageName: node linkType: hard -"@vitest/mocker@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/mocker@npm:4.0.16" +"@vitest/mocker@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/mocker@npm:4.1.8" dependencies: - "@vitest/spy": "npm:4.0.16" + "@vitest/spy": "npm:4.1.8" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.21" peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - checksum: 10c0/cf4469a4745e3cdd46e8ee4e20aa04369e7f985d40175d974d3a6f6d331bf9d8610f9638c5a18f7ff59a30ff04b19f4b823457b4c79142186fe463fa4cee80c5 + checksum: 10c0/f8cb2b8b55dc2cba0b2399aeee528b0187042f22cbc2d50a4fd6141f5aa246ebc41700f45dd1d73eca44ddfb57dcde48b2eb317bfbb1198f5ab2cc4fd04b2ea0 languageName: node linkType: hard -"@vitest/pretty-format@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/pretty-format@npm:4.0.16" +"@vitest/pretty-format@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/pretty-format@npm:4.1.8" dependencies: - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/11243e9c2d2d011ae23825c6b7464a4385a4a4efc4ceb28b7854bb9d73491f440b89d12f62c5c9737d26375cf9585b11bc20183d4dea4e983e79d5e162407eb9 + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/553c456692a4b9ae13cd116c234c74b4495e0f1a0d5c51ffc3fab8ea085e3550769967e29db79bdac0cf127b1bf88b7f70bfba3dcc72be6bddf834433e30cc91 languageName: node linkType: hard -"@vitest/runner@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/runner@npm:4.0.16" +"@vitest/runner@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/runner@npm:4.1.8" dependencies: - "@vitest/utils": "npm:4.0.16" + "@vitest/utils": "npm:4.1.8" pathe: "npm:^2.0.3" - checksum: 10c0/7f4614a9fe5e9f3683d30fb82d1489796c669df45fbc0beb22d39539e4b12ebef462062705545ca04391a0406af62088cbf1d613a812ecc9ea753a0edbfd5d26 + checksum: 10c0/706808a4b7b95ea9a9268fc152dd39e15a9a754f37c7990aea167486a9094caa913dae454771ae02c18dccfabd667f8cc38eed33a1307a79d32a89878b5bcce1 languageName: node linkType: hard -"@vitest/snapshot@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/snapshot@npm:4.0.16" +"@vitest/snapshot@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/snapshot@npm:4.1.8" dependencies: - "@vitest/pretty-format": "npm:4.0.16" + "@vitest/pretty-format": "npm:4.1.8" + "@vitest/utils": "npm:4.1.8" magic-string: "npm:^0.30.21" pathe: "npm:^2.0.3" - checksum: 10c0/4fa63ffa4f30c909078210a1edcb059dbfa3ec3deaebb8f93637f65a7efae9a2d7714129bae0cf615512a683e925cf31f281fc4cb02f1fdc4c72f68ce21ca11f + checksum: 10c0/ba4c32112491d42d24986f921c50ede5edbdb4b7eafa16c72cf8d2c9ecc44121fdb3d9365236747a9841f0d6776affc6457470fcbb082df9dbc28c24792a0c6d languageName: node linkType: hard -"@vitest/spy@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/spy@npm:4.0.16" - checksum: 10c0/2502918e703d60ef64854d0fa83ebf94da64b80e81b80c319568feee3d86069fd46e24880a768edba06c8caba13801e44005e17a0f16d9b389486f24d539f0bf +"@vitest/spy@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/spy@npm:4.1.8" + checksum: 10c0/3c10c0325a09d16bc0e77c0be96c47c15416186e33332880c0d1dd0a51d51a866091067b81f2a2ef6fb422a7760e6cf15c04d91a0eca4d59f62e8c8401fa53fc languageName: node linkType: hard -"@vitest/utils@npm:4.0.16": - version: 4.0.16 - resolution: "@vitest/utils@npm:4.0.16" +"@vitest/utils@npm:4.1.8": + version: 4.1.8 + resolution: "@vitest/utils@npm:4.1.8" dependencies: - "@vitest/pretty-format": "npm:4.0.16" - tinyrainbow: "npm:^3.0.3" - checksum: 10c0/bba35b4e102be03e106ced227809437573aa5c5f64d512301ca8de127dcb91cbedc11a2e823305f8ba82528c909c10510ec8c7e3d92b3d6d1c1aec33e143572a + "@vitest/pretty-format": "npm:4.1.8" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/acda9d3d640c1ebc81afb358ac30589d7d7d583af81e2d09419f0af9cbe41f3ce0b90527326943bf0da51614be5fc31afcd32259f6beb32b3417999d6ef380f3 languageName: node linkType: hard @@ -2404,9 +2760,9 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^1.20.2, body-parser@npm:~1.20.3": - version: 1.20.4 - resolution: "body-parser@npm:1.20.4" +"body-parser@npm:^1.20.5, body-parser@npm:~1.20.5": + version: 1.20.5 + resolution: "body-parser@npm:1.20.5" dependencies: bytes: "npm:~3.1.2" content-type: "npm:~1.0.5" @@ -2416,11 +2772,11 @@ __metadata: http-errors: "npm:~2.0.1" iconv-lite: "npm:~0.4.24" on-finished: "npm:~2.4.1" - qs: "npm:~6.14.0" + qs: "npm:~6.15.1" raw-body: "npm:~2.5.3" type-is: "npm:~1.6.18" unpipe: "npm:~1.0.0" - checksum: 10c0/569c1e896297d1fcd8f34026c8d0ab70b90d45343c15c5d8dff5de2bad08125fc1e2f8c2f3f4c1ac6c0caaad115218202594d37dcb8d89d9b5dcae1c2b736aa9 + checksum: 10c0/ad777ca5e4711eae253c93f50fdc4608c60b76a9710d79e5e5b84581c76691e6ad21ecc9158986d9ea2b365df73e403ca33c27a8bccc1a7cfc2ccc248548118d languageName: node linkType: hard @@ -2567,7 +2923,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:^6.2.1": +"chai@npm:^6.2.2": version: 6.2.2 resolution: "chai@npm:6.2.2" checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 @@ -2591,15 +2947,15 @@ __metadata: languageName: node linkType: hard -"chromium-bidi@npm:12.0.1": - version: 12.0.1 - resolution: "chromium-bidi@npm:12.0.1" +"chromium-bidi@npm:14.0.0": + version: 14.0.0 + resolution: "chromium-bidi@npm:14.0.0" dependencies: mitt: "npm:^3.0.1" zod: "npm:^3.24.1" peerDependencies: devtools-protocol: "*" - checksum: 10c0/cc278125a6ad4d4010f4c093442c6da608d0b18be483efbfad67def029a73f0bd16a7deccd9bf848e26dbd47119e3415724abdd7da497a80f886127494fb40b9 + checksum: 10c0/49da03868d3a46c68e40d20f68a75ffacc05b22dc247b7c1b2126716d8d69e618780f8ab1d63e676fb372cddb32b773efaabb2f520217321caece9a258500d31 languageName: node linkType: hard @@ -2621,10 +2977,10 @@ __metadata: languageName: node linkType: hard -"cluster-key-slot@npm:^1.1.0": - version: 1.1.2 - resolution: "cluster-key-slot@npm:1.1.2" - checksum: 10c0/d7d39ca28a8786e9e801eeb8c770e3c3236a566625d7299a47bb71113fb2298ce1039596acb82590e598c52dbc9b1f088c8f587803e697cb58e1867a95ff94d3 +"cluster-key-slot@npm:1.1.1": + version: 1.1.1 + resolution: "cluster-key-slot@npm:1.1.1" + checksum: 10c0/079b1ae86b20e2d53308a877b08de5e830722a45c07810569d0dab4955bed569da33ac9f79998289d014adf02cca7223a0647cb0ee6548a12ab3c4f9beac1377 languageName: node linkType: hard @@ -2635,12 +2991,12 @@ __metadata: languageName: node linkType: hard -"codsen-utils@npm:^1.7.0": - version: 1.7.0 - resolution: "codsen-utils@npm:1.7.0" +"codsen-utils@npm:^1.7.3": + version: 1.7.3 + resolution: "codsen-utils@npm:1.7.3" dependencies: rfdc: "npm:^1.4.1" - checksum: 10c0/b1c9ea13df3491bde7c74deeac69bedacb0170441b3e1f8e16fa627c85b0377e8e9066c993d0b9b0144db1e002870430a0979c7b399d8977539ad66e53eeaffd + checksum: 10c0/e419832d487f065a6761c8192be0c0d0c27436146d2d728440f70040d8ec52f4a1bd3514b2482dcbb4dc0e6797195b52b62cc9581a8bddafb9ab616e2e43b553 languageName: node linkType: hard @@ -2690,7 +3046,14 @@ __metadata: languageName: node linkType: hard -"cookie-parser@npm:^1.4.6": +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie-parser@npm:^1.4.7": version: 1.4.7 resolution: "cookie-parser@npm:1.4.7" dependencies: @@ -2707,14 +3070,14 @@ __metadata: languageName: node linkType: hard -"cookie-signature@npm:1.0.7, cookie-signature@npm:~1.0.6": +"cookie-signature@npm:~1.0.6, cookie-signature@npm:~1.0.7": version: 1.0.7 resolution: "cookie-signature@npm:1.0.7" checksum: 10c0/e7731ad2995ae2efeed6435ec1e22cdd21afef29d300c27281438b1eab2bae04ef0d1a203928c0afec2cee72aa36540b8747406ebe308ad23c8e8cc3c26c9c51 languageName: node linkType: hard -"cookie@npm:0.7.2, cookie@npm:~0.7.1": +"cookie@npm:0.7.2, cookie@npm:~0.7.1, cookie@npm:~0.7.2": version: 0.7.2 resolution: "cookie@npm:0.7.2" checksum: 10c0/9596e8ccdbf1a3a88ae02cf5ee80c1c50959423e1022e4e60b91dd87c622af1da309253d8abdb258fb5e3eacb4f08e579dc58b4897b8087574eee0fd35dfa5d2 @@ -2731,13 +3094,13 @@ __metadata: languageName: node linkType: hard -"cors@npm:^2.8.5": - version: 2.8.5 - resolution: "cors@npm:2.8.5" +"cors@npm:^2.8.6": + version: 2.8.6 + resolution: "cors@npm:2.8.6" dependencies: object-assign: "npm:^4" vary: "npm:^1" - checksum: 10c0/373702b7999409922da80de4a61938aabba6929aea5b6fd9096fefb9e8342f626c0ebd7507b0e8b0b311380744cc985f27edebc0a26e0ddb784b54e1085de761 + checksum: 10c0/ab2bc57b8af8ef8476682a59647f7c55c1a7d406b559ac06119aa1c5f70b96d35036864d197b24cf86e228e4547231088f1f94ca05061dbb14d89cc0bc9d4cab languageName: node linkType: hard @@ -2771,7 +3134,7 @@ __metadata: languageName: node linkType: hard -"cron@npm:^4.3.0": +"cron@npm:^4.4.0": version: 4.4.0 resolution: "cron@npm:4.4.0" dependencies: @@ -2806,7 +3169,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:2.6.9": +"debug@npm:2.6.9, debug@npm:~2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" dependencies: @@ -2815,7 +3178,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -2875,7 +3238,7 @@ __metadata: languageName: node linkType: hard -"denque@npm:^2.1.0": +"denque@npm:2.1.0": version: 2.1.0 resolution: "denque@npm:2.1.0" checksum: 10c0/f9ef81aa0af9c6c614a727cb3bd13c5d7db2af1abf9e6352045b86e85873e629690f6222f4edd49d10e4ccf8f078bbeec0794fafaf61b659c0589d0c511ec363 @@ -2903,10 +3266,17 @@ __metadata: languageName: node linkType: hard -"devtools-protocol@npm:0.0.1534754": - version: 0.0.1534754 - resolution: "devtools-protocol@npm:0.0.1534754" - checksum: 10c0/2ee96f10c9833f7e6ad0f9f66e42242129547e96c8cfe816ce508c222f4ccf4431a4b787cd6ee8174c9b2c8cc9a3c7f3b3b0a1fff96dd3cc5a16eb314027c9f7 +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"devtools-protocol@npm:0.0.1608973": + version: 0.0.1608973 + resolution: "devtools-protocol@npm:0.0.1608973" + checksum: 10c0/1e634ebf2645718c76d00d3704a4c90254b9ecbfb350b018c53729114ec0de31e88d43e43f391136a71834e4629db34ac677778044f2214bda9c433456f48a77 languageName: node linkType: hard @@ -2938,7 +3308,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.4.5": +"dotenv@npm:^16.6.1": version: 16.6.1 resolution: "dotenv@npm:16.6.1" checksum: 10c0/15ce56608326ea0d1d9414a5c8ee6dcf0fffc79d2c16422b4ac2268e7e2d76ff5a572d37ffe747c377de12005f14b3cc22361e79fc7f1061cce81f77d2c973dc @@ -3039,10 +3409,10 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.7.0": - version: 1.7.0 - resolution: "es-module-lexer@npm:1.7.0" - checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b +"es-module-lexer@npm:^2.0.0": + version: 2.1.0 + resolution: "es-module-lexer@npm:2.1.0" + checksum: 10c0/93bcf2454fa72d67fe3ccd0abef8ce7933f5840a319513418a643dd8e9c6aa8f49709cecfae02ded722805dd327232d30723a807cc52e6809d6ac697c62c29fb languageName: node linkType: hard @@ -3156,6 +3526,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:~0.28.0": + version: 0.28.0 + resolution: "esbuild@npm:0.28.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.0" + "@esbuild/android-arm": "npm:0.28.0" + "@esbuild/android-arm64": "npm:0.28.0" + "@esbuild/android-x64": "npm:0.28.0" + "@esbuild/darwin-arm64": "npm:0.28.0" + "@esbuild/darwin-x64": "npm:0.28.0" + "@esbuild/freebsd-arm64": "npm:0.28.0" + "@esbuild/freebsd-x64": "npm:0.28.0" + "@esbuild/linux-arm": "npm:0.28.0" + "@esbuild/linux-arm64": "npm:0.28.0" + "@esbuild/linux-ia32": "npm:0.28.0" + "@esbuild/linux-loong64": "npm:0.28.0" + "@esbuild/linux-mips64el": "npm:0.28.0" + "@esbuild/linux-ppc64": "npm:0.28.0" + "@esbuild/linux-riscv64": "npm:0.28.0" + "@esbuild/linux-s390x": "npm:0.28.0" + "@esbuild/linux-x64": "npm:0.28.0" + "@esbuild/netbsd-arm64": "npm:0.28.0" + "@esbuild/netbsd-x64": "npm:0.28.0" + "@esbuild/openbsd-arm64": "npm:0.28.0" + "@esbuild/openbsd-x64": "npm:0.28.0" + "@esbuild/openharmony-arm64": "npm:0.28.0" + "@esbuild/sunos-x64": "npm:0.28.0" + "@esbuild/win32-arm64": "npm:0.28.0" + "@esbuild/win32-ia32": "npm:0.28.0" + "@esbuild/win32-x64": "npm:0.28.0" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/8acd95c238ec6c4a9d16163277faf228a8994b642d187b3fe667ffbb469008e6748cde144fdc3c175bf8e78ee49e15a0ed9b9f183fdb5fcea1772f87fb1372a4 + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -3195,7 +3654,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^4.6.0": +"eslint-plugin-react-hooks@npm:^4.6.2": version: 4.6.2 resolution: "eslint-plugin-react-hooks@npm:4.6.2" peerDependencies: @@ -3204,7 +3663,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-refresh@npm:^0.4.6": +"eslint-plugin-react-refresh@npm:^0.4.26": version: 0.4.26 resolution: "eslint-plugin-react-refresh@npm:0.4.26" peerDependencies: @@ -3230,7 +3689,7 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.57.0": +"eslint@npm:^8.57.1": version: 8.57.1 resolution: "eslint@npm:8.57.1" dependencies: @@ -3363,7 +3822,7 @@ __metadata: languageName: node linkType: hard -"expect-type@npm:^1.2.2": +"expect-type@npm:^1.3.0": version: 1.3.0 resolution: "expect-type@npm:1.3.0" checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd @@ -3377,29 +3836,29 @@ __metadata: languageName: node linkType: hard -"express-session@npm:^1.18.0": - version: 1.18.2 - resolution: "express-session@npm:1.18.2" +"express-session@npm:^1.19.0": + version: 1.19.0 + resolution: "express-session@npm:1.19.0" dependencies: - cookie: "npm:0.7.2" - cookie-signature: "npm:1.0.7" - debug: "npm:2.6.9" + cookie: "npm:~0.7.2" + cookie-signature: "npm:~1.0.7" + debug: "npm:~2.6.9" depd: "npm:~2.0.0" on-headers: "npm:~1.1.0" parseurl: "npm:~1.3.3" - safe-buffer: "npm:5.2.1" + safe-buffer: "npm:~5.2.1" uid-safe: "npm:~2.1.5" - checksum: 10c0/27e17c3d365e3543ba7c1315ff14916b8347a2fd28f94817c6d2e2425923e61fa97fc23e0933015981c3358ba6f11964666249f046c4f93d22015fe2a95140ac + checksum: 10c0/b1766010a728c58ca1b93ea33d49008b0b16a8751e5767d36b5bf3dd2f7f77d9933bb55352d296edbad4824c8c65e33554f058a33f201406557c1b3615fd10dc languageName: node linkType: hard -"express@npm:^4.22.1": - version: 4.22.1 - resolution: "express@npm:4.22.1" +"express@npm:^4.22.2": + version: 4.22.2 + resolution: "express@npm:4.22.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:~1.20.3" + body-parser: "npm:~1.20.5" content-disposition: "npm:~0.5.4" content-type: "npm:~1.0.4" cookie: "npm:~0.7.1" @@ -3418,7 +3877,7 @@ __metadata: parseurl: "npm:~1.3.3" path-to-regexp: "npm:~0.1.12" proxy-addr: "npm:~2.0.7" - qs: "npm:~6.14.0" + qs: "npm:~6.15.1" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" send: "npm:~0.19.0" @@ -3428,7 +3887,7 @@ __metadata: type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10c0/ea57f512ab1e05e26b53a14fd432f65a10ec735ece342b37d0b63a7bcb8d337ffbb830ecb8ca15bcdfe423fbff88cea09786277baff200e8cde3ab40faa665cd + checksum: 10c0/d06dd4379fd217440b30f8abbe45f0e74931114c1395034f03e7d635196ecdab530d4835a1962a6aa34838d61967dc6f1f77846999bba3032373e9e714222c44 languageName: node linkType: hard @@ -3523,6 +3982,8 @@ __metadata: "fediverse-auth@workspace:.": version: 0.0.0-use.local resolution: "fediverse-auth@workspace:." + dependencies: + tsx: "npm:^4.22.4" languageName: unknown linkType: soft @@ -4037,20 +4498,18 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.6.1": - version: 5.8.2 - resolution: "ioredis@npm:5.8.2" +"ioredis@npm:^5.11.0": + version: 5.11.0 + resolution: "ioredis@npm:5.11.0" dependencies: - "@ioredis/commands": "npm:1.4.0" - cluster-key-slot: "npm:^1.1.0" - debug: "npm:^4.3.4" - denque: "npm:^2.1.0" - lodash.defaults: "npm:^4.2.0" - lodash.isarguments: "npm:^3.1.0" - redis-errors: "npm:^1.2.0" - redis-parser: "npm:^3.0.0" - standard-as-callback: "npm:^2.1.0" - checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88 + "@ioredis/commands": "npm:1.10.0" + cluster-key-slot: "npm:1.1.1" + debug: "npm:4.4.3" + denque: "npm:2.1.0" + redis-errors: "npm:1.2.0" + redis-parser: "npm:3.0.0" + standard-as-callback: "npm:2.1.0" + checksum: 10c0/6bba1eda256bafabf581089ec24c98bccc5af614b108f13fca6672ea707c36d67e7021c4f0965cbe0294e7a3964b6dbd897a95ed7f8fe82a175531219e91b84f languageName: node linkType: hard @@ -4347,6 +4806,126 @@ __metadata: languageName: node linkType: hard +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4" @@ -4372,24 +4951,10 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:^4.17.21": - version: 4.17.22 - resolution: "lodash-es@npm:4.17.22" - checksum: 10c0/5f28a262183cca43e08c580622557f393cb889386df2d8adf7c852bfdff7a84c5e629df5aa6c5c6274e83b38172f239d3e4e72e1ad27352d9ae9766627338089 - languageName: node - linkType: hard - -"lodash.defaults@npm:^4.2.0": - version: 4.2.0 - resolution: "lodash.defaults@npm:4.2.0" - checksum: 10c0/d5b77aeb702caa69b17be1358faece33a84497bcca814897383c58b28a2f8dfc381b1d9edbec239f8b425126a3bbe4916223da2a576bb0411c2cefd67df80707 - languageName: node - linkType: hard - -"lodash.isarguments@npm:^3.1.0": - version: 3.1.0 - resolution: "lodash.isarguments@npm:3.1.0" - checksum: 10c0/5e8f95ba10975900a3920fb039a3f89a5a79359a1b5565e4e5b4310ed6ebe64011e31d402e34f577eca983a1fc01ff86c926e3cbe602e1ddfc858fdd353e62d8 +"lodash-es@npm:^4.17.22": + version: 4.18.1 + resolution: "lodash-es@npm:4.18.1" + checksum: 10c0/35d4dcf87ef07f8d090f409447575800108057e360b445f590d0d25d09e3d1e33a163d2fc100d4d072b0f901d5e2fc533cd7c4bfd8eeb38a06abec693823c8b8 languageName: node linkType: hard @@ -4720,6 +5285,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.12": + version: 3.3.12 + resolution: "nanoid@npm:3.3.12" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/ba142b7b39e11e80c16dd74b0365d407880c87c1cf7e1480956981ae940ee36060fa5b6f092cd1e315184dd19244c657bd017d03327bd3c62247d691c5e8edfb + languageName: node + linkType: hard + "nanoid@npm:^5.0.9": version: 5.1.6 resolution: "nanoid@npm:5.1.6" @@ -4890,7 +5464,7 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.6.5": +"openid-client@npm:^5.7.1": version: 5.7.1 resolution: "openid-client@npm:5.7.1" dependencies: @@ -5096,6 +5670,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + "pkijs@npm:^3.2.4": version: 3.3.3 resolution: "pkijs@npm:3.3.3" @@ -5110,6 +5691,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.15": + version: 8.5.15 + resolution: "postcss@npm:8.5.15" + dependencies: + nanoid: "npm:^3.3.12" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/7f2e63ae22fbe43aace1bf652bd99da4e90737c64194d49e51ddc9cd0f9e51ff2861a7d734379b494deffa03a880a5c65eec70bc29ee9ebaa7136dde3eee8f31 + languageName: node + linkType: hard + "postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" @@ -5128,7 +5720,7 @@ __metadata: languageName: node linkType: hard -"prisma@npm:^5.13.0": +"prisma@npm:^5.22.0": version: 5.22.0 resolution: "prisma@npm:5.22.0" dependencies: @@ -5228,34 +5820,34 @@ __metadata: languageName: node linkType: hard -"puppeteer-core@npm:24.34.0": - version: 24.34.0 - resolution: "puppeteer-core@npm:24.34.0" +"puppeteer-core@npm:24.43.1": + version: 24.43.1 + resolution: "puppeteer-core@npm:24.43.1" dependencies: - "@puppeteer/browsers": "npm:2.11.0" - chromium-bidi: "npm:12.0.1" + "@puppeteer/browsers": "npm:2.13.2" + chromium-bidi: "npm:14.0.0" debug: "npm:^4.4.3" - devtools-protocol: "npm:0.0.1534754" - typed-query-selector: "npm:^2.12.0" - webdriver-bidi-protocol: "npm:0.3.10" - ws: "npm:^8.18.3" - checksum: 10c0/232d1e7d6af2099650fdaa849a487dae7bbb6df43ce35381849e7c08ca4eaf919a77fbb927bb416715fb5c6e396b5539ddc90d6d2d9750fe8fea558d82817801 + devtools-protocol: "npm:0.0.1608973" + typed-query-selector: "npm:^2.12.2" + webdriver-bidi-protocol: "npm:0.4.1" + ws: "npm:^8.20.0" + checksum: 10c0/720cdc67a3c70b1743e27783ede0e5f03323ff75e3f1332b645900f78210091fb57c49d4ac42d61d3550a0352294d9ade08a30647290e98632e70fe71225489d languageName: node linkType: hard -"puppeteer@npm:^24.33.0": - version: 24.34.0 - resolution: "puppeteer@npm:24.34.0" +"puppeteer@npm:^24.35.0": + version: 24.43.1 + resolution: "puppeteer@npm:24.43.1" dependencies: - "@puppeteer/browsers": "npm:2.11.0" - chromium-bidi: "npm:12.0.1" + "@puppeteer/browsers": "npm:2.13.2" + chromium-bidi: "npm:14.0.0" cosmiconfig: "npm:^9.0.0" - devtools-protocol: "npm:0.0.1534754" - puppeteer-core: "npm:24.34.0" - typed-query-selector: "npm:^2.12.0" + devtools-protocol: "npm:0.0.1608973" + puppeteer-core: "npm:24.43.1" + typed-query-selector: "npm:^2.12.2" bin: puppeteer: lib/cjs/puppeteer/node/cli.js - checksum: 10c0/bba31e2839a703e03ef7f0865dee8fc82836eac0a889e57a29d3493c0010bddccd6f3a7359ff9191d447fbd13db35895bfb866c23da8e648a2404aafdf6d2726 + checksum: 10c0/14e6efbd33ee0f6af377f3f17a11bd409925ef8504ccbc06318a995aba6bc4a3caa3f4d84ca323cdbaa396a20f1f91f284e8501723d557cfdb8c354985465675 languageName: node linkType: hard @@ -5275,12 +5867,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:~6.14.0": - version: 6.14.0 - resolution: "qs@npm:6.14.0" +"qs@npm:~6.15.1": + version: 6.15.2 + resolution: "qs@npm:6.15.2" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + checksum: 10c0/e6fd5f6f0aab06d480fe9ab15cebfc4ce4235303e2f91dc69a8f7f4df1e668a61c11d1cfbabacf4295cbbeb7b670ed23db45307480726259761f98e5695e93a7 languageName: node linkType: hard @@ -5319,42 +5911,42 @@ __metadata: languageName: node linkType: hard -"ranges-apply@npm:^7.1.0": - version: 7.1.0 - resolution: "ranges-apply@npm:7.1.0" +"ranges-apply@npm:^7.1.3": + version: 7.1.3 + resolution: "ranges-apply@npm:7.1.3" dependencies: - ranges-merge: "npm:^9.1.0" + ranges-merge: "npm:^9.1.3" tiny-invariant: "npm:^1.3.3" - checksum: 10c0/956332a03b89439980b53177ddb1c2167e08a0e8db5036e514d51f83e9a42532db2056223e6e2adcf5e1df7bc961dc9d431272fa80881380f03df7a1ee2da8ed + checksum: 10c0/6a26b60eaddd57b314299de713327ee0ee2beffdc6feb04349f9bdf4f15eecae3573645c270690e36d2ec6670f1524dfe46e322bccb7a62ae278f8af0b0d2179 languageName: node linkType: hard -"ranges-merge@npm:^9.1.0": - version: 9.1.0 - resolution: "ranges-merge@npm:9.1.0" +"ranges-merge@npm:^9.1.3": + version: 9.1.3 + resolution: "ranges-merge@npm:9.1.3" dependencies: - ranges-push: "npm:^7.1.0" - ranges-sort: "npm:^6.1.0" - checksum: 10c0/9bfe9f3c24cf4ef5ea2f9945a872ef093c3d57113d7225720a07fd9f16f27032833b54bf26f6b9832381e9eda7d52e4ed539ec5284cb7d19ff0a11377ed44d3f + ranges-push: "npm:^7.1.3" + ranges-sort: "npm:^6.1.3" + checksum: 10c0/a95d513955f8b1363a7beab05903264c5340fd615c9b0b7999bb5ef324ba5c952205e50814af92ecb4c7cba7fa15641d77560a034a8c32a6ab48df7548f7d4a8 languageName: node linkType: hard -"ranges-push@npm:^7.1.0": - version: 7.1.0 - resolution: "ranges-push@npm:7.1.0" +"ranges-push@npm:^7.1.3": + version: 7.1.3 + resolution: "ranges-push@npm:7.1.3" dependencies: - codsen-utils: "npm:^1.7.0" - ranges-sort: "npm:^6.1.0" - string-collapse-leading-whitespace: "npm:^7.1.0" - string-trim-spaces-only: "npm:^5.1.0" - checksum: 10c0/2fd9a2dcb61ff821e739fde119df6b147d19193152f80a836717ffe6b2a15e586543b33c9bb2c0ce795a6f1dcee8ae5db92cceec7c01fd0180f08929e7dbe1b9 + codsen-utils: "npm:^1.7.3" + ranges-sort: "npm:^6.1.3" + string-collapse-leading-whitespace: "npm:^7.1.3" + string-trim-spaces-only: "npm:^5.1.3" + checksum: 10c0/09b3c3b1a452d317d707bfb0ab733af92af2158cbfaede588fdc40ebbd794266aad3ed35a92c58d21ee96202926577ced5cc7441349dbad201c11b55fc814d48 languageName: node linkType: hard -"ranges-sort@npm:^6.1.0": - version: 6.1.0 - resolution: "ranges-sort@npm:6.1.0" - checksum: 10c0/696188e1d88d5655e517badcb9caf941f100dff44074e9d729fdee2b369d22b301e650db054972c9b7fe2aa3d82210aa8d24e6750c781d111b35b8b1cd07f05c +"ranges-sort@npm:^6.1.3": + version: 6.1.3 + resolution: "ranges-sort@npm:6.1.3" + checksum: 10c0/dfe17de192ee717240ecbf12ad3d12c109d09e08242e0076a5d9610ac11850a1fd94495efbc96c77b53135b044b11c0227fd3c4b9487510e5a9e7cc6d564c514 languageName: node linkType: hard @@ -5391,7 +5983,7 @@ __metadata: languageName: node linkType: hard -"react-dom@npm:^18.2.0": +"react-dom@npm:^18.3.1": version: 18.3.1 resolution: "react-dom@npm:18.3.1" dependencies: @@ -5417,27 +6009,27 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.23.0": - version: 6.30.2 - resolution: "react-router-dom@npm:6.30.2" +"react-router-dom@npm:^6.30.4": + version: 6.30.4 + resolution: "react-router-dom@npm:6.30.4" dependencies: - "@remix-run/router": "npm:1.23.1" - react-router: "npm:6.30.2" + "@remix-run/router": "npm:1.23.3" + react-router: "npm:6.30.4" peerDependencies: react: ">=16.8" react-dom: ">=16.8" - checksum: 10c0/d0c6edf4e2aa7639b4a4f64a7747f03d8861bdf4857e8981b1cff1451b7cb91fcdcd7e315a6e3df910271b2f5071825d2aec218d5f7890f2269fc074f198e42a + checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822 languageName: node linkType: hard -"react-router@npm:6.30.2": - version: 6.30.2 - resolution: "react-router@npm:6.30.2" +"react-router@npm:6.30.4": + version: 6.30.4 + resolution: "react-router@npm:6.30.4" dependencies: - "@remix-run/router": "npm:1.23.1" + "@remix-run/router": "npm:1.23.3" peerDependencies: react: ">=16.8" - checksum: 10c0/cff5ea92d248d2230adc46d4e2ed3fbeddfaf1ae2e63411da8b7ea6ddc207d71dbc522c05c492e671e553e2153934f4ab180ac02bd36205b274e097f2cfe6fc4 + checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33 languageName: node linkType: hard @@ -5456,7 +6048,7 @@ __metadata: languageName: node linkType: hard -"react@npm:^18.2.0": +"react@npm:^18.3.1": version: 18.3.1 resolution: "react@npm:18.3.1" dependencies: @@ -5465,14 +6057,14 @@ __metadata: languageName: node linkType: hard -"redis-errors@npm:^1.0.0, redis-errors@npm:^1.2.0": +"redis-errors@npm:1.2.0, redis-errors@npm:^1.0.0": version: 1.2.0 resolution: "redis-errors@npm:1.2.0" checksum: 10c0/5b316736e9f532d91a35bff631335137a4f974927bb2fb42bf8c2f18879173a211787db8ac4c3fde8f75ed6233eb0888e55d52510b5620e30d69d7d719c8b8a7 languageName: node linkType: hard -"redis-parser@npm:^3.0.0": +"redis-parser@npm:3.0.0": version: 3.0.0 resolution: "redis-parser@npm:3.0.0" dependencies: @@ -5583,6 +6175,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:1.0.3": + version: 1.0.3 + resolution: "rolldown@npm:1.0.3" + dependencies: + "@oxc-project/types": "npm:=0.133.0" + "@rolldown/binding-android-arm64": "npm:1.0.3" + "@rolldown/binding-darwin-arm64": "npm:1.0.3" + "@rolldown/binding-darwin-x64": "npm:1.0.3" + "@rolldown/binding-freebsd-x64": "npm:1.0.3" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.3" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.3" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.3" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.3" + "@rolldown/binding-linux-s390x-gnu": "npm:1.0.3" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.3" + "@rolldown/binding-linux-x64-musl": "npm:1.0.3" + "@rolldown/binding-openharmony-arm64": "npm:1.0.3" + "@rolldown/binding-wasm32-wasi": "npm:1.0.3" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.3" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.3" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/5f9dd47b7abf203b16bc600db68542f245e974c800e59ff50b76157d1dada1403657690435b036fabca88e93d13a67c31abe5cfaa6f61ce33717f61720204cdf + languageName: node + linkType: hard + "rollup@npm:^4.43.0": version: 4.54.0 resolution: "rollup@npm:4.54.0" @@ -5673,7 +6323,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1": +"safe-buffer@npm:5.2.1, safe-buffer@npm:~5.2.1": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -5707,7 +6357,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.6.0, semver@npm:^7.7.3": +"semver@npm:^7.3.5, semver@npm:^7.6.0": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -5716,6 +6366,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.4": + version: 7.8.1 + resolution: "semver@npm:7.8.1" + bin: + semver: bin/semver.js + checksum: 10c0/92d6871d6347e1f99d0ba396a70f2545ccf2a032cda3d378fa0699edf7506b5c6d266aed55c8b88e72bd91a30d2351e4f39db479375374430fcdc4b58f4e3c1a + languageName: node + linkType: hard + "send@npm:~0.19.0, send@npm:~0.19.1": version: 0.19.2 resolution: "send@npm:0.19.2" @@ -5913,7 +6572,7 @@ __metadata: languageName: node linkType: hard -"standard-as-callback@npm:^2.1.0": +"standard-as-callback@npm:2.1.0": version: 2.1.0 resolution: "standard-as-callback@npm:2.1.0" checksum: 10c0/012677236e3d3fdc5689d29e64ea8a599331c4babe86956bf92fc5e127d53f85411c5536ee0079c52c43beb0026b5ce7aa1d834dd35dd026e82a15d1bcaead1f @@ -5934,10 +6593,10 @@ __metadata: languageName: node linkType: hard -"std-env@npm:^3.10.0": - version: 3.10.0 - resolution: "std-env@npm:3.10.0" - checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f +"std-env@npm:^4.0.0-rc.1": + version: 4.1.0 + resolution: "std-env@npm:4.1.0" + checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 languageName: node linkType: hard @@ -5952,42 +6611,42 @@ __metadata: languageName: node linkType: hard -"string-collapse-leading-whitespace@npm:^7.1.0": - version: 7.1.0 - resolution: "string-collapse-leading-whitespace@npm:7.1.0" - checksum: 10c0/46899fc10df07c1c8fab4de0f6695465412dcaa538d0a8eab22929f6a187bc2f668119f320c74c84e1553452b94a1557174414efc5be1482f885c59d0a28a55d +"string-collapse-leading-whitespace@npm:^7.1.3": + version: 7.1.3 + resolution: "string-collapse-leading-whitespace@npm:7.1.3" + checksum: 10c0/c72cb7f72b78db2b452b01818c3d572b4ae3d0db3f3ece6efb63d35c668a596288c7aaed03ea4af1dbc69ce2daa2e3e5309266f09ba7b87cb3faf33c222b3ff8 languageName: node linkType: hard -"string-left-right@npm:^6.1.0": - version: 6.1.0 - resolution: "string-left-right@npm:6.1.0" +"string-left-right@npm:^6.1.3": + version: 6.1.3 + resolution: "string-left-right@npm:6.1.3" dependencies: - codsen-utils: "npm:^1.7.0" + codsen-utils: "npm:^1.7.3" rfdc: "npm:^1.4.1" - checksum: 10c0/45d13b3c015ec259f0004b63c9104cae8194960d8c7d4f2993047fde4fcd8e4e52ef55336495a200353f903f3b8caab1e0f1b6d39d01ac947edce4748dc3b8f8 + checksum: 10c0/b04aac049a5865087ccd82d5667b2b9cbb3261766829f093eec0a4d35a08d0002cd7e8bc3220bc9421fa71f5743ec393411c06f3bb8c41761a0def3a128a0771 languageName: node linkType: hard -"string-strip-html@npm:^13.4.12": - version: 13.5.0 - resolution: "string-strip-html@npm:13.5.0" +"string-strip-html@npm:^13.5.3": + version: 13.5.3 + resolution: "string-strip-html@npm:13.5.3" dependencies: "@types/lodash-es": "npm:^4.17.12" - codsen-utils: "npm:^1.7.0" + codsen-utils: "npm:^1.7.3" html-entities: "npm:^2.6.0" - lodash-es: "npm:^4.17.21" - ranges-apply: "npm:^7.1.0" - ranges-push: "npm:^7.1.0" - string-left-right: "npm:^6.1.0" - checksum: 10c0/501c62703b4a0418b513dd4441a172bd4716f3d314c722fbe050bf85f1e6a3317d2e681acac0e4175d425b9114512b36d4710ce6e5010f4a36fb0fbd15c5351a + lodash-es: "npm:^4.17.22" + ranges-apply: "npm:^7.1.3" + ranges-push: "npm:^7.1.3" + string-left-right: "npm:^6.1.3" + checksum: 10c0/5f9864fdf9f97a230ccc11e6704d6939fa033c92118ef9be0651b1abe28c0eb9a86053f8747fa743677f5d103286c6e867d21405087bd8c12e1e656501492835 languageName: node linkType: hard -"string-trim-spaces-only@npm:^5.1.0": - version: 5.1.0 - resolution: "string-trim-spaces-only@npm:5.1.0" - checksum: 10c0/449f7b1ab17a3cb957939b95e0c616e894b038fc60d55ef9355eedad09faffa2d48c067d222fb8dde4beb83ed7084b0a1951fef00098b92fc8109a9e44f44d1b +"string-trim-spaces-only@npm:^5.1.3": + version: 5.1.3 + resolution: "string-trim-spaces-only@npm:5.1.3" + checksum: 10c0/66d119d2561b47a23f1ea3443c60e07bd93cd871d5c9bfb729cc89c4c482e7b0c11ede6da7f33a067e4c1a27100d942259ffd0c6dcae0d1368d15c2d46fa48fb languageName: node linkType: hard @@ -6136,10 +6795,20 @@ __metadata: languageName: node linkType: hard -"tinyrainbow@npm:^3.0.3": - version: 3.0.3 - resolution: "tinyrainbow@npm:3.0.3" - checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c +"tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.1.0": + version: 3.1.0 + resolution: "tinyrainbow@npm:3.1.0" + checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 languageName: node linkType: hard @@ -6168,7 +6837,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.8.1": +"tslib@npm:^2.0.1, tslib@npm:^2.4.0, tslib@npm:^2.8.1": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -6182,7 +6851,7 @@ __metadata: languageName: node linkType: hard -"tsx@npm:^4.21.0, tsx@npm:^4.9.3": +"tsx@npm:^4.21.0": version: 4.21.0 resolution: "tsx@npm:4.21.0" dependencies: @@ -6198,6 +6867,21 @@ __metadata: languageName: node linkType: hard +"tsx@npm:^4.22.4": + version: 4.22.4 + resolution: "tsx@npm:4.22.4" + dependencies: + esbuild: "npm:~0.28.0" + fsevents: "npm:~2.3.3" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10c0/3df31eb4929ff501b40b122163705b201ea57a492581e14312ae95d21eb015b33ded46a3fd564f9c89a0e8083186987fc4f10dd38182c4ad6241605974f6c927 + languageName: node + linkType: hard + "type-check@npm:^0.4.0, type-check@npm:~0.4.0": version: 0.4.0 resolution: "type-check@npm:0.4.0" @@ -6224,14 +6908,14 @@ __metadata: languageName: node linkType: hard -"typed-query-selector@npm:^2.12.0": - version: 2.12.0 - resolution: "typed-query-selector@npm:2.12.0" - checksum: 10c0/069509887ecfff824a470f5f93d300cc9223cb059a36c47ac685f2812c4c9470340e07615893765e4264cef1678507532fa78f642fd52f276b589f7f5d791f79 +"typed-query-selector@npm:^2.12.2": + version: 2.12.2 + resolution: "typed-query-selector@npm:2.12.2" + checksum: 10c0/2710369ada5bde578606b043eefb8a6d7f9178630ae4950a4dfd4f70cceb8196d5bbc735a905cd3e06953127e4dd1825c3e0be06d64e5a6e5609965cffee701d languageName: node linkType: hard -"typescript@npm:^5.2.2, typescript@npm:^5.9.3": +"typescript@npm:^5.9.3": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -6241,7 +6925,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -6366,9 +7050,66 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0, vite@npm:^7.3.0": - version: 7.3.0 - resolution: "vite@npm:7.3.0" +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": + version: 8.0.16 + resolution: "vite@npm:8.0.16" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.15" + rolldown: "npm:1.0.3" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/d75be3fbe2f63e6a8145325970338afaf0dd4d96ba9175c13f9a286fd5f95afc489401b693e4fa6c0899a4dd0e137be91cdf9401a40a635563911ad5036e3467 + languageName: node + linkType: hard + +"vite@npm:^7.3.5": + version: 7.3.5 + resolution: "vite@npm:7.3.5" dependencies: esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" @@ -6417,44 +7158,47 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/0457c196cdd5761ec351c0f353945430fbad330e615b9eeab729c8ae163334f18acdc1d9cd7d9d673dbf111f07f6e4f0b25d4ac32360e65b4a6df9991046f3ff + checksum: 10c0/4ad700649ed2ebd1e726d32f3f6e41eecbec4e29bcb5977805f3d43a5659280518aa431b0fc61adc1879df4fe1978d7cfc7dbbe54cdf014022385052967721e8 languageName: node linkType: hard -"vitest@npm:^4.0.15": - version: 4.0.16 - resolution: "vitest@npm:4.0.16" +"vitest@npm:^4.1.8": + version: 4.1.8 + resolution: "vitest@npm:4.1.8" dependencies: - "@vitest/expect": "npm:4.0.16" - "@vitest/mocker": "npm:4.0.16" - "@vitest/pretty-format": "npm:4.0.16" - "@vitest/runner": "npm:4.0.16" - "@vitest/snapshot": "npm:4.0.16" - "@vitest/spy": "npm:4.0.16" - "@vitest/utils": "npm:4.0.16" - es-module-lexer: "npm:^1.7.0" - expect-type: "npm:^1.2.2" + "@vitest/expect": "npm:4.1.8" + "@vitest/mocker": "npm:4.1.8" + "@vitest/pretty-format": "npm:4.1.8" + "@vitest/runner": "npm:4.1.8" + "@vitest/snapshot": "npm:4.1.8" + "@vitest/spy": "npm:4.1.8" + "@vitest/utils": "npm:4.1.8" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" magic-string: "npm:^0.30.21" obug: "npm:^2.1.1" pathe: "npm:^2.0.3" picomatch: "npm:^4.0.3" - std-env: "npm:^3.10.0" + std-env: "npm:^4.0.0-rc.1" tinybench: "npm:^2.9.0" tinyexec: "npm:^1.0.2" tinyglobby: "npm:^0.2.15" - tinyrainbow: "npm:^3.0.3" - vite: "npm:^6.0.0 || ^7.0.0" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@opentelemetry/api": ^1.9.0 "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 - "@vitest/browser-playwright": 4.0.16 - "@vitest/browser-preview": 4.0.16 - "@vitest/browser-webdriverio": 4.0.16 - "@vitest/ui": 4.0.16 + "@vitest/browser-playwright": 4.1.8 + "@vitest/browser-preview": 4.1.8 + "@vitest/browser-webdriverio": 4.1.8 + "@vitest/coverage-istanbul": 4.1.8 + "@vitest/coverage-v8": 4.1.8 + "@vitest/ui": 4.1.8 happy-dom: "*" jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: "@edge-runtime/vm": optional: true @@ -6468,22 +7212,28 @@ __metadata: optional: true "@vitest/browser-webdriverio": optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true "@vitest/ui": optional: true happy-dom: optional: true jsdom: optional: true + vite: + optional: false bin: vitest: vitest.mjs - checksum: 10c0/b195c272198f7957c11186eb70ee78e2ec0f4524b4b5306ca8f05e41b3d84c6a4a15d02fca58d82f2b32ba61f610ae8a2a23d463a8336d7323e4832db5eef223 + checksum: 10c0/f459c500f8818c7a2318cd23228b4e5c0b5efb25bf8e5cb7f116c6d26e51482b2f800a8bb19837c0b5f0d05c51519edbf502bc8ceb5bd86868e8facf1d2c498e languageName: node linkType: hard -"webdriver-bidi-protocol@npm:0.3.10": - version: 0.3.10 - resolution: "webdriver-bidi-protocol@npm:0.3.10" - checksum: 10c0/fc66ba53b31c78a73ff7841303948894b77263051c146fed4331596b95f67a2e96dfaeb895483002bb152317d9cfbd215d9a66ef114f66dc17de2c32ac5ef33c +"webdriver-bidi-protocol@npm:0.4.1": + version: 0.4.1 + resolution: "webdriver-bidi-protocol@npm:0.4.1" + checksum: 10c0/16e6f0be41d725a465f02ad9b7bd9a6ce44466b381f31423485ad4d2e2bd0c5fb6f16bacb1867afbe45a41f3fe34960be890bcd28d32116ec2f9ff56794f28f3 languageName: node linkType: hard @@ -6546,9 +7296,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.18.3": - version: 8.18.3 - resolution: "ws@npm:8.18.3" +"ws@npm:^8.20.0": + version: 8.21.0 + resolution: "ws@npm:8.21.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -6557,7 +7307,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + checksum: 10c0/ef4a243476283fc49bc7550966c4af4aa0eef56273837211e700de3b664e08604a760cdddcb5ba43c049140e74ccfec5b0ee0bb439e08c2adf9138902fdde5f9 languageName: node linkType: hard -- GitLab From 12393c641224c308ac1087b8c7831f6b977bf50f Mon Sep 17 00:00:00 2001 From: Grant Date: Thu, 11 Jun 2026 17:28:09 -0600 Subject: [PATCH 03/10] fix typo in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c2cc17..c88b0ab 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Leverages OpenID Connect Auto Discovery & Dynamic Client Registration - frontend: copy `frontend/example.env` to `frontend/.env` - backend: copy `backend/example.env` to `backend/.env` - backend: run `yarn workspace @fediverse-auth/backend prisma migrate dev` -- `yarn start frontend:dev` -- `yarn start backend:dev` +- `yarn run frontend:dev` +- `yarn run backend:dev` ## Deployment -- GitLab From 3587424a2535f26713c318f80580daee828cde55 Mon Sep 17 00:00:00 2001 From: Grant Date: Fri, 12 Jun 2026 15:16:28 -0600 Subject: [PATCH 04/10] add devhomeutils and initial apub stub --- backend/package.json | 2 +- backend/src/controllers/DevController.ts | 155 +++++++++++++++++++++++ backend/src/lib/apub/utils.stub.ts | 73 +++++++++++ backend/src/lib/express.ts | 7 + backend/src/lib/oidc.ts | 14 +- frontend/src/App.tsx | 3 + frontend/src/dev/DevHomeUtils.tsx | 57 +++++++++ yarn.lock | 44 +++---- 8 files changed, 327 insertions(+), 28 deletions(-) create mode 100644 backend/src/controllers/DevController.ts create mode 100644 backend/src/lib/apub/utils.stub.ts create mode 100644 frontend/src/dev/DevHomeUtils.tsx diff --git a/backend/package.json b/backend/package.json index 3b6c128..fcedb95 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,7 +22,7 @@ "express-session": "^1.19.0", "ioredis": "^5.11.0", "oidc-provider": "^8.8.1", - "openid-client": "^5.7.1", + "openid-client": "^6.8.4", "string-strip-html": "^13.5.3" }, "devDependencies": { diff --git a/backend/src/controllers/DevController.ts b/backend/src/controllers/DevController.ts new file mode 100644 index 0000000..9c19f71 --- /dev/null +++ b/backend/src/controllers/DevController.ts @@ -0,0 +1,155 @@ +import e, { Router } from "express"; +import * as OIDClient from "openid-client"; +import { prisma } from "../lib/prisma.js"; +import { OidcTypes } from "../lib/adapter.js"; + +const DEV_CLIENT = { + client_id: "dev", + client_secret: "dev", + grant_types: ["authorization_code"], + subject_type: "public", + redirect_uris: [], + response_types: ["code"], + application_type: "web", + require_auth_time: false, + client_id_issued_at: Date.now(), + client_secret_expires_at: 0, + post_logout_redirect_uris: [], + token_endpoint_auth_method: "client_secret_basic", + id_token_signed_response_alg: "RS256", + require_pushed_authorization_requests: false, +}; + +const OPENID_CONFIG = new OIDClient.Configuration( + { issuer: "http://localhost:5173" }, + "dev", + { + client_secret: "dev", + }, +); + +const app = Router(); + +app.get("/test-exchange", (req, res) => { + // oauth4webapi complains as it's a non-https url + // const url = OIDClient.buildAuthorizationUrl(OPENID_CONFIG, { + // redirect_uri: "http://localhost:5173/api/dev/callback", + // scope: "openid instance", + // prompt: "consent", + // }); + + const url = new URL("http://localhost:5173/api/oidc/auth"); + url.searchParams.set( + "redirect_uri", + "http://localhost:5173/api/dev/callback", + ); + url.searchParams.set("prompt", "consent"); + url.searchParams.set("scope", "openid instance"); + url.searchParams.set("client_id", "dev"); + url.searchParams.set("response_type", "code"); + + res.redirect(String(url)); +}); + +/** + * Auth flow demo callback + */ +app.get("/callback", async (req, res) => { + const send = (res: e.Response, data: unknown) => { + res.contentType("html").send(` +

Dev Callback

+
${JSON.stringify(data, null, 2)}
+ Back Home`); + }; + + const exchange = await OIDClient.authorizationCodeGrant( + OPENID_CONFIG, + new URL(req.url), + {}, + new URLSearchParams(JSON.stringify(req.query)), + ).catch((e) => { + console.warn("/api/dev/callback authorizationCodeGrant request error", e); + return String(e); + }); + + if (typeof exchange === "string") { + return send(res.status(400), { + state: "token exchange failed", + exchange, + }); + } + + const userInfo = await OIDClient.fetchUserInfo( + OPENID_CONFIG, + exchange.access_token, + OIDClient.skipSubjectCheck, + ).catch((e) => { + console.warn("/api/dev/callback userInfo request error", e); + return String(e); + }); + + if (typeof userInfo === "string") { + return send(res.status(400), { + state: "userinfo request failed", + exchange, + userInfo, + }); + } + + send(res, { + state: "success", + exchange, + userInfo, + }); +}); + +/** + * create a development client + */ +app.post("/ensure-dev-client", async (req, res) => { + const DEFAULT_REDIRECT_URIS = [ + "http://localhost:3000", + "http://localhost:8008/_synapse/client/oidc/callback", + "http://localhost:5173/api/dev/callback", + ]; + + if ("redirect_uri" in req.body) { + if (!Array.isArray(req.body.redirect_uri)) { + req.body.redirect_uri = [String(req.body.redirect_uri)]; + } + + for (const input of req.body.redirect_uri) { + DEFAULT_REDIRECT_URIS.push(String(input)); + } + } + + await prisma.oidcModel + .upsert({ + where: { + id: DEV_CLIENT.client_id, + }, + create: { + id: DEV_CLIENT.client_id, + type: OidcTypes["Client"], + payload: { ...DEV_CLIENT, redirect_uris: DEFAULT_REDIRECT_URIS }, + }, + update: { + payload: { ...DEV_CLIENT, redirect_uris: DEFAULT_REDIRECT_URIS }, + }, + }) + .then(() => { + console.log("Created/upserted dev oidc client"); + res.status(201).json({ + success: true, + }); + }) + .catch((e) => { + console.error("Failed to create dev oidc client", e); + res.status(500).json({ + success: false, + error: String(e), + }); + }); +}); + +export default app; diff --git a/backend/src/lib/apub/utils.stub.ts b/backend/src/lib/apub/utils.stub.ts new file mode 100644 index 0000000..452f74d --- /dev/null +++ b/backend/src/lib/apub/utils.stub.ts @@ -0,0 +1,73 @@ +// a stub implementation of the activitypub utils +// the stub implementation is intended for non-federation development use +// to avoid having to setup full TLS-termination reverse proxy, this will +// supply the responses to authenticate as any user + +import { + Actor, + ChatMessage, + Context, + Note, + Person, + ResourceDescriptor, +} from "@fedify/fedify"; +import { IProfile } from "../instance/userMeta.js"; +import { USER_IDENTIFIER } from "./federation.js"; +import { APub } from "./utils.js"; +import { AuthSession } from "../../controllers/AuthSession.js"; + +export class APubStub extends APub { + constructor() { + super(null as any); + } + + static options = () => ({}); + + static get accountHandle() { + return USER_IDENTIFIER + "@localhost"; + } + + static get() { + return new APubStub(); + } + + static async buildProfile( + identifier: string | URL, + ): Promise { + const profile: IProfile = { + sub: String(identifier), + }; + + return profile; + } + + static async lookupWebfinger( + identifier: string | URL, + ): Promise { + return { + subject: String(identifier), + }; + } + + static async lookupActor(identifier: string | URL): Promise { + return new Person({ + id: new URL(identifier), + }); + } + + static async sendDM(session: AuthSession) {} + + static async deleteDM(session: AuthSession) {} + + async sendChatMessage(id: string, target: Actor, content: ChatMessage) {} + + async sendNote(id: string, target: Actor, content: Note) {} + + build( + type: "ChatMessage" | "Note", + opts: { id: string; one_time_code: string; createdAt: Date; target: Actor }, + ): any {} +} + +const _staticTypeCheck: typeof APub = APubStub; +_staticTypeCheck.get(); // to ignore "unused variable" but still trigger type check \ No newline at end of file diff --git a/backend/src/lib/express.ts b/backend/src/lib/express.ts index d8ec773..e979ce3 100644 --- a/backend/src/lib/express.ts +++ b/backend/src/lib/express.ts @@ -15,6 +15,7 @@ import { APub } from "./apub/utils.js"; import { APIAdminRouter } from "./api_admin.js"; import { Handoff } from "../handoff/index.js"; import { DocLinks } from "./DocLinks.js"; +import DevController from "../controllers/DevController.js"; export const app = express(); @@ -126,6 +127,10 @@ const interactionMiddleware = ( }; if (process.env.NODE_ENV === "development") { + console.log( + "Development Mode: /_dev & /api/dev endpoints have been activated", + ); + // expose the internals of the interaction middleware for the vite dev server to access app.post("/_dev/interaction/:uid", async (req, res) => { const middleware = await interactionMiddleware(req, res); @@ -150,6 +155,8 @@ if (process.env.NODE_ENV === "development") { break; } }); + + app.use("/api/dev", DevController); } app.use("/interaction/:uid", async (req, res, next) => { diff --git a/backend/src/lib/oidc.ts b/backend/src/lib/oidc.ts index 480fa89..bcd6d75 100644 --- a/backend/src/lib/oidc.ts +++ b/backend/src/lib/oidc.ts @@ -1,7 +1,7 @@ import Provider from "oidc-provider"; import fs from "node:fs"; +import * as OIDClient from "openid-client"; import { PrismaAdapter } from "./adapter.js"; -import { Issuer } from "openid-client"; import { IInstance, getInstanceMeta } from "./instance/instanceMeta.js"; import { IProfile, getUserMeta } from "./instance/userMeta.js"; import { ShadowAPI, UserLoginError } from "./shadow.js"; @@ -231,15 +231,21 @@ oidc.use(async (ctx, next) => { * @returns */ export const doesInstanceSupportOIDC = async (instance_hostname: string) => { - let issuer: Issuer; + let issuer: OIDClient.Configuration; try { - issuer = await Issuer.discover("https://" + instance_hostname); + // we pass an empty string into the client_id as we will + // not be using this instance to perform requests, + // only to check if the instance supports openid + issuer = await OIDClient.discovery( + new URL("https://" + instance_hostname), + "", + ); } catch (e) { return false; } - if (typeof issuer.metadata.registration_endpoint === "undefined") { + if (typeof issuer.serverMetadata().registration_endpoint === "undefined") { return false; } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a3ceb21..a578a44 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,6 +15,7 @@ import React, { useEffect, useState } from "react"; import { PageWrapper } from "./PageWrapper"; import { api } from "./lib/utils"; import { DocLinks } from "./lib/DocLinks"; +import DevHomeUtils from "./dev/DevHomeUtils"; const UserInfo = () => { const [avatarEl, setAvatarEl] = useState(null); @@ -114,6 +115,8 @@ function App() { You can find my source here. + + {import.meta.env.DEV && } ); } diff --git a/frontend/src/dev/DevHomeUtils.tsx b/frontend/src/dev/DevHomeUtils.tsx new file mode 100644 index 0000000..dafe764 --- /dev/null +++ b/frontend/src/dev/DevHomeUtils.tsx @@ -0,0 +1,57 @@ +import { Button, Typography } from "@mui/material"; +import { useCallback } from "react"; + +const DevHomeUtils = () => { + return ( +
+ development utils + + +
+ ); +}; + +const CreateDevClient = () => { + const handleCreate = useCallback(() => { + const redirect_uri = prompt( + "Any additional redirect_uris? (comma separated with schemes) (leave blank or cancel to use defaults)", + ); + + fetch("/api/dev/ensure-dev-client", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + redirect_uri: redirect_uri?.trim() ? redirect_uri : undefined, + }), + }) + .then((a) => a.json()) + .then((data) => { + if (data.success) { + alert("Created dev client (client_id & client_secret: dev)"); + } else { + alert("Failed to create dev client (check console): " + data.error); + } + }); + }, []); + + return ( + + ); +}; + +const ExecAuthFlow = () => { + return ( + + ); +}; + +export default DevHomeUtils; diff --git a/yarn.lock b/yarn.lock index 59c546f..2e7f8f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -817,7 +817,7 @@ __metadata: express-session: "npm:^1.19.0" ioredis: "npm:^5.11.0" oidc-provider: "npm:^8.8.1" - openid-client: "npm:^5.7.1" + openid-client: "npm:^6.8.4" prisma: "npm:^5.22.0" string-strip-html: "npm:^13.5.3" tsx: "npm:^4.22.4" @@ -4619,13 +4619,6 @@ __metadata: languageName: node linkType: hard -"jose@npm:^4.15.9": - version: 4.15.9 - resolution: "jose@npm:4.15.9" - checksum: 10c0/4ed4ddf4a029db04bd167f2215f65d7245e4dc5f36d7ac3c0126aab38d66309a9e692f52df88975d99429e357e5fd8bab340ff20baab544d17684dd1d940a0f4 - languageName: node - linkType: hard - "jose@npm:^5.9.6": version: 5.10.0 resolution: "jose@npm:5.10.0" @@ -4633,6 +4626,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^6.2.2": + version: 6.2.3 + resolution: "jose@npm:6.2.3" + checksum: 10c0/aa91bccba22cc84d86308f833749bcb0b00441e35c24e0ac79abeac5f76dc62d47bdef9c1da6a0c609f5da6478595f52b252085888b89dbdb163861e40ea4188 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -5369,6 +5369,13 @@ __metadata: languageName: node linkType: hard +"oauth4webapi@npm:^3.8.5": + version: 3.8.6 + resolution: "oauth4webapi@npm:3.8.6" + checksum: 10c0/479810716702c5616e4a0eb80d4f1c264a6a2b63b04061a62a59375e195d2726e947507ede98e41ee3f9ca800f159621ea371c36f5c4346d015b6ec03e477440 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -5376,13 +5383,6 @@ __metadata: languageName: node linkType: hard -"object-hash@npm:^2.2.0": - version: 2.2.0 - resolution: "object-hash@npm:2.2.0" - checksum: 10c0/1527de843926c5442ed61f8bdddfc7dc181b6497f725b0e89fcf50a55d9c803088763ed447cac85a5aa65345f1e99c2469ba679a54349ef3c4c0aeaa396a3eb9 - languageName: node - linkType: hard - "object-hash@npm:^3.0.0": version: 3.0.0 resolution: "object-hash@npm:3.0.0" @@ -5464,15 +5464,13 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.7.1": - version: 5.7.1 - resolution: "openid-client@npm:5.7.1" +"openid-client@npm:^6.8.4": + version: 6.8.4 + resolution: "openid-client@npm:6.8.4" dependencies: - jose: "npm:^4.15.9" - lru-cache: "npm:^6.0.0" - object-hash: "npm:^2.2.0" - oidc-token-hash: "npm:^5.0.3" - checksum: 10c0/6aae649758562002eace7574b6eda02be7eddbb0df61eef497ae98b7a4a0ae4c6b09f3f0c1b9b6cb7fcc0c70bbde2576691bf31b870db1f19ab634c1def10bc7 + jose: "npm:^6.2.2" + oauth4webapi: "npm:^3.8.5" + checksum: 10c0/bb062df77d48368f99f776f26d94e11053da80b89d2baeea9f069b7a2b359ef5dc10e0c9b0f9a90fb361eded7ff02dba3f5419b8909ec3f0c379d09807824c49 languageName: node linkType: hard -- GitLab From b82c680b9c1c9ba8f72148055cfbfb7ffba448e7 Mon Sep 17 00:00:00 2001 From: Grant Date: Sat, 13 Jun 2026 09:42:16 -0600 Subject: [PATCH 05/10] utils stub tradeout --- backend/src/lib/apub/utils.live.ts | 316 ++++++++++++++++++++++++++++ backend/src/lib/apub/utils.stub.ts | 28 ++- backend/src/lib/apub/utils.ts | 319 +---------------------------- 3 files changed, 340 insertions(+), 323 deletions(-) create mode 100644 backend/src/lib/apub/utils.live.ts diff --git a/backend/src/lib/apub/utils.live.ts b/backend/src/lib/apub/utils.live.ts new file mode 100644 index 0000000..9553430 --- /dev/null +++ b/backend/src/lib/apub/utils.live.ts @@ -0,0 +1,316 @@ +import { + Actor, + ChatMessage, + Context, + Create, + Delete, + isActor, + lookupObject, + LookupObjectOptions, + lookupWebFinger, + Mention, + Note, + Tombstone, +} from "@fedify/fedify"; +import { federation, USER_IDENTIFIER } from "./federation.js"; +import { Temporal } from "@js-temporal/polyfill"; +import { AuthSession } from "../../controllers/AuthSession.js"; +import { IProfile } from "../instance/userMeta.js"; +import { getSafeURL } from "../utils.js"; + +type BuildObjectOpts = { + id: string; + one_time_code: string; + createdAt: Date; + target: Actor; +}; + +export class APubLive { + ctx: Context; + + constructor(ctx: Context) { + this.ctx = ctx; + } + + static options: ( + ctx: Context, + opts?: Partial, + ) => LookupObjectOptions = (ctx, opts = {}) => ({ + ...ctx, + allowPrivateAddress: process.env.NODE_ENV === "development", + ...opts, + }); + + static get accountHandle() { + return USER_IDENTIFIER + "@" + new URL(process.env.OIDC_ISSUER).host; + } + + static get() { + return new APubLive( + federation.createContext(new URL("/", process.env.OIDC_ISSUER)), + ); + } + + /** + * Build IProfile from just an ActivityPub identifier + * @param identifier + */ + static async buildProfile( + identifier: string | URL, + ): Promise { + const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); + const documentLoader = await ctx.getDocumentLoader({ + identifier: USER_IDENTIFIER, + }); + + let profile: IProfile = { + sub: identifier.toString(), + }; + + // webfinger should support ?resource being a fully qualified profile page + // but... lemmy does not support it and returns a 400 bad request + + const webfinger = await this.lookupWebfinger(identifier).catch( + (_e) => null, + ); + const actor = await this.lookupActor(identifier).catch((_e) => undefined); + + const perferred_username = + webfinger?.subject?.replace("acct:", "") || + actor!.preferredUsername!.toString() + "@" + new URL(identifier).hostname; + const profile_url = + webfinger?.links?.find( + (l) => l.rel === "http://webfinger.net/rel/profile-page", + )?.href || identifier.toString(); + + profile.name = actor?.name?.toString(); + profile.preferred_username = perferred_username; + profile.profile = profile_url; + profile.raw_picture = ( + await actor?.getIcon(this.options(ctx, { documentLoader })) + )?.url?.href?.toString(); + if (profile.raw_picture) profile.picture = getSafeURL(profile.raw_picture); + + return profile; + } + + static async lookupWebfinger(identifier: string | URL) { + const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); + const documentLoader = await ctx.getDocumentLoader({ + identifier: USER_IDENTIFIER, + }); + return lookupWebFinger(identifier, this.options(ctx, { documentLoader })); + } + + static async lookupActor(identifier: string | URL): Promise { + const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); + const documentLoader = await ctx.getDocumentLoader({ + identifier: USER_IDENTIFIER, + }); + const object = await lookupObject( + identifier, + this.options(ctx, { documentLoader }), + ); + + if (isActor(object)) return object; + return null; + } + + static async sendDM(session: AuthSession) { + const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); + const documentLoader = await ctx.getDocumentLoader({ + identifier: USER_IDENTIFIER, + }); + const recipient = await lookupObject( + session.user_sub, + this.options(ctx, { documentLoader }), + ); + + if (!isActor(recipient)) throw new Error("Not an actor"); + + const apub = new APubLive(ctx); + const opts: BuildObjectOpts = { + id: session.id, + one_time_code: session.code, + createdAt: session.createdAt, + target: recipient, + }; + + try { + await apub.sendChatMessage( + session.id, + recipient, + apub.build("ChatMessage", opts), + ); + } catch (e) { + if (process.env.NODE_ENV === "development") { + // fediverse services may return non 2xx status codes, causing an error + // we can silently try another object if thats the case + // we log it here for development usage, but ignore otherwise + console.error(`Failed to send ChatMessage to ${session.user_sub}`, e); + } + } + + try { + await apub.sendNote(session.id, recipient, apub.build("Note", opts)); + } catch (e) { + if (process.env.NODE_ENV === "development") { + // fediverse services may return non 2xx status codes, causing an error + // we can silently try another object if thats the case + // we log it here for development usage, but ignore otherwise + console.error(`Failed to send Note to ${session.user_sub}`, e); + } + } + } + + static async deleteDM(session: AuthSession) { + const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); + const documentLoader = await ctx.getDocumentLoader({ + identifier: USER_IDENTIFIER, + }); + const recipient = await lookupObject( + session.user_sub, + this.options(ctx, { documentLoader }), + ); + + if (!isActor(recipient)) throw new Error("Not an actor"); + + const apub = new APubLive(ctx); + + try { + await apub.deleteChatMessage(session.id, recipient); + } catch (e) { + if (process.env.NODE_ENV === "development") { + // fediverse services may return non 2xx status codes, causing an error + // we can silently try another object if thats the case + // we log it here for development usage, but ignore otherwise + console.error(`Failed to delete ChatMessage to ${session.user_sub}`, e); + } + } + + try { + await apub.deleteNote(session.id, recipient); + } catch (e) { + if (process.env.NODE_ENV === "development") { + // fediverse services may return non 2xx status codes, causing an error + // we can silently try another object if thats the case + // we log it here for development usage, but ignore otherwise + console.error(`Failed to delete Note to ${session.user_sub}`, e); + } + } + } + + /** + * Send a ChatMessage message targeted at Actor + * + * Not many fediverse software supports this, but Lemmy <0.19 uses this exclusively for DMs + */ + async sendChatMessage(id: string, target: Actor, content: ChatMessage) { + const sender = this.ctx.getActorUri(USER_IDENTIFIER); + + await this.ctx.sendActivity( + { identifier: USER_IDENTIFIER }, + [target], + new Create({ + id: new URL("#create", this.ctx.getObjectUri(ChatMessage, { id })), + actor: sender, + to: target, + object: content, + }), + { fanout: "skip" }, + ); + } + + private async deleteChatMessage(id: string, target: Actor) { + const sender = this.ctx.getActorUri(USER_IDENTIFIER); + + await this.ctx.sendActivity( + { identifier: USER_IDENTIFIER }, + [target], + new Delete({ + id: new URL("#delete", this.ctx.getObjectUri(ChatMessage, { id })), + object: this.ctx.getObjectUri(ChatMessage, { id }), + actor: sender, + to: target, + }), + ); + } + + /** + * Send a private Note message targeted at Actor + * + * Most fediverse software supports this + */ + async sendNote(id: string, target: Actor, content: Note) { + const sender = this.ctx.getActorUri(USER_IDENTIFIER); + + await this.ctx.sendActivity( + { identifier: USER_IDENTIFIER }, + [target], + new Create({ + id: new URL("#create", this.ctx.getObjectUri(Note, { id })), + actor: sender, + to: target, + object: content, + }), + { fanout: "skip" }, + ); + } + + private async deleteNote(id: string, target: Actor) { + const sender = this.ctx.getActorUri(USER_IDENTIFIER); + + await this.ctx.sendActivity( + { identifier: USER_IDENTIFIER }, + [target], + new Delete({ + id: new URL("#delete", this.ctx.getObjectUri(Note, { id })), + object: new Tombstone({ + id: this.ctx.getObjectUri(Note, { id }), + }), + actor: sender, + to: target, + }), + ); + } + + build(type: "ChatMessage", opts: BuildObjectOpts): ChatMessage; + build(type: "Note", opts: BuildObjectOpts): Note; + build(type: "ChatMessage" | "Note", opts: BuildObjectOpts): unknown { + if (!type) throw new Error(); + + const { id, one_time_code, target, createdAt } = opts; + const sender = this.ctx.getActorUri(USER_IDENTIFIER); + + const content = { + content: `Code: ${one_time_code} + +Do not share this code. This code is used to identify you.`, + }; + + switch (type) { + case "ChatMessage": + return new ChatMessage({ + id: this.ctx.getObjectUri(ChatMessage, { id }), + attribution: sender, + to: target.id!, + published: Temporal.Instant.from(createdAt.toISOString()), + ...content, + }); + case "Note": + return new Note({ + id: this.ctx.getObjectUri(Note, { id }), + attribution: sender, + to: target.id!, + published: Temporal.Instant.from(createdAt.toISOString()), + tags: [ + new Mention({ + href: target.id, + name: target.id!.toString(), + }), + ], + ...content, + }); + } + } +} diff --git a/backend/src/lib/apub/utils.stub.ts b/backend/src/lib/apub/utils.stub.ts index 452f74d..6b66b12 100644 --- a/backend/src/lib/apub/utils.stub.ts +++ b/backend/src/lib/apub/utils.stub.ts @@ -13,10 +13,10 @@ import { } from "@fedify/fedify"; import { IProfile } from "../instance/userMeta.js"; import { USER_IDENTIFIER } from "./federation.js"; -import { APub } from "./utils.js"; +import { APubLive } from "./utils.live.js"; import { AuthSession } from "../../controllers/AuthSession.js"; -export class APubStub extends APub { +export class APubStub extends APubLive { constructor() { super(null as any); } @@ -24,7 +24,7 @@ export class APubStub extends APub { static options = () => ({}); static get accountHandle() { - return USER_IDENTIFIER + "@localhost"; + return process.env.DEV_APUB_STUB_HANDLE ?? USER_IDENTIFIER + "@localhost"; } static get() { @@ -50,14 +50,26 @@ export class APubStub extends APub { } static async lookupActor(identifier: string | URL): Promise { + // identifier can be a handle or URL + const id = String(identifier).startsWith("http") + ? new URL(identifier) + : new URL( + `/users/${String(identifier).split("@")[0]}`, + `https://${String(identifier).split("@").at(-1)}`, + ); + return new Person({ - id: new URL(identifier), + id, }); } - static async sendDM(session: AuthSession) {} + static async sendDM(session: AuthSession) { + console.debug("APubStub: sendDM", session); + } - static async deleteDM(session: AuthSession) {} + static async deleteDM(session: AuthSession) { + console.debug("APubStub: deleteDM", session); + } async sendChatMessage(id: string, target: Actor, content: ChatMessage) {} @@ -69,5 +81,5 @@ export class APubStub extends APub { ): any {} } -const _staticTypeCheck: typeof APub = APubStub; -_staticTypeCheck.get(); // to ignore "unused variable" but still trigger type check \ No newline at end of file +const _staticTypeCheck: typeof APubLive = APubStub; +_staticTypeCheck.get(); // to ignore "unused variable" but still trigger type check diff --git a/backend/src/lib/apub/utils.ts b/backend/src/lib/apub/utils.ts index cbadef6..45647f2 100644 --- a/backend/src/lib/apub/utils.ts +++ b/backend/src/lib/apub/utils.ts @@ -1,316 +1,5 @@ -import { - Actor, - ChatMessage, - Context, - Create, - Delete, - isActor, - lookupObject, - LookupObjectOptions, - lookupWebFinger, - Mention, - Note, - Tombstone, -} from "@fedify/fedify"; -import { federation, USER_IDENTIFIER } from "./federation.js"; -import { Temporal } from "@js-temporal/polyfill"; -import { AuthSession } from "../../controllers/AuthSession.js"; -import { IProfile } from "../instance/userMeta.js"; -import { getSafeURL } from "../utils.js"; +import { APubLive } from "./utils.live.js"; +import { APubStub } from "./utils.stub.js"; -type BuildObjectOpts = { - id: string; - one_time_code: string; - createdAt: Date; - target: Actor; -}; - -export class APub { - ctx: Context; - - constructor(ctx: Context) { - this.ctx = ctx; - } - - static options: ( - ctx: Context, - opts?: Partial - ) => LookupObjectOptions = (ctx, opts = {}) => ({ - ...ctx, - allowPrivateAddress: process.env.NODE_ENV === "development", - ...opts, - }); - - static get accountHandle() { - return USER_IDENTIFIER + "@" + new URL(process.env.OIDC_ISSUER).host; - } - - static get() { - return new APub( - federation.createContext(new URL("/", process.env.OIDC_ISSUER)) - ); - } - - /** - * Build IProfile from just an ActivityPub identifier - * @param identifier - */ - static async buildProfile( - identifier: string | URL - ): Promise { - const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); - const documentLoader = await ctx.getDocumentLoader({ - identifier: USER_IDENTIFIER, - }); - - let profile: IProfile = { - sub: identifier.toString(), - }; - - // webfinger should support ?resource being a fully qualified profile page - // but... lemmy does not support it and returns a 400 bad request - - const webfinger = await this.lookupWebfinger(identifier).catch( - (_e) => null - ); - const actor = await this.lookupActor(identifier).catch((_e) => undefined); - - const perferred_username = - webfinger?.subject?.replace("acct:", "") || - actor!.preferredUsername!.toString() + "@" + new URL(identifier).hostname; - const profile_url = - webfinger?.links?.find( - (l) => l.rel === "http://webfinger.net/rel/profile-page" - )?.href || identifier.toString(); - - profile.name = actor?.name?.toString(); - profile.preferred_username = perferred_username; - profile.profile = profile_url; - profile.raw_picture = ( - await actor?.getIcon(this.options(ctx, { documentLoader })) - )?.url?.href?.toString(); - if (profile.raw_picture) profile.picture = getSafeURL(profile.raw_picture); - - return profile; - } - - static async lookupWebfinger(identifier: string | URL) { - const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); - const documentLoader = await ctx.getDocumentLoader({ - identifier: USER_IDENTIFIER, - }); - return lookupWebFinger(identifier, this.options(ctx, { documentLoader })); - } - - static async lookupActor(identifier: string | URL): Promise { - const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); - const documentLoader = await ctx.getDocumentLoader({ - identifier: USER_IDENTIFIER, - }); - const object = await lookupObject( - identifier, - this.options(ctx, { documentLoader }) - ); - - if (isActor(object)) return object; - return null; - } - - static async sendDM(session: AuthSession) { - const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); - const documentLoader = await ctx.getDocumentLoader({ - identifier: USER_IDENTIFIER, - }); - const recipient = await lookupObject( - session.user_sub, - this.options(ctx, { documentLoader }) - ); - - if (!isActor(recipient)) throw new Error("Not an actor"); - - const apub = new APub(ctx); - const opts: BuildObjectOpts = { - id: session.id, - one_time_code: session.code, - createdAt: session.createdAt, - target: recipient, - }; - - try { - await apub.sendChatMessage( - session.id, - recipient, - apub.build("ChatMessage", opts) - ); - } catch (e) { - if (process.env.NODE_ENV === "development") { - // fediverse services may return non 2xx status codes, causing an error - // we can silently try another object if thats the case - // we log it here for development usage, but ignore otherwise - console.error(`Failed to send ChatMessage to ${session.user_sub}`, e); - } - } - - try { - await apub.sendNote(session.id, recipient, apub.build("Note", opts)); - } catch (e) { - if (process.env.NODE_ENV === "development") { - // fediverse services may return non 2xx status codes, causing an error - // we can silently try another object if thats the case - // we log it here for development usage, but ignore otherwise - console.error(`Failed to send Note to ${session.user_sub}`, e); - } - } - } - - static async deleteDM(session: AuthSession) { - const ctx = federation.createContext(new URL("/", process.env.OIDC_ISSUER)); - const documentLoader = await ctx.getDocumentLoader({ - identifier: USER_IDENTIFIER, - }); - const recipient = await lookupObject( - session.user_sub, - this.options(ctx, { documentLoader }) - ); - - if (!isActor(recipient)) throw new Error("Not an actor"); - - const apub = new APub(ctx); - - try { - await apub.deleteChatMessage(session.id, recipient); - } catch (e) { - if (process.env.NODE_ENV === "development") { - // fediverse services may return non 2xx status codes, causing an error - // we can silently try another object if thats the case - // we log it here for development usage, but ignore otherwise - console.error(`Failed to delete ChatMessage to ${session.user_sub}`, e); - } - } - - try { - await apub.deleteNote(session.id, recipient); - } catch (e) { - if (process.env.NODE_ENV === "development") { - // fediverse services may return non 2xx status codes, causing an error - // we can silently try another object if thats the case - // we log it here for development usage, but ignore otherwise - console.error(`Failed to delete Note to ${session.user_sub}`, e); - } - } - } - - /** - * Send a ChatMessage message targeted at Actor - * - * Not many fediverse software supports this, but Lemmy <0.19 uses this exclusively for DMs - */ - async sendChatMessage(id: string, target: Actor, content: ChatMessage) { - const sender = this.ctx.getActorUri(USER_IDENTIFIER); - - await this.ctx.sendActivity( - { identifier: USER_IDENTIFIER }, - [target], - new Create({ - id: new URL("#create", this.ctx.getObjectUri(ChatMessage, { id })), - actor: sender, - to: target, - object: content, - }), - { fanout: "skip" } - ); - } - - private async deleteChatMessage(id: string, target: Actor) { - const sender = this.ctx.getActorUri(USER_IDENTIFIER); - - await this.ctx.sendActivity( - { identifier: USER_IDENTIFIER }, - [target], - new Delete({ - id: new URL("#delete", this.ctx.getObjectUri(ChatMessage, { id })), - object: this.ctx.getObjectUri(ChatMessage, { id }), - actor: sender, - to: target, - }) - ); - } - - /** - * Send a private Note message targeted at Actor - * - * Most fediverse software supports this - */ - async sendNote(id: string, target: Actor, content: Note) { - const sender = this.ctx.getActorUri(USER_IDENTIFIER); - - await this.ctx.sendActivity( - { identifier: USER_IDENTIFIER }, - [target], - new Create({ - id: new URL("#create", this.ctx.getObjectUri(Note, { id })), - actor: sender, - to: target, - object: content, - }), - { fanout: "skip" } - ); - } - - private async deleteNote(id: string, target: Actor) { - const sender = this.ctx.getActorUri(USER_IDENTIFIER); - - await this.ctx.sendActivity( - { identifier: USER_IDENTIFIER }, - [target], - new Delete({ - id: new URL("#delete", this.ctx.getObjectUri(Note, { id })), - object: new Tombstone({ - id: this.ctx.getObjectUri(Note, { id }), - }), - actor: sender, - to: target, - }) - ); - } - - build(type: "ChatMessage", opts: BuildObjectOpts): ChatMessage; - build(type: "Note", opts: BuildObjectOpts): Note; - build(type: "ChatMessage" | "Note", opts: BuildObjectOpts): unknown { - if (!type) throw new Error(); - - const { id, one_time_code, target, createdAt } = opts; - const sender = this.ctx.getActorUri(USER_IDENTIFIER); - - const content = { - content: `Code: ${one_time_code} - -Do not share this code. This code is used to identify you.`, - }; - - switch (type) { - case "ChatMessage": - return new ChatMessage({ - id: this.ctx.getObjectUri(ChatMessage, { id }), - attribution: sender, - to: target.id!, - published: Temporal.Instant.from(createdAt.toISOString()), - ...content, - }); - case "Note": - return new Note({ - id: this.ctx.getObjectUri(Note, { id }), - attribution: sender, - to: target.id!, - published: Temporal.Instant.from(createdAt.toISOString()), - tags: [ - new Mention({ - href: target.id, - name: target.id!.toString(), - }), - ], - ...content, - }); - } - } -} +export const APub = + process.env.NODE_ENV === "development" ? APubStub : APubLive; -- GitLab From 0c1f6103130fde706b5bd9941d7683f690e50ca0 Mon Sep 17 00:00:00 2001 From: Grant Date: Sat, 13 Jun 2026 10:35:35 -0600 Subject: [PATCH 06/10] Reorganize file structure & move flags to dedicated file --- backend/README.md | 10 ++++ backend/src/__tests__/federation.test.ts | 18 +++---- backend/src/{lib => }/apub/federation.ts | 21 ++++---- backend/src/{lib => }/apub/transformers.ts | 0 backend/src/{lib => }/apub/utils.live.ts | 6 +-- backend/src/{lib => }/apub/utils.stub.ts | 4 +- backend/src/apub/utils.ts | 5 ++ backend/src/{lib => }/apub/worker.ts | 0 backend/src/controllers/DevController.ts | 2 +- backend/src/handoff/activitypub.ts | 4 +- backend/src/handoff/router.ts | 4 +- backend/src/index.ts | 44 ++++++----------- backend/src/jobs/jobs.ts | 2 +- backend/src/lib/apub/utils.ts | 5 -- backend/src/lib/flags.ts | 56 ++++++++++++++++++++++ backend/src/lib/utils.ts | 6 +-- backend/src/{lib => openid}/adapter.ts | 0 backend/src/{lib => openid}/oidc.ts | 8 ++-- backend/src/{lib => routes}/api.ts | 29 ++++++----- backend/src/{lib => routes}/api_admin.ts | 4 +- backend/src/{lib => routes}/express.ts | 13 ++--- backend/src/{lib => routes}/react.ts | 0 backend/src/tools/create-dev-client.ts | 2 +- backend/src/types/env.d.ts | 6 ++- 24 files changed, 153 insertions(+), 96 deletions(-) rename backend/src/{lib => }/apub/federation.ts (95%) rename backend/src/{lib => }/apub/transformers.ts (100%) rename backend/src/{lib => }/apub/utils.live.ts (98%) rename backend/src/{lib => }/apub/utils.stub.ts (94%) create mode 100644 backend/src/apub/utils.ts rename backend/src/{lib => }/apub/worker.ts (100%) delete mode 100644 backend/src/lib/apub/utils.ts create mode 100644 backend/src/lib/flags.ts rename backend/src/{lib => openid}/adapter.ts (100%) rename backend/src/{lib => openid}/oidc.ts (96%) rename backend/src/{lib => routes}/api.ts (93%) rename backend/src/{lib => routes}/api_admin.ts (96%) rename backend/src/{lib => routes}/express.ts (94%) rename backend/src/{lib => routes}/react.ts (100%) diff --git a/backend/README.md b/backend/README.md index bbd735f..e45c083 100644 --- a/backend/README.md +++ b/backend/README.md @@ -5,3 +5,13 @@ Built with Prisma, TypeScript and oidc-provider ## Development Move `example.env` to `.env` and edit to your desires + +### Development Environment Behavior + +These all have the prereq of `NODE_ENV = development` + +| Env var | Example | Purpose | +| ------------------------------- | ------- | ----------------------------------------------------------------------------------------- | +| `DEV_TRUST_PROXIES` | `true` | Forces proxies to be trusted while still in development (behind ngrok or other dev proxy) | +| `DEV_USE_APUB_STUB` | `true` | Use a placeholder implementation of activitypub federation | +| `DEV_BYPASS_TOKEN_VERIFICATION` | `true` | Disable verification of tokens | diff --git a/backend/src/__tests__/federation.test.ts b/backend/src/__tests__/federation.test.ts index 9253dba..790aedf 100644 --- a/backend/src/__tests__/federation.test.ts +++ b/backend/src/__tests__/federation.test.ts @@ -16,7 +16,7 @@ import { import { AuthSession } from "../controllers/AuthSession.js"; import { openTunnel, Tunnel } from "@hongminhee/localtunnel"; import { Server } from "node:http"; -import { type APub as TAPub } from "../lib/apub/utils.js"; +import { type APub as TAPub } from "../apub/utils.js"; import { type Federation } from "@fedify/fedify"; import { randomUUID } from "node:crypto"; import { configure, getConsoleSink } from "@logtape/logtape"; @@ -86,7 +86,7 @@ const getTestConfig = async (): Promise<{ typeof entry.auth === "string" && // "label" in entry && - typeof entry.label === "string" + typeof entry.label === "string", ) ) { return { @@ -106,9 +106,9 @@ let federation: Federation; * These modules may use process.env immediately */ const load = async () => { - const lib_express = await import("../lib/express.js"); - const lib_apub_utils = await import("../lib/apub/utils.js"); - const lib_apub_federation = await import("../lib/apub/federation.js"); + const lib_express = await import("../routes/express.js"); + const lib_apub_utils = await import("../apub/utils.js"); + const lib_apub_federation = await import("../apub/federation.js"); Express = lib_express.app; APub = lib_apub_utils.APub; @@ -137,7 +137,7 @@ beforeAll(async () => { ActiveTunnel = await openTunnel({ port: PORT, service: "localhost.run" }); console.log( - `[Prepare] Tunnel localhost:${PORT} -> ${ActiveTunnel.url} (pid: ${ActiveTunnel.pid})` + `[Prepare] Tunnel localhost:${PORT} -> ${ActiveTunnel.url} (pid: ${ActiveTunnel.pid})`, ); // modify environment @@ -171,7 +171,7 @@ afterAll(async () => { }); describe.for( - TestConfig.accounts.map(({ label, ...account }) => ({ label, account })) + TestConfig.accounts.map(({ label, ...account }) => ({ label, account })), )("Deliver to $label", { concurrent: true }, async ({ label, account }) => { let TestKit: FediTestKit; let sessionId: string; @@ -195,7 +195,7 @@ describe.for( if (!DRY_SEND) await TestKit.setup(); }, - account.type === "MBIN" ? 60000 : 10000 + account.type === "MBIN" ? 60000 : 10000, ); test.sequential("APub#sendDM", async () => { @@ -208,7 +208,7 @@ describe.for( } else { const version = await TestKit.getVersion(); await ctx.annotate( - `${label} reports as using version ${account.type} ${version}` + `${label} reports as using version ${account.type} ${version}`, ); await TestKit.expectToHave(sessionCode, 1000); } diff --git a/backend/src/lib/apub/federation.ts b/backend/src/apub/federation.ts similarity index 95% rename from backend/src/lib/apub/federation.ts rename to backend/src/apub/federation.ts index e7c55ea..810e7da 100644 --- a/backend/src/lib/apub/federation.ts +++ b/backend/src/apub/federation.ts @@ -16,9 +16,9 @@ import { import { RedisKvStore, RedisMessageQueue } from "@fedify/redis"; import { Redis } from "ioredis"; import { FedifyTransformers } from "./transformers.js"; -import { prisma } from "../prisma.js"; +import { prisma } from "../lib/prisma.js"; import { APub } from "./utils.js"; -import { Handoff } from "../../handoff/index.js"; +import { Handoff } from "../handoff/index.js"; // @auth@some.domain export const USER_IDENTIFIER = "auth"; @@ -81,9 +81,8 @@ federation publicKey: await importJwk(publicKey, "public"), }); } else { - const { privateKey, publicKey } = await generateCryptoKeyPair( - "RSASSA-PKCS1-v1_5" - ); + const { privateKey, publicKey } = + await generateCryptoKeyPair("RSASSA-PKCS1-v1_5"); await prisma.fediverseKeyPair.create({ data: { keyType: "RSASSA-PKCS1-v1_5", @@ -127,7 +126,7 @@ federation new Reject({ actor: follow.objectId, object: follow, - }) + }), ); }) .on(Create, async (ctx, create) => { @@ -156,7 +155,7 @@ federation.setOutboxDispatcher( return { items: [], }; - } + }, ); federation.setObjectDispatcher( @@ -173,7 +172,7 @@ federation.setObjectDispatcher( const recipient = await lookupObject( authSession.user_sub, - APub.options(ctx) + APub.options(ctx), ); const apub = new APub(ctx); @@ -183,7 +182,7 @@ federation.setObjectDispatcher( ...authSession, target: recipient!, }); - } + }, ); federation.setObjectDispatcher( @@ -200,7 +199,7 @@ federation.setObjectDispatcher( const recipient = await lookupObject( authSession.user_sub, - APub.options(ctx) + APub.options(ctx), ); const apub = new APub(ctx); @@ -210,5 +209,5 @@ federation.setObjectDispatcher( ...authSession, target: recipient!, }); - } + }, ); diff --git a/backend/src/lib/apub/transformers.ts b/backend/src/apub/transformers.ts similarity index 100% rename from backend/src/lib/apub/transformers.ts rename to backend/src/apub/transformers.ts diff --git a/backend/src/lib/apub/utils.live.ts b/backend/src/apub/utils.live.ts similarity index 98% rename from backend/src/lib/apub/utils.live.ts rename to backend/src/apub/utils.live.ts index 9553430..548666b 100644 --- a/backend/src/lib/apub/utils.live.ts +++ b/backend/src/apub/utils.live.ts @@ -14,9 +14,9 @@ import { } from "@fedify/fedify"; import { federation, USER_IDENTIFIER } from "./federation.js"; import { Temporal } from "@js-temporal/polyfill"; -import { AuthSession } from "../../controllers/AuthSession.js"; -import { IProfile } from "../instance/userMeta.js"; -import { getSafeURL } from "../utils.js"; +import { AuthSession } from "../controllers/AuthSession.js"; +import { IProfile } from "../lib/instance/userMeta.js"; +import { getSafeURL } from "../lib/utils.js"; type BuildObjectOpts = { id: string; diff --git a/backend/src/lib/apub/utils.stub.ts b/backend/src/apub/utils.stub.ts similarity index 94% rename from backend/src/lib/apub/utils.stub.ts rename to backend/src/apub/utils.stub.ts index 6b66b12..bb3ef61 100644 --- a/backend/src/lib/apub/utils.stub.ts +++ b/backend/src/apub/utils.stub.ts @@ -11,10 +11,10 @@ import { Person, ResourceDescriptor, } from "@fedify/fedify"; -import { IProfile } from "../instance/userMeta.js"; +import { IProfile } from "../lib/instance/userMeta.js"; import { USER_IDENTIFIER } from "./federation.js"; import { APubLive } from "./utils.live.js"; -import { AuthSession } from "../../controllers/AuthSession.js"; +import { AuthSession } from "../controllers/AuthSession.js"; export class APubStub extends APubLive { constructor() { diff --git a/backend/src/apub/utils.ts b/backend/src/apub/utils.ts new file mode 100644 index 0000000..279f834 --- /dev/null +++ b/backend/src/apub/utils.ts @@ -0,0 +1,5 @@ +import { Flags } from "../lib/flags.js"; +import { APubLive } from "./utils.live.js"; +import { APubStub } from "./utils.stub.js"; + +export const APub = Flags.Dev.UseAPubStub ? APubStub : APubLive; diff --git a/backend/src/lib/apub/worker.ts b/backend/src/apub/worker.ts similarity index 100% rename from backend/src/lib/apub/worker.ts rename to backend/src/apub/worker.ts diff --git a/backend/src/controllers/DevController.ts b/backend/src/controllers/DevController.ts index 9c19f71..69041a8 100644 --- a/backend/src/controllers/DevController.ts +++ b/backend/src/controllers/DevController.ts @@ -1,7 +1,7 @@ import e, { Router } from "express"; import * as OIDClient from "openid-client"; import { prisma } from "../lib/prisma.js"; -import { OidcTypes } from "../lib/adapter.js"; +import { OidcTypes } from "../openid/adapter.js"; const DEV_CLIENT = { client_id: "dev", diff --git a/backend/src/handoff/activitypub.ts b/backend/src/handoff/activitypub.ts index 566fbe5..9d0dc50 100644 --- a/backend/src/handoff/activitypub.ts +++ b/backend/src/handoff/activitypub.ts @@ -1,7 +1,7 @@ import { Actor, ChatMessage, Create, Mention, Note } from "@fedify/fedify"; import { HandoffSession } from "../controllers/HandoffSession.js"; -import { APub } from "../lib/apub/utils.js"; -import { USER_IDENTIFIER } from "../lib/apub/federation.js"; +import { APub } from "../apub/utils.js"; +import { USER_IDENTIFIER } from "../apub/federation.js"; import { Temporal } from "@js-temporal/polyfill"; import { stripHtml } from "string-strip-html"; import { Handoff } from "./index.js"; diff --git a/backend/src/handoff/router.ts b/backend/src/handoff/router.ts index 0bf5afa..e8f7414 100644 --- a/backend/src/handoff/router.ts +++ b/backend/src/handoff/router.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import { HandoffSession } from "../controllers/HandoffSession.js"; -import { ReactUtils } from "../lib/react.js"; -import { APub } from "../lib/apub/utils.js"; +import { ReactUtils } from "../routes/react.js"; +import { APub } from "../apub/utils.js"; export const router = Router(); diff --git a/backend/src/index.ts b/backend/src/index.ts index 8534df3..d357085 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,8 +1,11 @@ import { configure, getConsoleSink } from "@logtape/logtape"; -import { app as Express } from "./lib/express.js"; -import { oidc } from "./lib/oidc.js"; -import { FederationWorker } from "./lib/apub/worker.js"; +import { app as Express } from "./routes/express.js"; +import { oidc } from "./openid/oidc.js"; +import { FederationWorker } from "./apub/worker.js"; import { Jobs } from "./jobs/jobs.js"; +import { Flags } from "./lib/flags.js"; + +Flags.print(); if (typeof process.env.SESSION_SECRET !== "string") { throw new Error("SESSION_SECRET is not defined"); @@ -19,7 +22,7 @@ if (typeof process.env.OIDC_ISSUER !== "string") { if (typeof process.env.OIDC_REGISTRATION_TOKEN !== "string") { // throw new Error("OIDC_REGISTRATION_TOKEN is not defined"); console.warn( - "OIDC_REGISTRATION_TOKEN is not defined\nThis removes the requirement for passing a token and anyone can then register a client" + "OIDC_REGISTRATION_TOKEN is not defined\nThis removes the requirement for passing a token and anyone can then register a client", ); } @@ -50,43 +53,24 @@ await configure({ sinks: ["console"], lowestLevel: "debug", }, + { + category: ["logtape", "meta"], + lowestLevel: "warning", + }, ], filters: {}, }); -/** - * Should the worker scripts run now? - * - * Any of: - * - NODE_ENV = development - * - NODE_TYPE = worker - */ -const shouldStartWorker = - process.env.NODE_ENV === "development" || process.env.NODE_TYPE === "worker"; -/** - * Should the main scripts run now? - * - * Any of: - * - NODE_ENV = development - * - NODE_ENV = production & NODE_TYPE != worker - */ -const shouldStartMain = - process.env.NODE_ENV === "development" || - (process.env.NODE_ENV === "production" && process.env.NODE_TYPE !== "worker"); - console.log("\n"); -console.log("Initializing fediverse-auth...", { - "Start Main": shouldStartMain, - "Start Worker": shouldStartWorker, -}); +console.log("Good morning fediverse-auth"); console.log("\n"); -if (shouldStartWorker) { +if (Flags.Run.Worker) { Jobs.start(); void FederationWorker.create(); } -if (shouldStartMain) { +if (Flags.Run.Main) { Express.listen(process.env.PORT, () => { console.log("Listening on :" + process.env.PORT); }); diff --git a/backend/src/jobs/jobs.ts b/backend/src/jobs/jobs.ts index d31462e..0f9e06d 100644 --- a/backend/src/jobs/jobs.ts +++ b/backend/src/jobs/jobs.ts @@ -1,6 +1,6 @@ import { CronJob } from "cron"; import { AuthSession } from "../controllers/AuthSession.js"; -import { APub } from "../lib/apub/utils.js"; +import { APub } from "../apub/utils.js"; import { prisma } from "../lib/prisma.js"; export class Jobs { diff --git a/backend/src/lib/apub/utils.ts b/backend/src/lib/apub/utils.ts deleted file mode 100644 index 45647f2..0000000 --- a/backend/src/lib/apub/utils.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { APubLive } from "./utils.live.js"; -import { APubStub } from "./utils.stub.js"; - -export const APub = - process.env.NODE_ENV === "development" ? APubStub : APubLive; diff --git a/backend/src/lib/flags.ts b/backend/src/lib/flags.ts new file mode 100644 index 0000000..f6d5e7b --- /dev/null +++ b/backend/src/lib/flags.ts @@ -0,0 +1,56 @@ +export namespace Flags { + export namespace Dev { + export const UseAPubStub = + process.env.NODE_ENV === "development" && + Boolean(process.env.DEV_USE_APUB_STUB); + + export const TrustProxies = + process.env.NODE_ENV === "development" && + Boolean(process.env.DEV_TRUST_PROXIES); + + export const BypassCodeVerification = + process.env.NODE_ENV === "development" && + Boolean(process.env.DEV_BYPASS_TOKEN_VERIFICATION); + } + + export namespace Run { + /** + * Should the worker scripts run now? + * + * Any of: + * - NODE_ENV = development + * - NODE_TYPE = worker + */ + export const Worker = + process.env.NODE_ENV === "development" || + process.env.NODE_TYPE === "worker"; + + /** + * Should the main scripts run now? + * + * Any of: + * - NODE_ENV = development + * - NODE_ENV = production & NODE_TYPE != worker + */ + export const Main = + process.env.NODE_ENV === "development" || + (process.env.NODE_ENV === "production" && + process.env.NODE_TYPE !== "worker"); + } + + export const print = () => { + const output = ["Flags:"]; + + output.push(" Dev:"); + for (const [key, value] of Object.entries(Dev)) { + output.push(` ${key}: ${value}`); + } + + output.push(" Run:"); + for (const [key, value] of Object.entries(Run)) { + output.push(` ${key}: ${value}`); + } + + console.log(output.join("\n")); + }; +} diff --git a/backend/src/lib/utils.ts b/backend/src/lib/utils.ts index ee41aa5..400bb0b 100644 --- a/backend/src/lib/utils.ts +++ b/backend/src/lib/utils.ts @@ -1,7 +1,7 @@ import { NodeInfo } from "../types/nodeinfo.js"; import { IOIDC_Public_Client } from "../types/oidc.js"; import { getNodeInfo } from "./nodeinfo.js"; -import { oidc } from "./oidc.js"; +import { oidc } from "../openid/oidc.js"; import { type Request } from "express"; /** @@ -15,7 +15,7 @@ export const DOMAIN_REGEX = type IPrivateClient = Awaited>; export const makeClientPublic = ( - client: IPrivateClient + client: IPrivateClient, ): IOIDC_Public_Client | undefined => { if (!client) return undefined; @@ -41,7 +41,7 @@ export const makeClientPublic = ( * @param instance_hostname */ export const isInstanceDomainValid = async ( - instance_hostname: string + instance_hostname: string, ): Promise => { let nodeinfo: NodeInfo; try { diff --git a/backend/src/lib/adapter.ts b/backend/src/openid/adapter.ts similarity index 100% rename from backend/src/lib/adapter.ts rename to backend/src/openid/adapter.ts diff --git a/backend/src/lib/oidc.ts b/backend/src/openid/oidc.ts similarity index 96% rename from backend/src/lib/oidc.ts rename to backend/src/openid/oidc.ts index bcd6d75..f882760 100644 --- a/backend/src/lib/oidc.ts +++ b/backend/src/openid/oidc.ts @@ -2,10 +2,10 @@ import Provider from "oidc-provider"; import fs from "node:fs"; import * as OIDClient from "openid-client"; import { PrismaAdapter } from "./adapter.js"; -import { IInstance, getInstanceMeta } from "./instance/instanceMeta.js"; -import { IProfile, getUserMeta } from "./instance/userMeta.js"; -import { ShadowAPI, UserLoginError } from "./shadow.js"; -import { APub } from "./apub/utils.js"; +import { IInstance, getInstanceMeta } from "../lib/instance/instanceMeta.js"; +import { IProfile, getUserMeta } from "../lib/instance/userMeta.js"; +import { ShadowAPI, UserLoginError } from "../lib/shadow.js"; +import { APub } from "../apub/utils.js"; /** * ⚠ DEVELOPMENT KEYS ⚠ diff --git a/backend/src/lib/api.ts b/backend/src/routes/api.ts similarity index 93% rename from backend/src/lib/api.ts rename to backend/src/routes/api.ts index b62da50..232f749 100644 --- a/backend/src/lib/api.ts +++ b/backend/src/routes/api.ts @@ -1,19 +1,20 @@ import express from "express"; import cookieParser from "cookie-parser"; -import { doesInstanceSupportOIDC, oidc } from "./oidc.js"; +import { doesInstanceSupportOIDC, oidc } from "../openid/oidc.js"; import { DOMAIN_REGEX, getExpressIP, isInstanceDomainValid, makeClientPublic, -} from "./utils.js"; -import { getNodeInfo } from "./nodeinfo.js"; -import { prisma } from "./prisma.js"; -import { IProfile, getUserMeta } from "./instance/userMeta.js"; -import { IInstance, getInstanceMeta } from "./instance/instanceMeta.js"; -import { ShadowAPI, UserLoginError } from "./shadow.js"; -import { APub } from "./apub/utils.js"; +} from "../lib/utils.js"; +import { getNodeInfo } from "../lib/nodeinfo.js"; +import { prisma } from "../lib/prisma.js"; +import { IProfile, getUserMeta } from "../lib/instance/userMeta.js"; +import { IInstance, getInstanceMeta } from "../lib/instance/instanceMeta.js"; +import { ShadowAPI, UserLoginError } from "../lib/shadow.js"; +import { APub } from "../apub/utils.js"; import { AuthSession } from "../controllers/AuthSession.js"; +import { Flags } from "../lib/flags.js"; const app = express.Router(); @@ -168,7 +169,7 @@ app.post("/login/step/username", async (req, res) => { } catch (e) { console.error( "Error while delivering to " + [username, instance].join("@"), - e + e, ); await prisma.authSession.delete({ where: { id: session.id } }); @@ -216,12 +217,14 @@ app.post("/login/step/verify", async (req, res) => { return; } - if (process.env.BYPASS_CODE_VERIFICATION) { - console.warn("BYPASS_CODE_VERIFICATION is set; not verifying 2fa codes"); + if (Flags.Dev.BypassCodeVerification) { + console.warn( + "DEV_BYPASS_TOKEN_VERIFICATION is set; not verifying 2fa codes", + ); const user = await APub.lookupActor(session.user_sub); if (!user) - throw new Error("BYPASS_CODE_VERIFICATION user is not an actor!"); + throw new Error("DEV_BYPASS_TOKEN_VERIFICATION user is not an actor!"); req.session.user = { sub: user.id!.toString(), @@ -364,7 +367,7 @@ app.post("/interaction/:uid/confirm", async (req, res) => { if (interaction.prompt.details.missingOIDCScope) { grant!.addOIDCScope( - (interaction.prompt.details.missingOIDCScope as any).join(" ") + (interaction.prompt.details.missingOIDCScope as any).join(" "), ); } diff --git a/backend/src/lib/api_admin.ts b/backend/src/routes/api_admin.ts similarity index 96% rename from backend/src/lib/api_admin.ts rename to backend/src/routes/api_admin.ts index 54d8e87..43397da 100644 --- a/backend/src/lib/api_admin.ts +++ b/backend/src/routes/api_admin.ts @@ -1,7 +1,7 @@ import { Router } from "express"; -import { prisma } from "./prisma.js"; +import { prisma } from "../lib/prisma.js"; import { AuthSession } from "../controllers/AuthSession.js"; -import { APub } from "./apub/utils.js"; +import { APub } from "../apub/utils.js"; const app = Router(); diff --git a/backend/src/lib/express.ts b/backend/src/routes/express.ts similarity index 94% rename from backend/src/lib/express.ts rename to backend/src/routes/express.ts index e979ce3..c93511e 100644 --- a/backend/src/lib/express.ts +++ b/backend/src/routes/express.ts @@ -3,25 +3,26 @@ import session from "express-session"; import cors from "cors"; import bodyParser from "body-parser"; import path from "path"; -import { oidc } from "./oidc.js"; -import { makeClientPublic } from "./utils.js"; +import { oidc } from "../openid/oidc.js"; +import { makeClientPublic } from "../lib/utils.js"; import { errors as OIDC_Errors } from "oidc-provider"; import "../types/session-types.js"; import { APIRouter } from "./api.js"; import { integrateFederation } from "@fedify/express"; -import { federation, USER_IDENTIFIER } from "./apub/federation.js"; -import { APub } from "./apub/utils.js"; +import { federation, USER_IDENTIFIER } from "../apub/federation.js"; +import { APub } from "../apub/utils.js"; import { APIAdminRouter } from "./api_admin.js"; import { Handoff } from "../handoff/index.js"; -import { DocLinks } from "./DocLinks.js"; +import { DocLinks } from "../lib/DocLinks.js"; import DevController from "../controllers/DevController.js"; +import { Flags } from "../lib/flags.js"; export const app = express(); if (process.env.NODE_ENV === "production") app.set("trust proxy", 1); -if (process.env.NODE_ENV === "development" && process.env.DEV_TRUST_PROXIES) { +if (Flags.Dev.TrustProxies) { console.log("DEV_TRUST_PROXIES enabled, trusting proxies"); app.set("trust proxy", 1); oidc.proxy = true; diff --git a/backend/src/lib/react.ts b/backend/src/routes/react.ts similarity index 100% rename from backend/src/lib/react.ts rename to backend/src/routes/react.ts diff --git a/backend/src/tools/create-dev-client.ts b/backend/src/tools/create-dev-client.ts index 2d638d4..7666529 100644 --- a/backend/src/tools/create-dev-client.ts +++ b/backend/src/tools/create-dev-client.ts @@ -1,4 +1,4 @@ -import { OidcTypes } from "../lib/adapter.js"; +import { OidcTypes } from "../openid/adapter.js"; import { prisma } from "../lib/prisma.js"; const client = { diff --git a/backend/src/types/env.d.ts b/backend/src/types/env.d.ts index 0ed0e22..c3fb216 100644 --- a/backend/src/types/env.d.ts +++ b/backend/src/types/env.d.ts @@ -24,11 +24,15 @@ declare global { SHADOW_HOST?: string; SHADOW_TOKEN?: string; - BYPASS_CODE_VERIFICATION?: string; + DEV_BYPASS_TOKEN_VERIFICATION?: string; /** * Trust all proxies while in development */ DEV_TRUST_PROXIES?: string; + /** + * Don't run actual activitypub federation code + */ + DEV_USE_APUB_STUB?: string; HANDOFF_TOKEN?: string; -- GitLab From eed542e590feb16128f8489f68e1f3108dd23a70 Mon Sep 17 00:00:00 2001 From: Grant Date: Sun, 14 Jun 2026 22:48:46 -0600 Subject: [PATCH 07/10] move models & initial sidecar --- backend/src/__tests__/federation.test.ts | 2 +- backend/src/apub/utils.live.ts | 2 +- backend/src/apub/utils.stub.ts | 2 +- backend/src/handoff/activitypub.ts | 2 +- backend/src/handoff/index.ts | 7 +- backend/src/handoff/router.ts | 2 +- backend/src/handoff/sidecar.apps.ts | 15 ++++ backend/src/handoff/sidecar.ts | 78 +++++++++++++++++++ backend/src/jobs/jobs.ts | 2 +- .../{controllers => models}/AuthSession.ts | 0 .../{controllers => models}/HandoffSession.ts | 0 backend/src/routes/api.ts | 2 +- backend/src/routes/api_admin.ts | 2 +- 13 files changed, 102 insertions(+), 14 deletions(-) create mode 100644 backend/src/handoff/sidecar.apps.ts create mode 100644 backend/src/handoff/sidecar.ts rename backend/src/{controllers => models}/AuthSession.ts (100%) rename backend/src/{controllers => models}/HandoffSession.ts (100%) diff --git a/backend/src/__tests__/federation.test.ts b/backend/src/__tests__/federation.test.ts index 790aedf..89dbff2 100644 --- a/backend/src/__tests__/federation.test.ts +++ b/backend/src/__tests__/federation.test.ts @@ -13,7 +13,7 @@ import { type Software as FTK_Software, SOFTWARES, } from "@sc07/fedi-testkit"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; import { openTunnel, Tunnel } from "@hongminhee/localtunnel"; import { Server } from "node:http"; import { type APub as TAPub } from "../apub/utils.js"; diff --git a/backend/src/apub/utils.live.ts b/backend/src/apub/utils.live.ts index 548666b..2662506 100644 --- a/backend/src/apub/utils.live.ts +++ b/backend/src/apub/utils.live.ts @@ -14,7 +14,7 @@ import { } from "@fedify/fedify"; import { federation, USER_IDENTIFIER } from "./federation.js"; import { Temporal } from "@js-temporal/polyfill"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; import { IProfile } from "../lib/instance/userMeta.js"; import { getSafeURL } from "../lib/utils.js"; diff --git a/backend/src/apub/utils.stub.ts b/backend/src/apub/utils.stub.ts index bb3ef61..aed032c 100644 --- a/backend/src/apub/utils.stub.ts +++ b/backend/src/apub/utils.stub.ts @@ -14,7 +14,7 @@ import { import { IProfile } from "../lib/instance/userMeta.js"; import { USER_IDENTIFIER } from "./federation.js"; import { APubLive } from "./utils.live.js"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; export class APubStub extends APubLive { constructor() { diff --git a/backend/src/handoff/activitypub.ts b/backend/src/handoff/activitypub.ts index 9d0dc50..766d61d 100644 --- a/backend/src/handoff/activitypub.ts +++ b/backend/src/handoff/activitypub.ts @@ -1,5 +1,5 @@ import { Actor, ChatMessage, Create, Mention, Note } from "@fedify/fedify"; -import { HandoffSession } from "../controllers/HandoffSession.js"; +import { HandoffSession } from "../models/HandoffSession.js"; import { APub } from "../apub/utils.js"; import { USER_IDENTIFIER } from "../apub/federation.js"; import { Temporal } from "@js-temporal/polyfill"; diff --git a/backend/src/handoff/index.ts b/backend/src/handoff/index.ts index 46d82c7..420ba0e 100644 --- a/backend/src/handoff/index.ts +++ b/backend/src/handoff/index.ts @@ -9,13 +9,8 @@ export class Handoff { } static get() { - if (!process.env.HANDOFF_TOKEN) - throw new Error("HANDOFF_TOKEN not set, cannot use Handoff"); - if (!this.canEnable()) - throw new Error( - "This should never occur, developer missed adding description throw above this line" - ); + throw new Error("Cannot enable handoff, is HANDOFF_TOKEN set?"); if (!this.instance) this.instance = new Handoff(); diff --git a/backend/src/handoff/router.ts b/backend/src/handoff/router.ts index e8f7414..ec5db09 100644 --- a/backend/src/handoff/router.ts +++ b/backend/src/handoff/router.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import { HandoffSession } from "../controllers/HandoffSession.js"; +import { HandoffSession } from "../models/HandoffSession.js"; import { ReactUtils } from "../routes/react.js"; import { APub } from "../apub/utils.js"; diff --git a/backend/src/handoff/sidecar.apps.ts b/backend/src/handoff/sidecar.apps.ts new file mode 100644 index 0000000..2ebbdba --- /dev/null +++ b/backend/src/handoff/sidecar.apps.ts @@ -0,0 +1,15 @@ +import { ISupportedSidecar } from "./sidecar.js"; + +/** + * App definitions for sidecar support + * + * @see {HandoffSidecar} + */ +export const SidecarApps = [ + { + id: "mlem", + name: "Mlem", + hasLogo: false, + auth_handler: "mlem://fediverse-auth/login", + }, +] as const satisfies ISupportedSidecar[]; diff --git a/backend/src/handoff/sidecar.ts b/backend/src/handoff/sidecar.ts new file mode 100644 index 0000000..70ad9ff --- /dev/null +++ b/backend/src/handoff/sidecar.ts @@ -0,0 +1,78 @@ +import { SidecarApps } from "./sidecar.apps.js"; + +/** + * Handoff Sidecar + * + * Allows for authentication to be handled by a registered fediverse app + * + * - A list of registered apps will be sent to the frontend for the end user to select from + * - User is always given the choice to authenticate normally + * - If an app is chosen, the user is redirected using a system handler to the app + * - The app is given the details neccessary to trigger the login verification (sending code to actor on behalf of user) + * - The user is redirected back to website (up to app to determine how) + * - fediauth now waits for the dm from the user's actor, once received, authenticates as that user + * + * This system will also be designed to allow for fediauth to be embedded as an iframe + * in registered oidc client applications (for example, Canvas). Canvas will have the ability + * to "drive" fediauth via postmessage and receive updates the same way. + */ +export class HandoffSidecar { + static readonly DEV_SIDECAR = { + id: "dev", + name: "dev sidecar", + hasLogo: false, + auth_handler: "http://localhost:5173/api/dev/sidecar-handler", + } as const satisfies ISupportedSidecar; + static readonly SUPPORTED_APPS = (process.env.NODE_ENV === "development" + ? [this.DEV_SIDECAR, ...SidecarApps] + : SidecarApps) satisfies ISupportedSidecar[]; + + constructor(private authActor: `${string}@${string}`) {} + + /** + * Create a sidecar session + */ + createSession(consumer: ISidecarConsumer = { client_id: "internal" }) {} + + buildAuthURI(sidecarId: SidecarID, session: SidecarSession) { + const sidecar = HandoffSidecar.SUPPORTED_APPS.find( + (s) => s.id === sidecarId, + )!; + + const url = new URL(sidecar.auth_handler); + url.searchParams.set("session", session); + url.searchParams.set("actor", this.authActor); + + return url; + } +} + +type SidecarID = (typeof HandoffSidecar)["SUPPORTED_APPS"][number]["id"]; +type SidecarSession = `s${string}`; + +export interface ISupportedSidecar { + /** + * ID must be url safe + */ + readonly id: string; + name: string; + /** + * Logo url is derived from id and is served by the server + */ + hasLogo: boolean; + /** + * URI to window.open(_blank) on to handoff to + */ + auth_handler: string; +} + +/** + * An oauth consumer that is waiting for the auth + */ +interface ISidecarConsumer { + client_id: string; + /** + * Data provided by the consumer of the auth + */ + session_data?: string; +} diff --git a/backend/src/jobs/jobs.ts b/backend/src/jobs/jobs.ts index 0f9e06d..840c0e5 100644 --- a/backend/src/jobs/jobs.ts +++ b/backend/src/jobs/jobs.ts @@ -1,5 +1,5 @@ import { CronJob } from "cron"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; import { APub } from "../apub/utils.js"; import { prisma } from "../lib/prisma.js"; diff --git a/backend/src/controllers/AuthSession.ts b/backend/src/models/AuthSession.ts similarity index 100% rename from backend/src/controllers/AuthSession.ts rename to backend/src/models/AuthSession.ts diff --git a/backend/src/controllers/HandoffSession.ts b/backend/src/models/HandoffSession.ts similarity index 100% rename from backend/src/controllers/HandoffSession.ts rename to backend/src/models/HandoffSession.ts diff --git a/backend/src/routes/api.ts b/backend/src/routes/api.ts index 232f749..334761a 100644 --- a/backend/src/routes/api.ts +++ b/backend/src/routes/api.ts @@ -13,7 +13,7 @@ import { IProfile, getUserMeta } from "../lib/instance/userMeta.js"; import { IInstance, getInstanceMeta } from "../lib/instance/instanceMeta.js"; import { ShadowAPI, UserLoginError } from "../lib/shadow.js"; import { APub } from "../apub/utils.js"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; import { Flags } from "../lib/flags.js"; const app = express.Router(); diff --git a/backend/src/routes/api_admin.ts b/backend/src/routes/api_admin.ts index 43397da..ae6f0ef 100644 --- a/backend/src/routes/api_admin.ts +++ b/backend/src/routes/api_admin.ts @@ -1,6 +1,6 @@ import { Router } from "express"; import { prisma } from "../lib/prisma.js"; -import { AuthSession } from "../controllers/AuthSession.js"; +import { AuthSession } from "../models/AuthSession.js"; import { APub } from "../apub/utils.js"; const app = Router(); -- GitLab From e59e46804ca9c8feaddf8fff7dcf116a54ada36a Mon Sep 17 00:00:00 2001 From: Grant Date: Wed, 17 Jun 2026 18:59:01 -0600 Subject: [PATCH 08/10] ci: add todo-check component --- .gitlab-ci.yml | 3 +++ backend/src/handoff/router.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index db5ccbc..a481f75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,8 @@ include: - local: .gitlab/ci/federation_test.yml + - component: sc07.dev/sc07/ci/todo-check@main + inputs: + comment_token_var: CI_COMMENT_TOKEN build wiki: stage: build diff --git a/backend/src/handoff/router.ts b/backend/src/handoff/router.ts index ec5db09..3d591fb 100644 --- a/backend/src/handoff/router.ts +++ b/backend/src/handoff/router.ts @@ -5,6 +5,8 @@ import { APub } from "../apub/utils.js"; export const router = Router(); +// TODO: test comment for ci + type Data = | { state: "NOT_FOUND" } | { state: "MISSING_RETURN" | "INVALID_RETURN" } -- GitLab From 0413fc0095f32fcf59a65f9ef09671d84259631e Mon Sep 17 00:00:00 2001 From: Grant Date: Wed, 17 Jun 2026 21:40:08 -0600 Subject: [PATCH 09/10] remove test comment --- backend/src/handoff/router.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/handoff/router.ts b/backend/src/handoff/router.ts index 3d591fb..ec5db09 100644 --- a/backend/src/handoff/router.ts +++ b/backend/src/handoff/router.ts @@ -5,8 +5,6 @@ import { APub } from "../apub/utils.js"; export const router = Router(); -// TODO: test comment for ci - type Data = | { state: "NOT_FOUND" } | { state: "MISSING_RETURN" | "INVALID_RETURN" } -- GitLab From 811961adce802103a16c93fa608d475535966999 Mon Sep 17 00:00:00 2001 From: Grant Date: Wed, 17 Jun 2026 23:36:49 -0600 Subject: [PATCH 10/10] wip: sidecar work --- .vscode/settings.json | 3 +- backend/prisma/schema.prisma | 11 +++ backend/src/apub/federation.ts | 1 + backend/src/apub/utils.live.ts | 19 +++- backend/src/apub/utils.stub.ts | 22 +++-- backend/src/apub/utils.ts | 110 +++++++++++++++++++++++ backend/src/handoff/index.ts | 3 + backend/src/handoff/sidecar.ts | 113 ++++++++++++++++++++++-- backend/src/lib/DocLinks.ts | 2 + backend/src/lib/utils.ts | 7 ++ backend/src/models/SidecarSession.ts | 125 +++++++++++++++++++++++++++ frontend/src/Login/Login.tsx | 1 + 12 files changed, 400 insertions(+), 17 deletions(-) create mode 100644 backend/src/models/SidecarSession.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 697cab5..58f1817 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "yaml.schemas": { "https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json": ".gitlab/**/*.yml" - } + }, + "prisma.pinToPrisma6": true } diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index f3e5131..2b6fefd 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -60,3 +60,14 @@ model HandoffSession { updatedAt DateTime @default(now()) @updatedAt expiresAt DateTime } + +model SidecarSession { + id String @id @default(uuid()) + consumerClientId String + sessionData String? + userId String? // null when not yet authenticated + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + expiresAt DateTime +} diff --git a/backend/src/apub/federation.ts b/backend/src/apub/federation.ts index 810e7da..926df2b 100644 --- a/backend/src/apub/federation.ts +++ b/backend/src/apub/federation.ts @@ -144,6 +144,7 @@ federation if (object instanceof Note || object instanceof ChatMessage) { Handoff.get().activitypub.handle(actor, object, create); + Handoff.get().sidecar.handle(actor, object); } else { console.log("create object unknown type", create, object); } diff --git a/backend/src/apub/utils.live.ts b/backend/src/apub/utils.live.ts index 2662506..c5aa553 100644 --- a/backend/src/apub/utils.live.ts +++ b/backend/src/apub/utils.live.ts @@ -1,6 +1,7 @@ import { Actor, ChatMessage, + ConstructorWithTypeId, Context, Create, Delete, @@ -10,13 +11,15 @@ import { lookupWebFinger, Mention, Note, + Object, Tombstone, } from "@fedify/fedify"; import { federation, USER_IDENTIFIER } from "./federation.js"; import { Temporal } from "@js-temporal/polyfill"; import { AuthSession } from "../models/AuthSession.js"; import { IProfile } from "../lib/instance/userMeta.js"; -import { getSafeURL } from "../lib/utils.js"; +import { _staticType, getSafeURL } from "../lib/utils.js"; +import { IAPubUtils, IAPubUtils_Static } from "./utils.js"; type BuildObjectOpts = { id: string; @@ -25,7 +28,7 @@ type BuildObjectOpts = { target: Actor; }; -export class APubLive { +export class APubLive implements IAPubUtils { ctx: Context; constructor(ctx: Context) { @@ -221,7 +224,7 @@ export class APubLive { ); } - private async deleteChatMessage(id: string, target: Actor) { + async deleteChatMessage(id: string, target: Actor) { const sender = this.ctx.getActorUri(USER_IDENTIFIER); await this.ctx.sendActivity( @@ -257,7 +260,7 @@ export class APubLive { ); } - private async deleteNote(id: string, target: Actor) { + async deleteNote(id: string, target: Actor) { const sender = this.ctx.getActorUri(USER_IDENTIFIER); await this.ctx.sendActivity( @@ -274,6 +277,12 @@ export class APubLive { ); } + getObjectUri = ( + cls: ConstructorWithTypeId, + values: Record, + ) => this.ctx.getObjectUri(cls, values); + getActorUri = (identifier: string) => this.ctx.getActorUri(identifier); + build(type: "ChatMessage", opts: BuildObjectOpts): ChatMessage; build(type: "Note", opts: BuildObjectOpts): Note; build(type: "ChatMessage" | "Note", opts: BuildObjectOpts): unknown { @@ -314,3 +323,5 @@ Do not share this code. This code is used to identify you.`, } } } + +_staticType(APubLive); diff --git a/backend/src/apub/utils.stub.ts b/backend/src/apub/utils.stub.ts index aed032c..73fc96b 100644 --- a/backend/src/apub/utils.stub.ts +++ b/backend/src/apub/utils.stub.ts @@ -6,8 +6,10 @@ import { Actor, ChatMessage, + ConstructorWithTypeId, Context, Note, + Object, Person, ResourceDescriptor, } from "@fedify/fedify"; @@ -15,12 +17,10 @@ import { IProfile } from "../lib/instance/userMeta.js"; import { USER_IDENTIFIER } from "./federation.js"; import { APubLive } from "./utils.live.js"; import { AuthSession } from "../models/AuthSession.js"; +import { _staticType } from "../lib/utils.js"; +import { IAPubUtils, IAPubUtils_Static } from "./utils.js"; -export class APubStub extends APubLive { - constructor() { - super(null as any); - } - +export class APubStub implements IAPubUtils { static options = () => ({}); static get accountHandle() { @@ -73,13 +73,21 @@ export class APubStub extends APubLive { async sendChatMessage(id: string, target: Actor, content: ChatMessage) {} + async deleteChatMessage(id: string, target: Actor) {} + async sendNote(id: string, target: Actor, content: Note) {} + async deleteNote(id: string, target: Actor) {} + + getObjectUri = (cls: unknown, values: Record) => + new URL(`http://localhost/object/${String(cls)}-${JSON.stringify(values)}`); + getActorUri = (identifier: string) => + new URL(`/actor/${identifier}`, "http://localhost"); + build( type: "ChatMessage" | "Note", opts: { id: string; one_time_code: string; createdAt: Date; target: Actor }, ): any {} } -const _staticTypeCheck: typeof APubLive = APubStub; -_staticTypeCheck.get(); // to ignore "unused variable" but still trigger type check +_staticType(APubStub); diff --git a/backend/src/apub/utils.ts b/backend/src/apub/utils.ts index 279f834..f2c7190 100644 --- a/backend/src/apub/utils.ts +++ b/backend/src/apub/utils.ts @@ -1,5 +1,115 @@ +import { + Actor, + ChatMessage, + Context, + LookupObjectOptions, + Note, + ResourceDescriptor, +} from "@fedify/fedify"; import { Flags } from "../lib/flags.js"; import { APubLive } from "./utils.live.js"; import { APubStub } from "./utils.stub.js"; +import { IProfile } from "../lib/instance/userMeta.js"; +import { AuthSession } from "../models/AuthSession.js"; export const APub = Flags.Dev.UseAPubStub ? APubStub : APubLive; + +export interface IAPubUtils { + /** + * Send a ChatMessage to an actor + * + * Not many fediverse software supports this, but Lemmy <0.19 uses this exclusively for DMs + */ + sendChatMessage( + id: string, + target: Actor, + content: ChatMessage, + ): Promise; + + /** + * Delete a ChatMessage + */ + deleteChatMessage(id: string, target: Actor): Promise; + + /** + * Send a private Note to an actor + * + * Most fediverse software supports this + */ + sendNote(id: string, target: Actor, content: Note): Promise; + + /** + * Delete a Note + */ + deleteNote(id: string, target: Actor): Promise; + + getObjectUri: Context["getObjectUri"]; + getActorUri: Context["getActorUri"]; + + /** + * Create ChatMessage or Note for one-time DM + */ + build(type: "ChatMessage", opts: BuildObjectOpts): ChatMessage; + build(type: "Note", opts: BuildObjectOpts): Note; + build(type: "ChatMessage" | "Note", opts: BuildObjectOpts): unknown; +} + +export interface IAPubUtils_Static { + /** + * Build options for object lookup + */ + options( + ctx: Context, + opts: Partial, + ): LookupObjectOptions; + + /** + * get the account handle for the service's actor + */ + get accountHandle(): string; + + /** + * get an instance if APubUtils + */ + get(): IAPubUtils; + + /** + * Build an IProfile given an identifier + * + * @param identifier either a @user@handle.com or a FQ url for an activitypub actor + */ + buildProfile(identifier: ActorIdentifier): Promise; + + /** + * Lookup webfinger resource for actor + */ + lookupWebfinger( + identifier: ActorIdentifier, + ): Promise; + + /** + * Lookup actor from identifier + */ + lookupActor(identifier: ActorIdentifier): Promise; + + /** + * Send handoff DM (one-time code DM) + */ + sendDM(session: AuthSession): Promise; + + /** + * Delete handoff DM (one-time code DM) + */ + deleteDM(session: AuthSession): Promise; +} + +export type ActorIdentifier = + | `@${string}@${string}` + | `${string}@${string}` + | URL; +export type BuildObjectOpts = { + id: string; + one_time_code: string; + createdAt: Date; + target: Actor; +}; diff --git a/backend/src/handoff/index.ts b/backend/src/handoff/index.ts index 420ba0e..cb6bece 100644 --- a/backend/src/handoff/index.ts +++ b/backend/src/handoff/index.ts @@ -1,5 +1,7 @@ +import { APub } from "../apub/utils.js"; import { HandoffActivityPub } from "./activitypub.js"; import { router } from "./router.js"; +import { HandoffSidecar } from "./sidecar.js"; export class Handoff { private static instance: Handoff; @@ -21,5 +23,6 @@ export class Handoff { static readonly HANDOFF_TOKEN = process.env.HANDOFF_TOKEN!; readonly activitypub = new HandoffActivityPub(); + readonly sidecar = new HandoffSidecar(APub.accountHandle as any); readonly router = router; } diff --git a/backend/src/handoff/sidecar.ts b/backend/src/handoff/sidecar.ts index 70ad9ff..5d13322 100644 --- a/backend/src/handoff/sidecar.ts +++ b/backend/src/handoff/sidecar.ts @@ -1,4 +1,21 @@ +import { Actor, ChatMessage, Mention, Note } from "@fedify/fedify"; import { SidecarApps } from "./sidecar.apps.js"; +import { SidecarSession, SidecarSessionID } from "../models/SidecarSession.js"; +import EventEmitter from "node:events"; +import { APub } from "../apub/utils.js"; +import { USER_IDENTIFIER } from "../apub/federation.js"; +import { Temporal } from "@js-temporal/polyfill"; +import { DocLinks } from "../lib/DocLinks.js"; + +interface IHandoffSidecarEvents { + /** + * Notify when a specific session has been authenticated + * + * @warn this may take a while to authenticate, and can also never be called + * the sidecar session is set to expire after 15 minutes (@see SidecarSession#create) + */ + sessionAuth: [sessionId: SidecarSessionID, actor: URL]; +} /** * Handoff Sidecar @@ -16,7 +33,7 @@ import { SidecarApps } from "./sidecar.apps.js"; * in registered oidc client applications (for example, Canvas). Canvas will have the ability * to "drive" fediauth via postmessage and receive updates the same way. */ -export class HandoffSidecar { +export class HandoffSidecar extends EventEmitter { static readonly DEV_SIDECAR = { id: "dev", name: "dev sidecar", @@ -27,14 +44,21 @@ export class HandoffSidecar { ? [this.DEV_SIDECAR, ...SidecarApps] : SidecarApps) satisfies ISupportedSidecar[]; - constructor(private authActor: `${string}@${string}`) {} + constructor(private authActor: `${string}@${string}`) { + super(); + } /** * Create a sidecar session */ - createSession(consumer: ISidecarConsumer = { client_id: "internal" }) {} + createSession( + consumer: ISidecarConsumer = { client_id: "internal" }, + ): SidecarSessionID { + // TODO: create sidecar sessions + return "sidecar"; + } - buildAuthURI(sidecarId: SidecarID, session: SidecarSession) { + buildAuthURI(sidecarId: SidecarID, session: SidecarSessionID) { const sidecar = HandoffSidecar.SUPPORTED_APPS.find( (s) => s.id === sidecarId, )!; @@ -45,10 +69,89 @@ export class HandoffSidecar { return url; } + + async handle(actor: Actor, object: Message) { + if (!object.content && !object.contents[0]) return; + if (!actor.id) return; + + const realContent = + object.content?.toString() ?? object.contents[0]?.toString(); + + if (!new RegExp(SidecarSession.Regex).test(realContent)) return; + + const [, sessionId] = new RegExp(SidecarSession.Regex).exec(realContent)!; + + const session = await SidecarSession.get(sessionId as any); + + if (!session) { + // session is not found + // at this point, the message did match the expected `sidecar{...}` layout + // so we should notify the actor, as the client/ap-server might be mangling it + + await this.sendDM( + actor, + `unknown-session-${Date.now()}`, + `Invalid session ID (did it get mangled?) (${sessionId}) + +${DocLinks.SIDECAR_DOCS}`, + ); + + return; + } + + await session.claimFor(String(actor.id)); + this.emit("sessionAuth", session.id, actor.id); // TODO: react to session auth + + await this.sendDM( + actor, + `session-auth-${session.id}-${Date.now()}`, + `Authenticated as ${String(actor.id)}! + +${DocLinks.SIDECAR_DOCS}`, + ); + } + + sendDM(actor: Actor, id: string, message: string) { + const apub = APub.get(); + const sender = apub.getActorUri(USER_IDENTIFIER); + + return Promise.allSettled([ + apub.sendNote( + `sidecar-${id}-note`, + actor, + new Note({ + id: apub.getObjectUri(Note, { id: `sidecar-${id}-note` }), + attribution: sender, + to: actor.id!, + published: Temporal.Now.instant(), + tags: [ + new Mention({ + href: actor.id, + name: actor.id!.toString(), + }), + ], + content: message, + }), + ), + apub.sendChatMessage( + `sidecar-${id}-chatmessage`, + actor, + new ChatMessage({ + id: apub.getObjectUri(ChatMessage, { + id: `sidecar-${id}-chatmessage`, + }), + attribution: sender, + to: actor.id!, + published: Temporal.Now.instant(), + content: message, + }), + ), + ]); + } } +type Message = Note | ChatMessage; type SidecarID = (typeof HandoffSidecar)["SUPPORTED_APPS"][number]["id"]; -type SidecarSession = `s${string}`; export interface ISupportedSidecar { /** diff --git a/backend/src/lib/DocLinks.ts b/backend/src/lib/DocLinks.ts index 6eb01ec..6df3012 100644 --- a/backend/src/lib/DocLinks.ts +++ b/backend/src/lib/DocLinks.ts @@ -2,4 +2,6 @@ export const DocLinks = { SOURCE: "https://sc07.dev/sc07/fediverse-auth", DOCS: "https://sc07.dev/sc07/fediverse-auth/-/wikis", HANDOFF_DOCS: "https://sc07.dev/sc07/fediverse-auth/-/wikis/handoff", + // TODO: pick sidecar docs path / write wiki page + SIDECAR_DOCS: "https://sc07.dev/sc07/fediverse-auth/-/wikis/handoff#sidecar", }; diff --git a/backend/src/lib/utils.ts b/backend/src/lib/utils.ts index 400bb0b..7c65dfe 100644 --- a/backend/src/lib/utils.ts +++ b/backend/src/lib/utils.ts @@ -88,3 +88,10 @@ export const getExpressIP = (req: Request): string => { return req.socket.remoteAddress!; }; + +/** + * Dummy function to enforce static properties on a class + * + * @example _staticType(ClassName) + */ +export const _staticType = (clazz: T) => {}; diff --git a/backend/src/models/SidecarSession.ts b/backend/src/models/SidecarSession.ts new file mode 100644 index 0000000..0970010 --- /dev/null +++ b/backend/src/models/SidecarSession.ts @@ -0,0 +1,125 @@ +import { prisma } from "../lib/prisma.js"; +import { SidecarSession as DBSidecarSession } from "@prisma/client"; + +export type SidecarSessionID = `sidecar${string}`; + +export class SidecarSession { + static readonly Regex = /(?:^|\s)(sidecar[0-9a-zA-Z-]+)(?:$|\s)/gm; + + static async create( + consumerClientId: string, + sessionData?: string, + ): Promise { + // 15 minutes + const expiresAt = new Date(Date.now() + 1000 * 60 * 15); + + const session = await prisma.sidecarSession.create({ + data: { + consumerClientId, + sessionData, + expiresAt, + }, + }); + + return new SidecarSession(session); + } + + static async getExpired() { + const session = await prisma.sidecarSession.findMany({ + where: { + expiresAt: { + lte: new Date(), + }, + }, + }); + return session.map((d) => new SidecarSession(d)); + } + + /** + * convert sidecarId to database id + */ + private static sidecarId(id: SidecarSessionID) { + return id.replace("sidecar", ""); + } + + static async get(id: SidecarSessionID) { + const session = await prisma.sidecarSession.findFirst({ + where: { + id: this.sidecarId(id), + expiresAt: { gt: new Date() }, + }, + }); + + if (!session) return null; + + return new SidecarSession(session); + } + + private _id: string; + private _consumerClientId: string; + private _sessionData: string | null; + private _userId: string | null; + private _createdAt: Date; + private _updatedAt: Date; + private _expiresAt: Date; + + private constructor(session: DBSidecarSession) { + this._id = session.id; + this._consumerClientId = session.consumerClientId; + this._sessionData = session.sessionData; + this._userId = session.userId; + this._createdAt = session.createdAt; + this._updatedAt = session.updatedAt; + this._expiresAt = session.expiresAt; + } + + get id(): SidecarSessionID { + return `sidecar${this._id}`; + } + + get consumerClientId() { + return this._consumerClientId; + } + + get sessionData() { + return this._sessionData; + } + + /** + * URL of the actor, if the session was claimed + */ + get userId() { + return this._userId; + } + + get createdAt() { + return this._createdAt; + } + + get updatedAt() { + return this._updatedAt; + } + + get expiresAt() { + return this._expiresAt; + } + + async claimFor(userId: string) { + if (this._userId) throw new Error("Session is already claimed"); + + await prisma.sidecarSession.update({ + data: { + userId, + }, + where: { + id: this._id, + userId: null, + }, + }); + + this._userId = (await prisma.sidecarSession.findFirst({ + where: { id: this._id }, + select: { id: true }, + }))!.id; + } +} diff --git a/frontend/src/Login/Login.tsx b/frontend/src/Login/Login.tsx index 64abf40..074c5f4 100644 --- a/frontend/src/Login/Login.tsx +++ b/frontend/src/Login/Login.tsx @@ -28,6 +28,7 @@ enum LoginState { CODE_INPUT, } +// TODO: implement sidecar UI export const LoginPage = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); -- GitLab