Compare commits

...

153 commits

Author SHA1 Message Date
87dac36860 Merge pull request '2024.11.0-yumechinokuni.7' (#41) from develop into master
Some checks failed
Lint / pnpm_install (push) Successful in 1m27s
Test (production install and build) / production (22.11.0) (push) Successful in 58s
Publish Docker image / Build (push) Successful in 4m39s
Lint / lint (backend) (push) Successful in 2m7s
Lint / lint (frontend) (push) Successful in 1m59s
Lint / lint (frontend-embed) (push) Successful in 1m59s
Test (backend) / unit (22.11.0) (push) Failing after 7m27s
Lint / lint (frontend-shared) (push) Successful in 1m59s
Lint / lint (misskey-bubble-game) (push) Successful in 2m11s
Lint / lint (misskey-js) (push) Successful in 2m8s
Lint / lint (misskey-reversi) (push) Successful in 2m8s
Lint / lint (sw) (push) Successful in 2m9s
Lint / typecheck (misskey-js) (push) Successful in 1m18s
Lint / typecheck (backend) (push) Successful in 2m1s
Lint / typecheck (sw) (push) Successful in 1m23s
Reviewed-on: #41
2024-11-22 10:16:03 -06:00
d591282f5e
2024.11.0-yumechinokuni.7
Some checks failed
Lint / pnpm_install (push) Successful in 1m48s
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Publish Docker image / Build (push) Successful in 4m43s
Lint / lint (backend) (push) Successful in 2m4s
Lint / lint (frontend) (push) Successful in 1m56s
Lint / lint (frontend-embed) (push) Successful in 1m58s
Test (backend) / unit (22.11.0) (push) Failing after 7m27s
Lint / lint (frontend-shared) (push) Successful in 2m8s
Lint / lint (misskey-bubble-game) (push) Successful in 2m5s
Lint / lint (misskey-js) (push) Successful in 2m3s
Lint / lint (misskey-reversi) (push) Successful in 1m59s
Lint / lint (sw) (push) Successful in 2m9s
Lint / typecheck (backend) (push) Successful in 1m50s
Lint / typecheck (misskey-js) (push) Successful in 1m35s
Lint / typecheck (sw) (push) Successful in 1m47s
Lint / pnpm_install (pull_request) Successful in 1m21s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 58s
Publish Docker image / Build (pull_request) Successful in 4m25s
Lint / lint (backend) (pull_request) Successful in 2m11s
Lint / lint (frontend) (pull_request) Successful in 1m55s
Lint / lint (frontend-embed) (pull_request) Successful in 1m56s
Lint / lint (frontend-shared) (pull_request) Successful in 1m56s
Test (backend) / unit (22.11.0) (pull_request) Failing after 7m25s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m4s
Lint / lint (misskey-js) (pull_request) Successful in 2m34s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m5s
Lint / lint (sw) (pull_request) Successful in 2m23s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m32s
Lint / typecheck (backend) (pull_request) Successful in 2m12s
Lint / typecheck (sw) (pull_request) Successful in 1m31s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-22 09:55:32 -06:00
FLY_MC
873ef89e42
retry pick pari sw.ts
Some checks failed
Lint / pnpm_install (push) Successful in 1m51s
Test (production install and build) / production (22.11.0) (push) Successful in 1m0s
Publish Docker image / Build (push) Successful in 4m27s
Lint / lint (backend) (push) Successful in 2m22s
Lint / lint (frontend) (push) Successful in 1m56s
Lint / lint (frontend-embed) (push) Successful in 1m59s
Test (backend) / unit (22.11.0) (push) Failing after 7m32s
Lint / lint (frontend-shared) (push) Successful in 2m0s
Lint / lint (misskey-bubble-game) (push) Successful in 2m19s
Lint / lint (misskey-js) (push) Successful in 1m59s
Lint / lint (misskey-reversi) (push) Successful in 2m2s
Lint / lint (sw) (push) Successful in 2m14s
Lint / typecheck (misskey-js) (push) Successful in 1m22s
Lint / typecheck (backend) (push) Successful in 2m5s
Lint / typecheck (sw) (push) Successful in 1m10s
2024-11-22 09:38:18 -06:00
d7a8660952
set connect-src for sw.js
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-22 09:37:41 -06:00
eec5ce1a99
Remove trademarked branding
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-22 09:35:51 -06:00
f7cdb9df70
bump fedivet
Some checks failed
Lint / pnpm_install (push) Successful in 1m35s
Publish Docker image / Build (push) Successful in 3m55s
Test (production install and build) / production (22.11.0) (push) Successful in 1m15s
Lint / lint (backend) (push) Successful in 2m14s
Lint / lint (frontend) (push) Successful in 1m55s
Lint / lint (frontend-embed) (push) Successful in 1m59s
Lint / lint (frontend-shared) (push) Successful in 2m1s
Test (backend) / unit (22.11.0) (push) Failing after 7m56s
Lint / lint (misskey-bubble-game) (push) Successful in 2m12s
Lint / lint (misskey-js) (push) Successful in 2m9s
Lint / lint (misskey-reversi) (push) Successful in 2m12s
Lint / typecheck (backend) (push) Successful in 2m19s
Lint / lint (sw) (push) Successful in 2m33s
Lint / typecheck (misskey-js) (push) Successful in 1m29s
Lint / typecheck (sw) (push) Successful in 1m12s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-22 08:57:48 -06:00
2b1c4b7245
2024.11.0-yumechinokuni.6p2
Some checks failed
Lint / pnpm_install (push) Successful in 1m48s
Test (production install and build) / production (22.11.0) (push) Successful in 1m0s
Publish Docker image / Build (push) Successful in 4m41s
Lint / lint (backend) (push) Successful in 2m7s
Lint / lint (frontend) (push) Successful in 2m6s
Lint / lint (frontend-embed) (push) Successful in 2m5s
Test (backend) / unit (22.11.0) (push) Failing after 7m59s
Lint / lint (frontend-shared) (push) Successful in 2m5s
Lint / lint (misskey-bubble-game) (push) Successful in 2m8s
Lint / lint (misskey-js) (push) Successful in 2m2s
Lint / lint (misskey-reversi) (push) Successful in 2m15s
Lint / lint (sw) (push) Successful in 2m1s
Lint / typecheck (misskey-js) (push) Successful in 1m28s
Lint / typecheck (backend) (push) Successful in 2m6s
Lint / typecheck (sw) (push) Successful in 1m28s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 12:38:16 -06:00
e885beaab9
Revert "Pick pari sw.ts"
Some checks failed
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 1m44s
Test (production install and build) / production (22.11.0) (push) Successful in 56s
Publish Docker image / Build (push) Successful in 4m38s
Lint / lint (backend) (push) Successful in 2m5s
Lint / lint (frontend-embed) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
This reverts commit 8e508b921c.

Revert "add media proxy to worker-src"

This reverts commit b29f49fefc.
2024-11-21 12:32:43 -06:00
d25fa27c24
add content and tag to ap safelist
Some checks failed
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 1m49s
Test (production install and build) / production (22.11.0) (push) Successful in 1m0s
Publish Docker image / Build (push) Successful in 4m42s
Lint / lint (backend) (push) Successful in 2m5s
Lint / lint (frontend) (push) Successful in 1m52s
Lint / lint (frontend-embed) (push) Successful in 2m3s
Test (backend) / unit (22.11.0) (push) Failing after 8m4s
Lint / lint (misskey-bubble-game) (push) Successful in 1m58s
Lint / lint (frontend-shared) (push) Has been cancelled
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (misskey-js) (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 12:23:48 -06:00
7a0067460b Merge pull request 'Merge upstream' (#40) from develop into master
Some checks failed
Lint / pnpm_install (push) Successful in 1m39s
Test (production install and build) / production (22.11.0) (push) Successful in 1m5s
Publish Docker image / Build (push) Successful in 4m56s
Lint / lint (backend) (push) Successful in 2m17s
Lint / lint (frontend) (push) Successful in 2m0s
Lint / lint (frontend-embed) (push) Successful in 1m59s
Lint / lint (frontend-shared) (push) Successful in 1m56s
Lint / lint (misskey-bubble-game) (push) Successful in 2m6s
Test (backend) / unit (22.11.0) (push) Failing after 8m8s
Lint / lint (misskey-reversi) (push) Successful in 2m2s
Lint / lint (misskey-js) (push) Successful in 2m40s
Lint / lint (sw) (push) Successful in 2m0s
Lint / typecheck (misskey-js) (push) Successful in 1m27s
Lint / typecheck (sw) (push) Successful in 1m32s
Lint / typecheck (backend) (push) Successful in 2m18s
Reviewed-on: #40
2024-11-21 11:40:02 -06:00
63a98f3b41
2024.11.0-yumechinokuni.6
Some checks failed
Lint / pnpm_install (pull_request) Successful in 1m30s
Lint / pnpm_install (push) Successful in 1m9s
Publish Docker image / Build (push) Successful in 4m58s
Publish Docker image / Build (pull_request) Successful in 4m6s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m21s
Test (production install and build) / production (22.11.0) (push) Successful in 1m1s
Lint / lint (backend) (pull_request) Successful in 2m1s
Lint / lint (frontend) (pull_request) Successful in 2m19s
Test (backend) / unit (22.11.0) (pull_request) Failing after 7m47s
Test (backend) / unit (22.11.0) (push) Failing after 7m57s
Lint / lint (frontend-shared) (pull_request) Successful in 2m20s
Lint / lint (frontend-embed) (pull_request) Successful in 2m34s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m13s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m25s
Lint / lint (misskey-js) (pull_request) Successful in 2m27s
Lint / lint (sw) (pull_request) Successful in 2m14s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m24s
Lint / typecheck (backend) (pull_request) Successful in 1m56s
Lint / typecheck (sw) (pull_request) Successful in 1m29s
Lint / lint (backend) (push) Successful in 2m12s
Lint / lint (frontend) (push) Successful in 2m8s
Lint / lint (frontend-embed) (push) Successful in 2m6s
Lint / lint (frontend-shared) (push) Successful in 2m7s
Lint / lint (misskey-bubble-game) (push) Successful in 2m8s
Lint / lint (misskey-js) (push) Successful in 2m9s
Lint / lint (misskey-reversi) (push) Failing after 1m37s
Lint / lint (sw) (push) Successful in 2m16s
Lint / typecheck (backend) (push) Successful in 2m4s
Lint / typecheck (misskey-js) (push) Successful in 1m31s
Lint / typecheck (sw) (push) Successful in 1m53s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 11:23:41 -06:00
b29f49fefc
add media proxy to worker-src
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 11:21:45 -06:00
fly_mc
8e508b921c Pick pari sw.ts 2024-11-21 10:58:08 -06:00
9052a02598
fix a mishap during merging upstream
Some checks failed
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 1m39s
Test (production install and build) / production (22.11.0) (push) Successful in 1m5s
Publish Docker image / Build (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Lint / pnpm_install (pull_request) Successful in 1m52s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m7s
Publish Docker image / Build (pull_request) Successful in 5m5s
Test (backend) / unit (22.11.0) (pull_request) Failing after 10m20s
Lint / lint (backend) (pull_request) Successful in 2m0s
Lint / lint (frontend) (pull_request) Successful in 2m15s
Lint / lint (frontend-shared) (pull_request) Successful in 2m25s
Lint / lint (frontend-embed) (pull_request) Successful in 2m44s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m16s
Lint / lint (misskey-js) (pull_request) Successful in 2m11s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m14s
Lint / lint (sw) (pull_request) Successful in 2m16s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m27s
Lint / typecheck (backend) (pull_request) Successful in 1m59s
Lint / typecheck (sw) (pull_request) Successful in 1m31s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 10:20:20 -06:00
57c4fef275 Merge branch 'master' into develop 2024-11-21 10:19:57 -06:00
748685e53e
fix handling of private renotes
Some checks failed
Lint / pnpm_install (push) Successful in 1m43s
Publish Docker image / Build (push) Successful in 4m15s
Test (production install and build) / production (22.11.0) (push) Successful in 54s
Lint / lint (backend) (push) Successful in 1m58s
Test (backend) / unit (22.11.0) (push) Failing after 8m4s
Lint / lint (frontend) (push) Successful in 2m35s
Lint / lint (frontend-embed) (push) Successful in 2m15s
Lint / lint (frontend-shared) (push) Successful in 2m14s
Lint / lint (misskey-bubble-game) (push) Successful in 2m17s
Test (backend) / e2e (22.11.0) (push) Failing after 11m49s
Lint / lint (misskey-js) (push) Successful in 2m17s
Lint / lint (misskey-reversi) (push) Successful in 2m30s
Lint / typecheck (backend) (push) Failing after 1m52s
Lint / lint (sw) (push) Successful in 2m32s
Lint / typecheck (misskey-js) (push) Successful in 1m29s
Lint / typecheck (sw) (push) Successful in 1m53s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 10:11:42 -06:00
8212c62663 Merge pull request 'prepare for upstream release' (#39) from incoming into develop
Some checks failed
Lint / pnpm_install (push) Successful in 1m41s
Test (production install and build) / production (22.11.0) (push) Successful in 56s
Publish Docker image / Build (push) Successful in 3m57s
Lint / lint (backend) (push) Successful in 2m12s
Lint / lint (frontend) (push) Successful in 2m4s
Lint / lint (frontend-embed) (push) Successful in 1m54s
Test (backend) / unit (22.11.0) (push) Failing after 7m4s
Lint / lint (frontend-shared) (push) Successful in 2m2s
Lint / lint (misskey-bubble-game) (push) Successful in 2m3s
Lint / lint (misskey-js) (push) Successful in 2m0s
Lint / lint (misskey-reversi) (push) Successful in 1m59s
Lint / lint (sw) (push) Successful in 1m58s
Lint / typecheck (backend) (push) Successful in 1m54s
Lint / typecheck (misskey-js) (push) Successful in 1m16s
Lint / typecheck (sw) (push) Successful in 1m12s
Reviewed-on: #39
2024-11-21 09:04:32 -06:00
8d48909e4f
remove unused httpAgent
All checks were successful
Lint / pnpm_install (push) Successful in 1m31s
Test (production install and build) / production (22.11.0) (push) Successful in 1m18s
Lint / lint (backend) (push) Successful in 1m50s
Publish Docker image / Build (push) Successful in 4m1s
Lint / lint (frontend) (push) Successful in 2m19s
Lint / lint (frontend-embed) (push) Successful in 2m13s
Lint / lint (frontend-shared) (push) Successful in 2m4s
Lint / lint (misskey-bubble-game) (push) Successful in 2m4s
Lint / lint (misskey-js) (push) Successful in 2m6s
Lint / lint (misskey-reversi) (push) Successful in 2m6s
Lint / lint (sw) (push) Successful in 2m8s
Lint / typecheck (misskey-js) (push) Successful in 1m16s
Lint / typecheck (backend) (push) Successful in 2m0s
Lint / typecheck (sw) (push) Successful in 1m23s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 06:19:09 -06:00
5587de26c7
fix sw.js initialization order
Some checks are pending
Publish Docker image / Build (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (production install and build) / production (22.11.0) (push) Waiting to run
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 06:05:51 -06:00
FLY_MC
9bb310e0d1
pick pari sw.ts 2024-11-21 05:54:22 -06:00
d621657f16
remove e2e testing
Some checks failed
Lint / pnpm_install (pull_request) Successful in 1m7s
Publish Docker image / Build (pull_request) Successful in 4m12s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 56s
Test (backend) / unit (22.11.0) (pull_request) Failing after 8m40s
Lint / lint (backend) (pull_request) Successful in 1m50s
Lint / lint (frontend) (pull_request) Successful in 1m47s
Lint / lint (frontend-embed) (pull_request) Successful in 1m45s
Lint / lint (frontend-shared) (pull_request) Successful in 2m0s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m2s
Lint / lint (misskey-js) (pull_request) Successful in 1m56s
Lint / lint (misskey-reversi) (pull_request) Successful in 1m55s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m13s
Lint / typecheck (backend) (pull_request) Successful in 1m45s
Lint / lint (sw) (pull_request) Successful in 2m13s
Lint / typecheck (sw) (pull_request) Successful in 1m23s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:50:15 -06:00
b33a595b67
Merge remote-tracking branch 'upstream/develop' into incoming
Some checks failed
Lint / pnpm_install (pull_request) Successful in 1m31s
Publish Docker image / Build (pull_request) Successful in 4m2s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 55s
Test (backend) / unit (22.11.0) (pull_request) Failing after 6m54s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 10m6s
Lint / lint (backend) (pull_request) Successful in 2m2s
Lint / lint (frontend) (pull_request) Successful in 2m0s
Lint / lint (frontend-embed) (pull_request) Successful in 2m4s
Lint / lint (frontend-shared) (pull_request) Successful in 1m48s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m11s
Lint / lint (misskey-js) (pull_request) Successful in 1m58s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m8s
Lint / lint (sw) (pull_request) Successful in 2m21s
Lint / typecheck (backend) (pull_request) Successful in 1m58s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m22s
Lint / typecheck (sw) (pull_request) Successful in 1m23s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:49:44 -06:00
rectcoordsystem
ce08d2c827
Merge commit from fork
* fix(backend): check target IP before sending HTTP request

* fix(backend): allow accessing private IP when testing

* Apply suggestions from code review

Co-authored-by: anatawa12 <anatawa12@icloud.com>

* fix(backend): lint and typecheck

* fix(backend): add isLocalAddressAllowed option to getAgentByUrl and send (HttpRequestService)

* fix(backend): allow fetchSummaryFromProxy, trueMail to access local addresses

---------

Co-authored-by: anatawa12 <anatawa12@icloud.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:47:00 -06:00
Julia
a3ad95c058
Merge commit from fork
* Fix poll update spoofing

* fix: Disallow negative poll counts

---------

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:42:45 -06:00
syuilo
3b804799c3
New Crowdin updates (#15000)
* New translations ja-jp.yml (Catalan)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (German)

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:40:30 -06:00
Sayamame-beans
323de25075
Fix: リノートミュートが新規投稿通知に対して作用していなかった問題を修正 (#15006)
* fix(backend): renoteMute doesn't work for note notification

* docs(changelog): update changelog

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:40:22 -06:00
syuilo
c427e10f17
perf(frontend): reduce api requests for non-logged-in enviroment (#15001)
* wip

* Update CHANGELOG.md

* wip

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:40:12 -06:00
鴇峰 朔華
329995f4a3
feat: 絵文字のポップアップメニューに編集を追加 (#15004)
* Mod: 絵文字のポップアップメニューに編集を追加

* fix: code styleの修正

* fix: code styleの修正

* fix

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:39:33 -06:00
zawa-ch.
f0a754eaa8
Fix(backend): アカウント削除のモデレーションログが動作していないのを修正 (#14996) (#14997)
* アカウント削除のモデレーションログが動作していないのを修正

* update CHANGELOG

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:39:19 -06:00
Julia
504ead526a
Partial Merge of 5f675201f261d5db6a58d3099a190372bb2f09f0
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 05:39:09 -06:00
15e669d943 Merge branch 'master' into develop
Some checks failed
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m2s
Test (backend) / unit (22.11.0) (pull_request) Failing after 6m40s
Publish Docker image / Build (push) Successful in 4m7s
Lint / pnpm_install (push) Successful in 1m26s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 10m10s
Test (production install and build) / production (22.11.0) (push) Successful in 58s
Test (backend) / unit (22.11.0) (push) Failing after 6m18s
Test (backend) / e2e (22.11.0) (push) Failing after 10m19s
Lint / lint (backend) (pull_request) Successful in 2m7s
Lint / lint (frontend) (pull_request) Successful in 2m7s
Lint / lint (frontend-embed) (pull_request) Successful in 2m2s
Lint / lint (frontend-shared) (pull_request) Successful in 2m4s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m2s
Lint / lint (misskey-js) (pull_request) Successful in 2m2s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m3s
Lint / typecheck (backend) (pull_request) Failing after 1m35s
Lint / lint (sw) (pull_request) Successful in 2m6s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m25s
Lint / typecheck (sw) (pull_request) Successful in 1m20s
Lint / lint (backend) (push) Successful in 2m10s
Lint / lint (frontend) (push) Successful in 2m15s
Lint / lint (frontend-embed) (push) Successful in 2m9s
Lint / lint (frontend-shared) (push) Successful in 2m8s
Lint / lint (misskey-bubble-game) (push) Successful in 2m3s
Lint / lint (misskey-js) (push) Successful in 2m6s
Lint / lint (misskey-reversi) (push) Successful in 2m8s
Lint / typecheck (backend) (push) Failing after 1m33s
Lint / lint (sw) (push) Successful in 2m8s
Lint / typecheck (misskey-js) (push) Successful in 1m21s
Lint / typecheck (sw) (push) Successful in 1m18s
2024-11-21 03:51:59 -06:00
e01e82aa65
make sanitization failures permanent
Some checks failed
Lint / pnpm_install (push) Successful in 1m8s
Test (backend) / unit (22.11.0) (push) Failing after 6m45s
Lint / pnpm_install (pull_request) Successful in 1m7s
Publish Docker image / Build (pull_request) Successful in 4m10s
Test (backend) / e2e (22.11.0) (push) Failing after 10m23s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 56s
Test (backend) / unit (22.11.0) (pull_request) Failing after 6m57s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 10m10s
Lint / lint (frontend) (push) Successful in 2m6s
Lint / lint (backend) (push) Successful in 2m33s
Lint / lint (frontend-embed) (push) Successful in 2m6s
Lint / lint (misskey-bubble-game) (push) Successful in 2m6s
Lint / lint (frontend-shared) (push) Successful in 2m17s
Lint / lint (misskey-js) (push) Successful in 2m15s
Lint / typecheck (backend) (push) Failing after 1m25s
Lint / lint (sw) (push) Successful in 2m15s
Lint / lint (misskey-reversi) (push) Successful in 2m18s
Lint / typecheck (misskey-js) (push) Successful in 1m21s
Lint / typecheck (sw) (push) Successful in 1m20s
Lint / lint (backend) (pull_request) Successful in 2m11s
Lint / lint (frontend) (pull_request) Successful in 2m10s
Lint / lint (frontend-embed) (pull_request) Successful in 1m59s
Lint / lint (frontend-shared) (pull_request) Successful in 2m1s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m0s
Lint / lint (misskey-js) (pull_request) Successful in 1m59s
Lint / lint (misskey-reversi) (pull_request) Successful in 1m59s
Lint / typecheck (backend) (pull_request) Failing after 1m28s
Lint / lint (sw) (pull_request) Successful in 2m1s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m34s
Lint / typecheck (sw) (pull_request) Successful in 1m22s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 03:51:28 -06:00
a72ca7dcf4 Merge pull request 'make sanitization failures permanent' (#37) from develop into master
Some checks are pending
Publish Docker image / Build (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Reviewed-on: #37
2024-11-21 03:47:38 -06:00
599c265530
make sanitization failures permanent
Some checks failed
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Lint / pnpm_install (pull_request) Successful in 1m34s
Publish Docker image / Build (pull_request) Successful in 3m57s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 56s
Test (backend) / unit (22.11.0) (pull_request) Failing after 7m3s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 9m56s
Lint / lint (backend) (pull_request) Successful in 2m3s
Lint / lint (frontend-embed) (pull_request) Successful in 2m9s
Lint / lint (frontend) (pull_request) Successful in 2m20s
Lint / lint (frontend-shared) (pull_request) Successful in 2m7s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m13s
Lint / lint (misskey-js) (pull_request) Successful in 2m11s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m13s
Lint / typecheck (backend) (pull_request) Failing after 1m31s
Lint / lint (sw) (pull_request) Successful in 2m15s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m34s
Lint / typecheck (sw) (pull_request) Successful in 1m25s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 03:40:59 -06:00
a97b5921c9 Merge pull request 'reject all non TLSv1.2 AP queries' (#36) from develop into master
Some checks are pending
Publish Docker image / Build (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Reviewed-on: #36
2024-11-21 03:16:09 -06:00
5b6e8cc110
reject all non TLSv1.2 AP queries
Some checks failed
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Lint / pnpm_install (pull_request) Successful in 1m16s
Publish Docker image / Build (pull_request) Successful in 3m50s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 55s
Test (backend) / unit (22.11.0) (pull_request) Failing after 6m38s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 10m0s
Lint / lint (backend) (pull_request) Successful in 2m5s
Lint / lint (frontend) (pull_request) Successful in 2m0s
Lint / lint (frontend-embed) (pull_request) Successful in 1m55s
Lint / lint (frontend-shared) (pull_request) Successful in 1m55s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m1s
Lint / lint (misskey-js) (pull_request) Successful in 2m3s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m3s
Lint / typecheck (backend) (pull_request) Failing after 1m24s
Lint / lint (sw) (pull_request) Successful in 2m8s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m24s
Lint / typecheck (sw) (pull_request) Successful in 1m22s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 03:12:47 -06:00
a2517d3d03 Merge pull request 'normalize all referential AP objects' (#35) from develop into master
Some checks are pending
Publish Docker image / Build (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Reviewed-on: #35
2024-11-21 02:46:06 -06:00
9b8d02d1c3
type-safe sanitization of AP objects
Some checks failed
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (backend) / unit (22.11.0) (push) Waiting to run
Test (backend) / e2e (22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
Lint / pnpm_install (pull_request) Successful in 1m8s
Publish Docker image / Build (pull_request) Successful in 4m13s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 58s
Test (backend) / unit (22.11.0) (pull_request) Failing after 6m43s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 10m17s
Lint / lint (backend) (pull_request) Successful in 2m20s
Lint / lint (frontend) (pull_request) Successful in 1m58s
Lint / lint (frontend-embed) (pull_request) Successful in 2m23s
Lint / lint (frontend-shared) (pull_request) Successful in 2m8s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 1m56s
Lint / lint (misskey-js) (pull_request) Successful in 2m2s
Lint / lint (misskey-reversi) (pull_request) Successful in 1m58s
Lint / lint (sw) (pull_request) Successful in 1m59s
Lint / typecheck (backend) (pull_request) Successful in 1m57s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m23s
Lint / typecheck (sw) (pull_request) Successful in 1m26s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-21 02:42:46 -06:00
7a7aef71cd
do not use media proxy if emoji is local
Some checks failed
Lint / pnpm_install (push) Successful in 1m33s
Publish Docker image / Build (push) Successful in 3m47s
Test (production install and build) / production (22.11.0) (push) Successful in 58s
Lint / lint (backend) (push) Successful in 1m51s
Test (backend) / unit (22.11.0) (push) Successful in 6m52s
Lint / lint (frontend) (push) Successful in 1m55s
Lint / lint (frontend-embed) (push) Successful in 1m54s
Lint / lint (frontend-shared) (push) Successful in 2m0s
Lint / lint (misskey-bubble-game) (push) Successful in 2m2s
Test (backend) / e2e (22.11.0) (push) Failing after 10m4s
Lint / lint (misskey-js) (push) Successful in 2m7s
Lint / lint (misskey-reversi) (push) Successful in 2m8s
Lint / lint (sw) (push) Successful in 1m59s
Lint / typecheck (misskey-js) (push) Successful in 1m17s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (sw) (push) Successful in 1m19s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-20 13:23:41 -06:00
95d3fb08f4
Prefix all calls to Image and Video Processing service
Some checks failed
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m3s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m32s
Publish Docker image / Build (push) Successful in 4m37s
Lint / pnpm_install (push) Successful in 1m39s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 11m28s
Test (production install and build) / production (22.11.0) (push) Successful in 1m1s
Lint / lint (backend) (pull_request) Successful in 2m8s
Test (backend) / unit (22.11.0) (push) Successful in 7m57s
Lint / lint (frontend) (pull_request) Successful in 2m13s
Lint / lint (frontend-embed) (pull_request) Successful in 2m18s
Lint / lint (frontend-shared) (pull_request) Successful in 2m19s
Test (backend) / e2e (22.11.0) (push) Failing after 11m35s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m35s
Lint / lint (misskey-js) (pull_request) Successful in 2m20s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m34s
Lint / lint (sw) (pull_request) Successful in 2m36s
Lint / typecheck (backend) (pull_request) Successful in 2m19s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m38s
Lint / typecheck (sw) (pull_request) Successful in 1m50s
Lint / lint (backend) (push) Successful in 2m40s
Lint / lint (frontend) (push) Successful in 2m38s
Lint / lint (frontend-embed) (push) Successful in 2m30s
Lint / lint (frontend-shared) (push) Successful in 2m26s
Lint / lint (misskey-bubble-game) (push) Successful in 2m36s
Lint / lint (misskey-js) (push) Successful in 2m36s
Lint / lint (misskey-reversi) (push) Successful in 2m26s
Lint / lint (sw) (push) Successful in 2m27s
Lint / typecheck (misskey-js) (push) Failing after 36s
Lint / typecheck (backend) (push) Successful in 2m22s
Lint / typecheck (sw) (push) Successful in 1m28s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-20 02:07:13 -06:00
4ba0357d49
fix(backend): Atomically mark remote account deletions
Some checks failed
Lint / pnpm_install (push) Successful in 1m59s
Publish Docker image / Build (push) Successful in 4m56s
Test (production install and build) / production (22.11.0) (push) Successful in 1m2s
Lint / lint (backend) (push) Successful in 2m12s
Test (backend) / unit (22.11.0) (push) Successful in 8m22s
Lint / lint (frontend-embed) (push) Failing after 1m40s
Lint / lint (frontend) (push) Successful in 2m43s
Lint / lint (frontend-shared) (push) Successful in 2m20s
Lint / lint (misskey-bubble-game) (push) Successful in 2m17s
Test (backend) / e2e (22.11.0) (push) Failing after 11m44s
Lint / lint (misskey-js) (push) Successful in 2m28s
Lint / lint (misskey-reversi) (push) Successful in 2m25s
Lint / lint (sw) (push) Successful in 2m26s
Lint / typecheck (misskey-js) (push) Successful in 1m39s
Lint / typecheck (backend) (push) Successful in 2m37s
Lint / typecheck (sw) (push) Successful in 1m45s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 23:44:20 -06:00
416d71002a
improve emoji packing
Some checks failed
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m10s
Publish Docker image / Build (pull_request) Successful in 5m10s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m39s
Lint / lint (backend) (pull_request) Successful in 2m52s
Lint / lint (frontend-embed) (pull_request) Successful in 2m56s
Lint / lint (frontend) (pull_request) Successful in 3m9s
Lint / lint (frontend-shared) (pull_request) Successful in 2m51s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m51s
Lint / lint (misskey-js) (pull_request) Successful in 2m49s
Lint / lint (misskey-reversi) (pull_request) Successful in 3m0s
Lint / typecheck (backend) (pull_request) Successful in 2m48s
Lint / lint (sw) (pull_request) Successful in 2m50s
Lint / typecheck (misskey-js) (pull_request) Successful in 2m12s
Lint / typecheck (sw) (pull_request) Successful in 2m4s
Lint / pnpm_install (push) Successful in 1m56s
Publish Docker image / Build (push) Successful in 4m28s
Test (production install and build) / production (22.11.0) (push) Successful in 1m2s
Lint / lint (backend) (push) Successful in 2m8s
Test (backend) / unit (22.11.0) (push) Successful in 8m28s
Lint / lint (frontend) (push) Successful in 2m42s
Lint / lint (frontend-embed) (push) Successful in 2m19s
Lint / lint (frontend-shared) (push) Successful in 2m21s
Lint / lint (misskey-bubble-game) (push) Successful in 2m20s
Test (backend) / e2e (22.11.0) (push) Failing after 11m37s
Lint / lint (misskey-js) (push) Successful in 2m30s
Lint / lint (misskey-reversi) (push) Successful in 2m31s
Lint / lint (sw) (push) Successful in 2m28s
Lint / typecheck (misskey-js) (push) Successful in 1m37s
Lint / typecheck (backend) (push) Successful in 2m26s
Lint / typecheck (sw) (push) Successful in 1m52s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 10:57:45 -06:00
a236bbb8d4
CSP: allow blob images for cropping avatars
Some checks failed
Test (backend) / unit (22.11.0) (pull_request) Failing after 1m12s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m9s
Lint / pnpm_install (push) Successful in 1m31s
Publish Docker image / Build (push) Successful in 4m56s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m9s
Test (production install and build) / production (22.11.0) (push) Successful in 1m7s
Test (backend) / unit (22.11.0) (push) Successful in 8m3s
Test (backend) / e2e (22.11.0) (push) Successful in 12m2s
Lint / lint (frontend) (pull_request) Successful in 2m50s
Lint / lint (backend) (pull_request) Successful in 2m51s
Lint / lint (frontend-embed) (pull_request) Successful in 2m47s
Lint / lint (frontend-shared) (pull_request) Successful in 2m45s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m44s
Lint / lint (misskey-js) (pull_request) Successful in 2m52s
Lint / lint (sw) (pull_request) Successful in 2m45s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m47s
Lint / typecheck (backend) (pull_request) Successful in 2m41s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m45s
Lint / typecheck (sw) (pull_request) Successful in 1m56s
Lint / lint (backend) (push) Successful in 2m43s
Lint / lint (frontend-embed) (push) Successful in 2m41s
Lint / lint (frontend) (push) Successful in 2m53s
Lint / lint (frontend-shared) (push) Successful in 2m46s
Lint / lint (misskey-bubble-game) (push) Successful in 2m44s
Lint / lint (misskey-js) (push) Successful in 2m42s
Lint / lint (misskey-reversi) (push) Successful in 2m33s
Lint / typecheck (backend) (push) Successful in 2m19s
Lint / lint (sw) (push) Successful in 2m44s
Lint / typecheck (misskey-js) (push) Successful in 1m39s
Lint / typecheck (sw) (push) Failing after 1m23s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 06:17:23 -06:00
3d3bfad5d0
fixup! more path sanitization
All checks were successful
Lint / pnpm_install (push) Successful in 1m58s
Publish Docker image / Build (push) Successful in 5m21s
Test (production install and build) / production (22.11.0) (push) Successful in 1m5s
Lint / lint (backend) (push) Successful in 2m14s
Test (backend) / unit (22.11.0) (push) Successful in 9m27s
Lint / lint (frontend) (push) Successful in 2m37s
Lint / lint (frontend-embed) (push) Successful in 2m18s
Lint / lint (frontend-shared) (push) Successful in 2m23s
Lint / lint (misskey-bubble-game) (push) Successful in 2m20s
Test (backend) / e2e (22.11.0) (push) Successful in 12m58s
Lint / lint (misskey-js) (push) Successful in 2m34s
Lint / lint (misskey-reversi) (push) Successful in 2m35s
Lint / lint (sw) (push) Successful in 2m19s
Lint / typecheck (misskey-js) (push) Successful in 1m37s
Lint / typecheck (backend) (push) Successful in 2m29s
Lint / typecheck (sw) (push) Successful in 1m38s
2024-11-19 03:15:48 -06:00
8a26ab9fb5
2024.11.0-yumechinokuni.5
All checks were successful
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m5s
Test (backend) / unit (22.11.0) (pull_request) Successful in 9m8s
Publish Docker image / Build (push) Successful in 4m37s
Lint / pnpm_install (push) Successful in 1m25s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 11m50s
Test (production install and build) / production (22.11.0) (push) Successful in 1m1s
Lint / lint (backend) (pull_request) Successful in 1m56s
Test (backend) / unit (22.11.0) (push) Successful in 8m17s
Lint / lint (frontend) (pull_request) Successful in 2m13s
Lint / lint (frontend-embed) (pull_request) Successful in 2m30s
Lint / lint (frontend-shared) (pull_request) Successful in 2m19s
Test (backend) / e2e (22.11.0) (push) Successful in 12m12s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m39s
Lint / lint (misskey-js) (pull_request) Successful in 2m37s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m25s
Lint / typecheck (backend) (pull_request) Successful in 2m20s
Lint / lint (sw) (pull_request) Successful in 2m37s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m34s
Lint / typecheck (sw) (pull_request) Successful in 1m51s
Lint / lint (backend) (push) Successful in 2m26s
Lint / lint (frontend) (push) Successful in 2m37s
Lint / lint (frontend-embed) (push) Successful in 2m15s
Lint / lint (frontend-shared) (push) Successful in 2m26s
Lint / lint (misskey-bubble-game) (push) Successful in 2m16s
Lint / lint (misskey-js) (push) Successful in 2m22s
Lint / lint (misskey-reversi) (push) Successful in 2m21s
Lint / lint (sw) (push) Successful in 2m22s
Lint / typecheck (backend) (push) Successful in 2m20s
Lint / typecheck (misskey-js) (push) Successful in 1m33s
Lint / typecheck (sw) (push) Successful in 1m34s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 01:41:59 -06:00
fe3fd951ed
Merge tag '2024.11.0-alpha.2' into develop
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 01:37:57 -06:00
5673f0fff6 Merge pull request 'Sync upstream/develop' (#29) from incoming into develop
Some checks failed
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 2m9s
Publish Docker image / Build (push) Successful in 4m35s
Test (production install and build) / production (22.11.0) (push) Successful in 1m7s
Lint / lint (backend) (push) Successful in 2m15s
Lint / lint (frontend) (push) Has been cancelled
Test (backend) / e2e (22.11.0) (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Reviewed-on: #29
2024-11-19 01:34:05 -06:00
ccf81b9398
filter out duplicate events
All checks were successful
Test (production install and build) / production (22.11.0) (push) Successful in 1m27s
Publish Docker image / Build (push) Successful in 4m45s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-19 01:18:31 -06:00
1025d5a4c4 Merge remote-tracking branch 'upstream/develop' into incoming
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m52s
Publish Docker image / Build (pull_request) Successful in 4m19s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m1s
Lint / lint (backend) (pull_request) Successful in 2m15s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m24s
Lint / lint (frontend) (pull_request) Successful in 2m40s
Lint / lint (frontend-embed) (pull_request) Successful in 2m15s
Lint / lint (frontend-shared) (pull_request) Successful in 2m16s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m27s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 11m55s
Lint / lint (misskey-js) (pull_request) Successful in 2m39s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m28s
Lint / lint (sw) (pull_request) Successful in 2m26s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m38s
Lint / typecheck (backend) (pull_request) Successful in 2m25s
Lint / typecheck (sw) (pull_request) Successful in 1m40s
2024-11-18 18:44:41 -06:00
f0c0b75285
notifications metrics
All checks were successful
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m9s
Test (backend) / unit (22.11.0) (pull_request) Successful in 9m20s
Publish Docker image / Build (push) Successful in 4m48s
Lint / pnpm_install (push) Successful in 1m42s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m21s
Test (production install and build) / production (22.11.0) (push) Successful in 1m4s
Test (backend) / unit (22.11.0) (push) Successful in 9m3s
Lint / lint (backend) (pull_request) Successful in 2m36s
Lint / lint (frontend) (pull_request) Successful in 2m31s
Lint / lint (frontend-embed) (pull_request) Successful in 2m32s
Test (backend) / e2e (22.11.0) (push) Successful in 12m43s
Lint / lint (frontend-shared) (pull_request) Successful in 2m45s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m45s
Lint / lint (misskey-js) (pull_request) Successful in 2m43s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m45s
Lint / lint (sw) (pull_request) Successful in 2m26s
Lint / typecheck (backend) (pull_request) Successful in 2m30s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m58s
Lint / typecheck (sw) (pull_request) Successful in 1m43s
Lint / lint (backend) (push) Successful in 2m44s
Lint / lint (frontend) (push) Successful in 2m36s
Lint / lint (frontend-embed) (push) Successful in 2m24s
Lint / lint (frontend-shared) (push) Successful in 2m45s
Lint / lint (misskey-bubble-game) (push) Successful in 2m34s
Lint / lint (misskey-js) (push) Successful in 2m33s
Lint / lint (misskey-reversi) (push) Successful in 2m30s
Lint / lint (sw) (push) Successful in 2m31s
Lint / typecheck (backend) (push) Successful in 2m25s
Lint / typecheck (misskey-js) (push) Successful in 1m44s
Lint / typecheck (sw) (push) Successful in 1m44s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 13:48:45 -06:00
4b6ec86a00
backend: check AP fetch URL
All checks were successful
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m6s
Test (backend) / unit (22.11.0) (pull_request) Successful in 9m5s
Lint / pnpm_install (push) Successful in 1m28s
Publish Docker image / Build (push) Successful in 4m56s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m34s
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Lint / lint (backend) (pull_request) Successful in 2m18s
Test (backend) / unit (22.11.0) (push) Successful in 8m20s
Lint / lint (frontend) (pull_request) Successful in 2m26s
Lint / lint (frontend-embed) (pull_request) Successful in 2m26s
Lint / lint (frontend-shared) (pull_request) Successful in 2m32s
Test (backend) / e2e (22.11.0) (push) Successful in 12m9s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m46s
Lint / lint (misskey-js) (pull_request) Successful in 2m39s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m30s
Lint / lint (sw) (pull_request) Successful in 2m33s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m42s
Lint / typecheck (backend) (pull_request) Successful in 2m43s
Lint / typecheck (sw) (pull_request) Successful in 1m45s
Lint / lint (backend) (push) Successful in 2m44s
Lint / lint (frontend) (push) Successful in 2m43s
Lint / lint (frontend-embed) (push) Successful in 2m36s
Lint / lint (frontend-shared) (push) Successful in 3m2s
Lint / lint (misskey-bubble-game) (push) Successful in 2m49s
Lint / lint (misskey-js) (push) Successful in 2m48s
Lint / lint (misskey-reversi) (push) Successful in 2m51s
Lint / lint (sw) (push) Successful in 2m50s
Lint / typecheck (backend) (push) Successful in 2m50s
Lint / typecheck (misskey-js) (push) Successful in 2m7s
Lint / typecheck (sw) (push) Successful in 2m22s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 12:54:46 -06:00
677a2f0770
update ts model of drive file comment
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 11:55:10 -06:00
fly_mc
9899b3cc38 backend: change HASHTAG_RANKING_WINDOW
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 11:52:21 -06:00
6fab3e6087
Fix usability issues for queue and inbox error metrics
All checks were successful
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m7s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m35s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m9s
Lint / lint (backend) (pull_request) Successful in 2m47s
Lint / lint (frontend) (pull_request) Successful in 2m39s
Lint / lint (frontend-embed) (pull_request) Successful in 2m40s
Lint / lint (frontend-shared) (pull_request) Successful in 2m38s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m36s
Lint / lint (misskey-js) (pull_request) Successful in 2m37s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m41s
Lint / lint (sw) (pull_request) Successful in 2m43s
Lint / typecheck (backend) (pull_request) Successful in 2m25s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m42s
Lint / typecheck (sw) (pull_request) Successful in 1m34s
Lint / pnpm_install (push) Successful in 2m12s
Publish Docker image / Build (push) Successful in 4m38s
Test (production install and build) / production (22.11.0) (push) Successful in 1m5s
Lint / lint (backend) (push) Successful in 2m19s
Test (backend) / unit (22.11.0) (push) Successful in 9m4s
Lint / lint (frontend) (push) Successful in 2m29s
Lint / lint (frontend-embed) (push) Successful in 2m29s
Lint / lint (frontend-shared) (push) Successful in 2m32s
Lint / lint (misskey-bubble-game) (push) Successful in 2m32s
Test (backend) / e2e (22.11.0) (push) Successful in 12m35s
Lint / lint (misskey-js) (push) Successful in 2m43s
Lint / lint (misskey-reversi) (push) Successful in 2m43s
Lint / lint (sw) (push) Successful in 2m37s
Lint / typecheck (backend) (push) Successful in 2m22s
Lint / typecheck (misskey-js) (push) Successful in 1m43s
Lint / typecheck (sw) (push) Successful in 1m44s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 07:12:12 -06:00
31f19bd686
csp: add Bull Dashboard to whitelist
All checks were successful
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m8s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m31s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m13s
Lint / lint (backend) (pull_request) Successful in 2m34s
Lint / lint (frontend) (pull_request) Successful in 2m27s
Lint / lint (frontend-embed) (pull_request) Successful in 2m26s
Lint / lint (frontend-shared) (pull_request) Successful in 2m33s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m34s
Lint / lint (misskey-js) (pull_request) Successful in 2m20s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m33s
Lint / lint (sw) (pull_request) Successful in 2m33s
Lint / typecheck (backend) (pull_request) Successful in 2m26s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m42s
Lint / typecheck (sw) (pull_request) Successful in 1m42s
Lint / pnpm_install (push) Successful in 2m10s
Publish Docker image / Build (push) Successful in 4m41s
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Lint / lint (backend) (push) Successful in 2m17s
Test (backend) / unit (22.11.0) (push) Successful in 9m10s
Lint / lint (frontend) (push) Successful in 2m25s
Lint / lint (frontend-embed) (push) Successful in 2m30s
Lint / lint (frontend-shared) (push) Successful in 2m33s
Lint / lint (misskey-bubble-game) (push) Successful in 2m48s
Test (backend) / e2e (22.11.0) (push) Successful in 12m22s
Lint / lint (misskey-js) (push) Successful in 2m44s
Lint / lint (sw) (push) Successful in 2m49s
Lint / lint (misskey-reversi) (push) Successful in 2m51s
Lint / typecheck (backend) (push) Successful in 2m51s
Lint / typecheck (misskey-js) (push) Successful in 1m40s
Lint / typecheck (sw) (push) Successful in 1m52s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 03:45:47 -06:00
b78edccd19 Fix permetheus query fingerprinting (#24)
Some checks failed
Publish Docker image / Build (push) Successful in 4m42s
Lint / pnpm_install (push) Successful in 1m27s
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Test (backend) / unit (22.11.0) (push) Successful in 8m38s
Test (backend) / e2e (22.11.0) (push) Failing after 11m33s
Lint / lint (backend) (push) Successful in 2m38s
Lint / lint (frontend) (push) Successful in 2m32s
Lint / lint (frontend-embed) (push) Successful in 2m33s
Lint / lint (frontend-shared) (push) Successful in 2m30s
Lint / lint (misskey-bubble-game) (push) Successful in 2m29s
Lint / lint (misskey-js) (push) Successful in 2m28s
Lint / lint (misskey-reversi) (push) Successful in 2m25s
Lint / lint (sw) (push) Successful in 2m27s
Lint / typecheck (backend) (push) Successful in 2m26s
Lint / typecheck (misskey-js) (push) Successful in 1m40s
Lint / typecheck (sw) (push) Successful in 1m44s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

<!-- ℹ お読みください / README
PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->

## What
<!-- このPRで何をしたのか? どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->

## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->

## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->

## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Add story of storybook
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

Reviewed-on: #24
Co-authored-by: eternal-flame-AD <yume@yumechi.jp>
Co-committed-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-18 03:29:53 -06:00
b805239b39 Merge pull request 'reduce buckets for incoming ap' (#23) from develop into master
All checks were successful
Publish Docker image / Build (push) Successful in 4m33s
Lint / pnpm_install (push) Successful in 1m28s
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Test (backend) / unit (22.11.0) (push) Successful in 8m37s
Test (backend) / e2e (22.11.0) (push) Successful in 11m47s
Lint / lint (backend) (push) Successful in 2m43s
Lint / lint (frontend-embed) (push) Successful in 2m33s
Lint / lint (frontend) (push) Successful in 3m1s
Lint / lint (frontend-shared) (push) Successful in 2m28s
Lint / lint (misskey-bubble-game) (push) Successful in 2m45s
Lint / lint (misskey-js) (push) Successful in 2m45s
Lint / lint (misskey-reversi) (push) Successful in 2m43s
Lint / typecheck (misskey-js) (push) Successful in 1m32s
Lint / typecheck (backend) (push) Successful in 2m42s
Lint / lint (sw) (push) Successful in 3m9s
Lint / typecheck (sw) (push) Successful in 1m35s
Reviewed-on: #23
2024-11-17 21:54:18 -06:00
f9167b8b86
reduce buckets for incoming ap
All checks were successful
Test (production install and build) / production (22.11.0) (push) Successful in 1m3s
Test (backend) / unit (22.11.0) (push) Successful in 8m38s
Publish Docker image / Build (pull_request) Successful in 4m25s
Lint / pnpm_install (pull_request) Successful in 1m36s
Test (backend) / e2e (22.11.0) (push) Successful in 11m20s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m2s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m0s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 11m39s
Lint / lint (backend) (push) Successful in 2m19s
Lint / lint (frontend) (push) Successful in 2m25s
Lint / lint (frontend-shared) (push) Successful in 2m35s
Lint / lint (frontend-embed) (push) Successful in 2m38s
Lint / lint (misskey-bubble-game) (push) Successful in 2m27s
Lint / lint (misskey-js) (push) Successful in 2m43s
Lint / lint (misskey-reversi) (push) Successful in 2m27s
Lint / typecheck (misskey-js) (push) Successful in 1m34s
Lint / lint (sw) (push) Successful in 2m37s
Lint / typecheck (backend) (push) Successful in 2m32s
Lint / typecheck (sw) (push) Successful in 1m48s
Lint / lint (backend) (pull_request) Successful in 3m7s
Lint / lint (frontend) (pull_request) Successful in 2m54s
Lint / lint (frontend-embed) (pull_request) Successful in 2m40s
Lint / lint (frontend-shared) (pull_request) Successful in 2m32s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m43s
Lint / lint (misskey-js) (pull_request) Successful in 2m25s
Lint / lint (sw) (pull_request) Successful in 2m49s
Lint / lint (misskey-reversi) (pull_request) Successful in 3m2s
Lint / typecheck (backend) (pull_request) Successful in 2m17s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m39s
Lint / typecheck (sw) (pull_request) Successful in 1m52s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-17 21:53:54 -06:00
d7080be847 Merge pull request 'prometheus - stage 2 workers' (#22) from develop into master
Some checks failed
Publish Docker image / Build (push) Successful in 4m35s
Lint / pnpm_install (push) Successful in 1m38s
Test (production install and build) / production (22.11.0) (push) Successful in 1m1s
Test (backend) / unit (22.11.0) (push) Successful in 7m45s
Test (backend) / e2e (22.11.0) (push) Successful in 11m38s
Lint / lint (backend) (push) Successful in 2m32s
Lint / lint (frontend-embed) (push) Successful in 2m38s
Lint / lint (frontend) (push) Successful in 2m52s
Lint / lint (misskey-bubble-game) (push) Failing after 1m42s
Lint / lint (frontend-shared) (push) Successful in 2m49s
Lint / lint (misskey-js) (push) Successful in 2m27s
Lint / lint (misskey-reversi) (push) Successful in 2m23s
Lint / lint (sw) (push) Successful in 2m23s
Lint / typecheck (backend) (push) Successful in 2m9s
Lint / typecheck (misskey-js) (push) Successful in 1m32s
Lint / typecheck (sw) (push) Successful in 1m37s
Reviewed-on: #22
2024-11-17 19:07:22 -06:00
935efb5bf2
prometheus - stage 2 workers
Some checks failed
Test (production install and build) / production (22.11.0) (push) Successful in 1m3s
Lint / lint (backend) (push) Successful in 2m21s
Test (backend) / unit (22.11.0) (push) Successful in 8m52s
Lint / lint (frontend) (push) Successful in 2m30s
Lint / lint (frontend-embed) (push) Successful in 2m27s
Lint / lint (misskey-bubble-game) (push) Successful in 2m33s
Lint / lint (frontend-shared) (push) Successful in 2m46s
Test (backend) / e2e (22.11.0) (push) Successful in 11m57s
Lint / lint (misskey-js) (push) Successful in 2m38s
Lint / lint (sw) (push) Successful in 2m47s
Lint / lint (misskey-reversi) (push) Successful in 2m59s
Lint / typecheck (misskey-js) (push) Successful in 1m42s
Lint / typecheck (sw) (push) Successful in 1m54s
Lint / typecheck (backend) (push) Successful in 2m45s
Lint / pnpm_install (pull_request) Successful in 2m11s
Publish Docker image / Build (pull_request) Successful in 4m43s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m2s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m45s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 11m13s
Lint / lint (backend) (pull_request) Successful in 2m6s
Lint / lint (frontend) (pull_request) Successful in 2m19s
Lint / lint (frontend-embed) (pull_request) Successful in 2m14s
Lint / lint (frontend-shared) (pull_request) Successful in 2m27s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m36s
Lint / lint (misskey-js) (pull_request) Successful in 2m31s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m50s
Lint / lint (sw) (pull_request) Successful in 2m39s
Lint / typecheck (backend) (pull_request) Successful in 2m42s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m41s
Lint / typecheck (sw) (pull_request) Successful in 1m52s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-17 18:47:56 -06:00
c22160aa90 Merge pull request 'more path sanitization' (#21) from develop into master
Some checks failed
Publish Docker image / Build (push) Successful in 4m42s
Lint / pnpm_install (push) Successful in 1m43s
Test (production install and build) / production (22.11.0) (push) Successful in 1m8s
Test (backend) / unit (22.11.0) (push) Successful in 8m22s
Test (backend) / e2e (22.11.0) (push) Failing after 12m4s
Lint / lint (backend) (push) Successful in 2m37s
Lint / lint (frontend) (push) Successful in 2m34s
Lint / lint (frontend-embed) (push) Successful in 2m33s
Lint / lint (frontend-shared) (push) Successful in 2m40s
Lint / lint (misskey-bubble-game) (push) Successful in 2m29s
Lint / lint (misskey-js) (push) Successful in 2m27s
Lint / lint (misskey-reversi) (push) Successful in 2m41s
Lint / lint (sw) (push) Successful in 2m40s
Lint / typecheck (backend) (push) Successful in 2m23s
Lint / typecheck (misskey-js) (push) Successful in 1m35s
Lint / typecheck (sw) (push) Successful in 1m41s
Reviewed-on: #21
2024-11-17 12:58:51 -06:00
3906efa8d5
more path sanitization
All checks were successful
Test (production install and build) / production (22.11.0) (push) Successful in 1m6s
Test (backend) / unit (22.11.0) (push) Successful in 8m54s
Publish Docker image / Build (pull_request) Successful in 4m37s
Lint / pnpm_install (pull_request) Successful in 1m45s
Test (backend) / e2e (22.11.0) (push) Successful in 11m52s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m5s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m25s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 12m16s
Lint / lint (backend) (push) Successful in 2m19s
Lint / lint (frontend) (push) Successful in 2m26s
Lint / lint (frontend-embed) (push) Successful in 2m27s
Lint / lint (frontend-shared) (push) Successful in 2m19s
Lint / lint (misskey-bubble-game) (push) Successful in 2m28s
Lint / lint (misskey-js) (push) Successful in 2m34s
Lint / lint (misskey-reversi) (push) Successful in 2m32s
Lint / lint (sw) (push) Successful in 2m31s
Lint / typecheck (backend) (push) Successful in 2m29s
Lint / typecheck (misskey-js) (push) Successful in 1m39s
Lint / typecheck (sw) (push) Successful in 1m42s
Lint / lint (backend) (pull_request) Successful in 2m33s
Lint / lint (frontend) (pull_request) Successful in 2m27s
Lint / lint (frontend-embed) (pull_request) Successful in 2m28s
Lint / lint (frontend-shared) (pull_request) Successful in 2m27s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m28s
Lint / lint (misskey-js) (pull_request) Successful in 2m30s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m30s
Lint / lint (sw) (pull_request) Successful in 2m31s
Lint / typecheck (backend) (pull_request) Successful in 2m28s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m39s
Lint / typecheck (sw) (pull_request) Successful in 1m44s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-17 12:57:49 -06:00
577a7e5e96 Merge pull request 'prometheus' (#20) from develop into master
Some checks failed
Lint / pnpm_install (push) Successful in 2m5s
Publish Docker image / Build (push) Successful in 4m52s
Test (production install and build) / production (22.11.0) (push) Successful in 1m13s
Lint / lint (backend) (push) Successful in 2m19s
Test (backend) / unit (22.11.0) (push) Successful in 9m25s
Lint / lint (frontend) (push) Successful in 2m27s
Lint / lint (frontend-embed) (push) Successful in 2m25s
Lint / lint (frontend-shared) (push) Successful in 2m23s
Lint / lint (misskey-bubble-game) (push) Successful in 2m36s
Test (backend) / e2e (22.11.0) (push) Failing after 12m25s
Lint / lint (misskey-js) (push) Successful in 2m35s
Lint / lint (misskey-reversi) (push) Successful in 2m37s
Lint / lint (sw) (push) Successful in 2m48s
Lint / typecheck (backend) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
Lint / typecheck (misskey-js) (push) Has been cancelled
Reviewed-on: #20
2024-11-17 12:40:23 -06:00
109d8f8008
don't log metrics for postgres in testing
All checks were successful
Test (production install and build) / production (22.11.0) (push) Successful in 1m53s
Lint / pnpm_install (pull_request) Successful in 1m36s
Publish Docker image / Build (push) Successful in 5m12s
Publish Docker image / Build (pull_request) Successful in 4m28s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m13s
Lint / lint (backend) (pull_request) Successful in 2m47s
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m38s
Lint / lint (frontend-embed) (pull_request) Successful in 2m25s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 11m35s
Lint / lint (frontend-shared) (pull_request) Successful in 2m28s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m37s
Lint / lint (frontend) (pull_request) Successful in 9m50s
Lint / lint (misskey-js) (pull_request) Successful in 2m27s
Lint / lint (sw) (pull_request) Successful in 2m45s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m58s
Lint / typecheck (backend) (pull_request) Successful in 2m42s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m49s
Lint / typecheck (sw) (pull_request) Successful in 1m59s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-17 11:58:15 -06:00
4d44adfaa9
prometheus - stage 1 deployment
Some checks failed
Test (production install and build) / production (22.11.0) (push) Successful in 1m25s
Lint / lint (backend) (push) Successful in 2m7s
Lint / lint (frontend) (push) Successful in 2m11s
Lint / lint (frontend-embed) (push) Successful in 2m9s
Lint / lint (frontend-shared) (push) Successful in 2m19s
Lint / lint (misskey-bubble-game) (push) Successful in 2m21s
Lint / lint (misskey-reversi) (push) Successful in 2m33s
Lint / lint (misskey-js) (push) Successful in 2m44s
Lint / typecheck (misskey-js) (push) Successful in 1m23s
Lint / lint (sw) (push) Successful in 2m21s
Lint / typecheck (backend) (push) Successful in 2m3s
Lint / typecheck (sw) (push) Successful in 1m23s
Lint / pnpm_install (pull_request) Successful in 1m35s
Publish Docker image / Build (pull_request) Successful in 4m59s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m32s
Test (backend) / e2e (22.11.0) (push) Failing after 1m56s
Lint / lint (backend) (pull_request) Successful in 2m0s
Lint / lint (frontend) (pull_request) Successful in 2m16s
Lint / lint (frontend-embed) (pull_request) Successful in 2m3s
Lint / lint (frontend-shared) (pull_request) Successful in 2m9s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m7s
Test (backend) / unit (22.11.0) (push) Failing after 10m2s
Lint / lint (misskey-js) (pull_request) Successful in 2m13s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m16s
Lint / lint (sw) (pull_request) Successful in 2m26s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m29s
Lint / typecheck (backend) (pull_request) Successful in 2m16s
Lint / typecheck (sw) (pull_request) Successful in 1m40s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 2m5s
Test (backend) / unit (22.11.0) (pull_request) Failing after 7m45s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-17 04:44:39 -06:00
13e50cd8d9
fix(backend): アナウンスメントを作成ときに画像URLを後悔できないのを修正
All checks were successful
Lint / pnpm_install (push) Successful in 2m0s
Publish Docker image / Build (push) Successful in 4m48s
Test (production install and build) / production (22.11.0) (push) Successful in 1m3s
Lint / lint (backend) (push) Successful in 2m9s
Test (backend) / unit (22.11.0) (push) Successful in 8m6s
Lint / lint (frontend) (push) Successful in 2m19s
Lint / lint (frontend-embed) (push) Successful in 2m12s
Lint / lint (frontend-shared) (push) Successful in 2m15s
Lint / lint (misskey-bubble-game) (push) Successful in 2m14s
Test (backend) / e2e (22.11.0) (push) Successful in 11m38s
Lint / lint (misskey-js) (push) Successful in 2m30s
Lint / lint (misskey-reversi) (push) Successful in 2m20s
Lint / lint (sw) (push) Successful in 2m30s
Lint / typecheck (misskey-js) (push) Successful in 1m37s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (sw) (push) Successful in 1m33s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-16 17:37:26 -06:00
a95e3716ff
2024.11.0-yumechinokuni.4p1
Some checks failed
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m24s
Lint / lint (backend) (pull_request) Successful in 2m25s
Lint / lint (frontend) (pull_request) Successful in 2m27s
Test (backend) / unit (22.11.0) (pull_request) Successful in 9m22s
Lint / lint (frontend-embed) (pull_request) Successful in 2m26s
Lint / lint (frontend-shared) (pull_request) Successful in 2m22s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m30s
Lint / lint (misskey-js) (pull_request) Successful in 2m32s
Test (backend) / e2e (22.11.0) (pull_request) Failing after 13m39s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m36s
Lint / lint (sw) (pull_request) Successful in 2m43s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m50s
Lint / typecheck (backend) (pull_request) Successful in 2m42s
Lint / typecheck (sw) (pull_request) Successful in 1m52s
Lint / pnpm_install (push) Successful in 2m18s
Publish Docker image / Build (push) Successful in 5m5s
Test (production install and build) / production (22.11.0) (push) Successful in 1m10s
Lint / lint (backend) (push) Successful in 2m25s
Test (backend) / unit (22.11.0) (push) Successful in 10m20s
Lint / lint (frontend) (push) Successful in 2m36s
Lint / lint (frontend-embed) (push) Successful in 2m40s
Lint / lint (frontend-shared) (push) Successful in 2m39s
Lint / lint (misskey-bubble-game) (push) Successful in 2m38s
Test (backend) / e2e (22.11.0) (push) Successful in 13m53s
Lint / lint (misskey-js) (push) Successful in 2m57s
Lint / lint (misskey-reversi) (push) Successful in 2m40s
Lint / lint (sw) (push) Successful in 2m42s
Lint / typecheck (backend) (push) Successful in 2m38s
Lint / typecheck (misskey-js) (push) Successful in 1m42s
Lint / typecheck (sw) (push) Successful in 1m41s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-16 06:26:06 -06:00
1862b33aba
pgroonga CW search
All checks were successful
Lint / pnpm_install (push) Successful in 2m23s
Publish Docker image / Build (push) Successful in 5m4s
Lint / lint (backend) (push) Successful in 2m29s
Test (backend) / unit (22.11.0) (push) Successful in 9m49s
Lint / lint (frontend) (push) Successful in 2m35s
Lint / lint (frontend-embed) (push) Successful in 2m24s
Lint / lint (frontend-shared) (push) Successful in 2m40s
Lint / lint (misskey-bubble-game) (push) Successful in 2m29s
Lint / lint (misskey-js) (push) Successful in 2m41s
Test (backend) / e2e (22.11.0) (push) Successful in 13m29s
Lint / lint (misskey-reversi) (push) Successful in 2m51s
Lint / typecheck (backend) (push) Successful in 3m4s
Lint / lint (sw) (push) Successful in 3m10s
Lint / typecheck (misskey-js) (push) Successful in 1m46s
Test (production install and build) / production (22.11.0) (push) Successful in 1m24s
Lint / typecheck (sw) (push) Successful in 1m46s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-16 05:50:57 -06:00
a6b90b14b6
set docker db shm_size
Some checks failed
Test (backend) / unit (22.11.0) (pull_request) Successful in 8m19s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 11m15s
Lint / pnpm_install (pull_request) Successful in 1m14s
Lint / lint (backend) (pull_request) Successful in 2m24s
Lint / lint (frontend) (pull_request) Successful in 2m34s
Lint / lint (frontend-embed) (pull_request) Successful in 2m44s
Lint / lint (frontend-shared) (pull_request) Successful in 3m13s
Lint / lint (misskey-js) (pull_request) Successful in 2m49s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 3m1s
Lint / typecheck (backend) (pull_request) Successful in 2m15s
Lint / lint (sw) (pull_request) Successful in 2m47s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m50s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m29s
Lint / typecheck (sw) (pull_request) Successful in 1m25s
Lint / pnpm_install (push) Successful in 1m57s
Publish Docker image / Build (push) Successful in 5m26s
Test (production install and build) / production (22.11.0) (push) Successful in 1m5s
Lint / lint (backend) (push) Successful in 2m16s
Test (backend) / unit (22.11.0) (push) Successful in 9m11s
Lint / lint (frontend) (push) Successful in 2m29s
Lint / lint (frontend-embed) (push) Successful in 2m27s
Lint / lint (frontend-shared) (push) Successful in 2m28s
Lint / lint (misskey-bubble-game) (push) Successful in 2m27s
Test (backend) / e2e (22.11.0) (push) Failing after 12m38s
Lint / lint (misskey-js) (push) Successful in 2m40s
Lint / lint (sw) (push) Successful in 2m45s
Lint / lint (misskey-reversi) (push) Successful in 2m59s
Lint / typecheck (backend) (push) Successful in 2m46s
Lint / typecheck (misskey-js) (push) Successful in 1m46s
Lint / typecheck (sw) (push) Successful in 1m57s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 17:16:33 -06:00
c830e9ba0d
Revert "use execa 8.0.1"
Some checks failed
Test (production install and build) / production (22.11.0) (push) Successful in 1m59s
Lint / pnpm_install (push) Successful in 2m22s
Publish Docker image / Build (push) Successful in 5m29s
Lint / lint (backend) (push) Successful in 3m45s
Lint / lint (frontend-embed) (push) Successful in 3m4s
Lint / lint (misskey-bubble-game) (push) Failing after -4s
Lint / lint (misskey-js) (push) Failing after -4s
Lint / lint (misskey-reversi) (push) Failing after -3s
Lint / lint (sw) (push) Failing after 12m56s
Lint / lint (frontend-shared) (push) Failing after 15m37s
Lint / lint (frontend) (push) Failing after 19m30s
Lint / typecheck (misskey-js) (push) Successful in 2m14s
Lint / typecheck (sw) (push) Successful in 2m27s
Lint / typecheck (backend) (push) Successful in 2m53s
This reverts commit eef0c895bc. Cannot
reproduce original issue in question
2024-11-15 15:23:12 -06:00
d3674705a5 Merge remote-tracking branch 'upstream/develop' into develop 2024-11-15 15:22:22 -06:00
18c5d5cd02
add missing devDependencies
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 15:17:34 -06:00
237ac7cffc
cleanup metadata
Some checks failed
Test (production install and build) / production (22.11.0) (push) Successful in 1m40s
Lint / pnpm_install (push) Successful in 2m3s
Lint / lint (backend) (push) Successful in 2m27s
Publish Docker image / Build (push) Successful in 5m6s
Lint / lint (frontend) (push) Successful in 3m1s
Lint / lint (frontend-shared) (push) Successful in 2m46s
Lint / lint (misskey-bubble-game) (push) Successful in 2m58s
Lint / lint (frontend-embed) (push) Successful in 3m33s
Lint / lint (misskey-js) (push) Successful in 2m33s
Lint / lint (sw) (push) Successful in 2m36s
Lint / lint (misskey-reversi) (push) Successful in 2m49s
Lint / typecheck (sw) (push) Successful in 1m53s
Lint / typecheck (misskey-js) (push) Successful in 1m48s
Lint / typecheck (backend) (push) Failing after 18m3s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 14:37:13 -06:00
0ac3544a5c
Frontend: Stream再接続ロジックdata raceを修正
Some checks failed
Lint / pnpm_install (push) Successful in 1m38s
Test (production install and build) / production (22.11.0) (push) Successful in 1m46s
Lint / lint (backend) (push) Successful in 2m13s
Lint / lint (frontend) (push) Successful in 2m19s
Publish Docker image / Build (push) Successful in 5m14s
Lint / lint (frontend-embed) (push) Successful in 2m23s
Lint / lint (frontend-shared) (push) Successful in 2m42s
Lint / lint (misskey-bubble-game) (push) Successful in 2m30s
Lint / lint (sw) (push) Failing after 1m21s
Lint / lint (misskey-js) (push) Successful in 2m23s
Lint / typecheck (backend) (push) Failing after 54s
Lint / typecheck (sw) (push) Successful in 2m5s
Lint / typecheck (misskey-js) (push) Failing after 12m52s
Lint / lint (misskey-reversi) (push) Failing after 14m42s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 05:38:23 -06:00
18d5587e5c
Merge tag '2024.11.0-alpha.1' into develop
Some checks failed
Lint / pnpm_install (pull_request) Successful in 1m42s
Publish Docker image / Build (pull_request) Successful in 5m0s
Test (production install and build) / production (22.11.0) (pull_request) Successful in 1m10s
Lint / lint (backend) (pull_request) Successful in 2m21s
Lint / lint (frontend) (pull_request) Successful in 10m7s
Lint / lint (misskey-js) (pull_request) Successful in 2m30s
Lint / lint (sw) (pull_request) Successful in 3m55s
Lint / lint (misskey-reversi) (pull_request) Successful in 3m57s
Lint / typecheck (backend) (pull_request) Successful in 2m20s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m38s
Lint / typecheck (sw) (pull_request) Successful in 1m49s
Lint / lint (frontend-embed) (pull_request) Failing after 15m54s
Test (backend) / unit (22.11.0) (pull_request) Successful in 7m1s
Test (backend) / e2e (22.11.0) (pull_request) Successful in 10m29s
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Lint / pnpm_install (push) Successful in 2m10s
Publish Docker image / Build (push) Successful in 4m50s
Test (production install and build) / production (22.11.0) (push) Successful in 1m17s
Lint / lint (backend) (push) Successful in 2m10s
Test (backend) / unit (22.11.0) (push) Successful in 8m55s
Lint / lint (frontend) (push) Successful in 2m11s
Lint / lint (frontend-embed) (push) Successful in 2m12s
Lint / lint (frontend-shared) (push) Successful in 2m7s
Lint / lint (misskey-bubble-game) (push) Successful in 2m5s
Test (backend) / e2e (22.11.0) (push) Failing after 11m29s
Lint / lint (misskey-js) (push) Successful in 2m20s
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-15 04:46:18 -06:00
60eb7e1dc9
admin registration script
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m28s
Publish Docker image / Build (push) Successful in 4m51s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-14 12:36:38 -06:00
738877016c Merge pull request 'Relax admin automated account registration' (#16) from relax-account-registration into develop
All checks were successful
Lint / pnpm_install (push) Successful in 1m45s
Publish Docker image / Build (push) Successful in 4m33s
Test (production install and build) / production (20.16.0) (push) Successful in 1m17s
Test (backend) / unit (20.16.0) (push) Successful in 7m58s
Lint / lint (backend) (push) Successful in 2m27s
Lint / lint (frontend) (push) Successful in 2m56s
Lint / lint (frontend-embed) (push) Successful in 2m18s
Test (backend) / e2e (20.16.0) (push) Successful in 11m35s
Lint / lint (frontend-shared) (push) Successful in 2m26s
Lint / lint (misskey-bubble-game) (push) Successful in 2m30s
Lint / lint (misskey-js) (push) Successful in 2m40s
Lint / lint (misskey-reversi) (push) Successful in 2m37s
Lint / lint (sw) (push) Successful in 2m33s
Lint / typecheck (misskey-js) (push) Successful in 1m33s
Lint / typecheck (backend) (push) Successful in 2m23s
Lint / typecheck (sw) (push) Successful in 2m23s
Reviewed-on: #16
2024-11-14 11:39:59 -06:00
0f90b37b62
Add role write:admin:create-account
Some checks failed
Publish Docker image / Build (pull_request) Successful in 4m59s
Test (backend) / unit (20.16.0) (pull_request) Successful in 6m47s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m13s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 10m40s
Lint / lint (backend) (pull_request) Has been cancelled
Lint / lint (frontend) (pull_request) Has been cancelled
Lint / pnpm_install (pull_request) Has been cancelled
Lint / lint (frontend-embed) (pull_request) Has been cancelled
Lint / lint (frontend-shared) (pull_request) Has been cancelled
Lint / lint (misskey-bubble-game) (pull_request) Has been cancelled
Lint / lint (misskey-js) (pull_request) Has been cancelled
Lint / lint (misskey-reversi) (pull_request) Has been cancelled
Lint / lint (sw) (pull_request) Has been cancelled
Lint / typecheck (backend) (pull_request) Has been cancelled
Lint / typecheck (misskey-js) (pull_request) Has been cancelled
Lint / typecheck (sw) (pull_request) Has been cancelled
2024-11-14 11:21:27 -06:00
52d0100918 Merge branch 'develop' into relax-account-registration 2024-11-14 09:22:24 -06:00
fd271ea268
Relax admin automated account registration
Some checks failed
Lint / pnpm_install (pull_request) Successful in 1m50s
Publish Docker image / Build (pull_request) Successful in 5m30s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m20s
Test (backend) / unit (20.16.0) (pull_request) Successful in 8m21s
Lint / lint (backend) (pull_request) Successful in 2m30s
Lint / lint (frontend) (pull_request) Successful in 2m15s
Lint / lint (frontend-embed) (pull_request) Successful in 2m26s
Lint / lint (frontend-shared) (pull_request) Successful in 2m24s
Test (backend) / e2e (20.16.0) (pull_request) Failing after 12m16s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m31s
Lint / typecheck (backend) (pull_request) Has been cancelled
Lint / lint (misskey-js) (pull_request) Has been cancelled
Lint / typecheck (misskey-js) (pull_request) Has been cancelled
Lint / lint (misskey-reversi) (pull_request) Has been cancelled
Lint / typecheck (sw) (pull_request) Has been cancelled
Lint / lint (sw) (pull_request) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-14 09:18:02 -06:00
afac979977
Web security configuration
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-14 08:56:11 -06:00
073c70d42b Merge pull request 'Web security configuration' (#15) from configurable-web-security into develop
All checks were successful
Lint / pnpm_install (push) Successful in 2m5s
Publish Docker image / Build (push) Successful in 5m48s
Test (production install and build) / production (20.16.0) (push) Successful in 1m22s
Test (backend) / unit (20.16.0) (push) Successful in 8m44s
Lint / lint (backend) (push) Successful in 2m31s
Lint / lint (frontend-embed) (push) Successful in 2m28s
Lint / lint (frontend) (push) Successful in 2m57s
Test (backend) / e2e (20.16.0) (push) Successful in 12m23s
Lint / lint (frontend-shared) (push) Successful in 2m34s
Lint / lint (misskey-bubble-game) (push) Successful in 2m37s
Lint / lint (misskey-js) (push) Successful in 2m26s
Lint / lint (sw) (push) Successful in 2m34s
Lint / lint (misskey-reversi) (push) Successful in 2m51s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (misskey-js) (push) Successful in 1m51s
Lint / typecheck (sw) (push) Successful in 2m1s
Reviewed-on: #15
2024-11-14 08:47:00 -06:00
174c3ef096
Web security configuration
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m59s
Publish Docker image / Build (pull_request) Successful in 4m52s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m13s
Test (backend) / unit (20.16.0) (pull_request) Successful in 8m32s
Lint / lint (backend) (pull_request) Successful in 2m25s
Lint / lint (frontend) (pull_request) Successful in 2m22s
Lint / lint (frontend-embed) (pull_request) Successful in 2m9s
Lint / lint (frontend-shared) (pull_request) Successful in 2m19s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m53s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m32s
Lint / lint (misskey-js) (pull_request) Successful in 2m34s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m37s
Lint / lint (sw) (pull_request) Successful in 2m15s
Lint / typecheck (backend) (pull_request) Successful in 2m8s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m53s
Lint / typecheck (sw) (pull_request) Successful in 1m53s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-12 02:20:42 -06:00
7748ab5dd0
allow self-embed (previews, etc.)
Some checks failed
Lint / pnpm_install (push) Successful in 1m48s
Publish Docker image / Build (push) Successful in 4m52s
Test (production install and build) / production (20.16.0) (push) Failing after 1m46s
Test (backend) / e2e (20.16.0) (push) Failing after 4m56s
Lint / lint (backend) (push) Failing after 23s
Test (backend) / unit (20.16.0) (push) Successful in 6m48s
Lint / lint (frontend) (push) Failing after 17s
Lint / lint (frontend-embed) (push) Failing after 14s
Lint / lint (frontend-shared) (push) Failing after 22s
Lint / lint (misskey-bubble-game) (push) Failing after 24s
Lint / lint (misskey-js) (push) Failing after 25s
Lint / lint (misskey-reversi) (push) Failing after 25s
Lint / lint (sw) (push) Failing after 24s
Lint / typecheck (backend) (push) Failing after 24s
Lint / typecheck (misskey-js) (push) Failing after 25s
Lint / typecheck (sw) (push) Failing after 24s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-12 00:15:29 -06:00
2419a9f740
fixup! implement CSP, remove commercial supporters from about section
Some checks failed
Publish Docker image / Build (push) Successful in 4m39s
Lint / pnpm_install (push) Successful in 1m36s
Test (production install and build) / production (20.16.0) (push) Successful in 1m12s
Test (backend) / unit (20.16.0) (push) Successful in 6m53s
Test (backend) / e2e (20.16.0) (push) Failing after 12m11s
Lint / lint (backend) (push) Successful in 2m34s
Lint / lint (frontend) (push) Successful in 2m30s
Lint / lint (frontend-embed) (push) Successful in 2m30s
Lint / lint (frontend-shared) (push) Successful in 2m34s
Lint / lint (misskey-bubble-game) (push) Successful in 2m30s
Lint / lint (misskey-js) (push) Successful in 2m25s
Lint / lint (misskey-reversi) (push) Successful in 2m28s
Lint / lint (sw) (push) Successful in 2m31s
Lint / typecheck (backend) (push) Successful in 2m8s
Lint / typecheck (misskey-js) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-11 21:32:27 -06:00
3fcea3eeb6
bump version 2024.11.0-yumechinokuni.3
Some checks failed
Test (production install and build) / production (20.16.0) (push) Successful in 1m18s
Test (backend) / unit (20.16.0) (push) Successful in 8m36s
Lint / pnpm_install (pull_request) Successful in 1m13s
Publish Docker image / Build (pull_request) Successful in 4m40s
Test (backend) / e2e (20.16.0) (push) Failing after 11m2s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m11s
Lint / lint (backend) (push) Successful in 2m19s
Lint / lint (frontend-embed) (push) Successful in 2m27s
Lint / lint (frontend) (push) Successful in 2m32s
Lint / lint (frontend-shared) (push) Successful in 2m32s
Lint / lint (misskey-bubble-game) (push) Successful in 2m31s
Lint / lint (misskey-js) (push) Successful in 2m15s
Lint / lint (misskey-reversi) (push) Successful in 2m56s
Lint / lint (sw) (push) Successful in 2m53s
Lint / typecheck (backend) (push) Successful in 2m20s
Lint / typecheck (misskey-js) (push) Successful in 1m35s
Lint / typecheck (sw) (push) Successful in 1m49s
Lint / lint (backend) (pull_request) Successful in 2m22s
Lint / lint (frontend) (pull_request) Successful in 2m18s
Lint / lint (frontend-embed) (pull_request) Successful in 2m34s
Lint / lint (frontend-shared) (pull_request) Successful in 2m15s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m16s
Lint / lint (misskey-js) (pull_request) Successful in 2m29s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m16s
Lint / lint (sw) (pull_request) Successful in 2m12s
Lint / typecheck (backend) (pull_request) Successful in 2m7s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m29s
Lint / typecheck (sw) (pull_request) Successful in 1m31s
Test (backend) / unit (20.16.0) (pull_request) Successful in 6m15s
Test (backend) / e2e (20.16.0) (pull_request) Failing after 9m40s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-11 17:10:37 -06:00
67197fbb76
implement CSP, remove commercial supporters from about section
Too much diff don't want to deal with it

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-11 17:09:11 -06:00
e75d885a2c
enable sourceMap on frontend
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m47s
Lint / pnpm_install (push) Successful in 2m5s
Publish Docker image / Build (push) Successful in 4m44s
Lint / lint (frontend) (push) Successful in 2m44s
Lint / lint (backend) (push) Successful in 2m58s
Lint / lint (frontend-embed) (push) Successful in 2m50s
Lint / lint (frontend-shared) (push) Successful in 2m54s
Lint / lint (misskey-bubble-game) (push) Successful in 2m55s
Lint / lint (misskey-reversi) (push) Successful in 2m53s
Lint / lint (misskey-js) (push) Successful in 3m25s
Lint / lint (sw) (push) Successful in 3m7s
Lint / typecheck (misskey-js) (push) Successful in 2m9s
Lint / typecheck (sw) (push) Successful in 2m12s
Lint / typecheck (backend) (push) Successful in 3m19s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-11 02:29:47 -06:00
77b333dbdb Merge pull request 'more robust recursive folder check' (#12) from folder-recursive into develop
All checks were successful
Lint / pnpm_install (push) Successful in 1m57s
Publish Docker image / Build (push) Successful in 4m50s
Test (production install and build) / production (20.16.0) (push) Successful in 1m10s
Test (backend) / unit (20.16.0) (push) Successful in 7m26s
Lint / lint (backend) (push) Successful in 2m9s
Lint / lint (frontend) (push) Successful in 2m12s
Lint / lint (frontend-embed) (push) Successful in 2m14s
Lint / lint (frontend-shared) (push) Successful in 2m12s
Test (backend) / e2e (20.16.0) (push) Successful in 10m38s
Lint / lint (misskey-bubble-game) (push) Successful in 2m27s
Lint / lint (misskey-js) (push) Successful in 2m22s
Lint / lint (misskey-reversi) (push) Successful in 2m13s
Lint / lint (sw) (push) Successful in 2m31s
Lint / typecheck (misskey-js) (push) Successful in 1m26s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (sw) (push) Successful in 1m28s
Reviewed-on: #12
2024-11-10 18:23:10 -06:00
82f4277673
more robust recursive folder check
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m51s
Publish Docker image / Build (pull_request) Successful in 4m34s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m4s
Lint / lint (backend) (pull_request) Successful in 2m11s
Test (backend) / unit (20.16.0) (pull_request) Successful in 6m58s
Lint / lint (frontend) (pull_request) Successful in 2m20s
Lint / lint (frontend-embed) (pull_request) Successful in 2m23s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 10m29s
Lint / lint (frontend-shared) (pull_request) Successful in 2m23s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m22s
Lint / lint (misskey-js) (pull_request) Successful in 2m13s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m29s
Lint / lint (sw) (pull_request) Successful in 2m28s
Lint / typecheck (backend) (pull_request) Successful in 2m17s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m46s
Lint / typecheck (sw) (pull_request) Successful in 1m48s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 17:32:05 -06:00
7a106a390d
add localComments count
Some checks failed
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m9s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m47s
Lint / pnpm_install (push) Successful in 1m15s
Publish Docker image / Build (push) Successful in 4m45s
Test (backend) / e2e (20.16.0) (pull_request) Failing after 10m58s
Test (production install and build) / production (20.16.0) (push) Successful in 1m12s
Lint / lint (backend) (pull_request) Successful in 2m12s
Test (backend) / unit (20.16.0) (push) Successful in 7m24s
Lint / lint (frontend) (pull_request) Successful in 2m17s
Lint / lint (frontend-embed) (pull_request) Successful in 2m21s
Lint / lint (frontend-shared) (pull_request) Successful in 2m26s
Test (backend) / e2e (20.16.0) (push) Successful in 10m51s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m43s
Lint / lint (misskey-js) (pull_request) Successful in 2m24s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m28s
Lint / lint (sw) (pull_request) Successful in 2m29s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m29s
Lint / typecheck (backend) (pull_request) Successful in 2m19s
Lint / typecheck (sw) (pull_request) Successful in 1m34s
Lint / lint (backend) (push) Successful in 2m41s
Lint / lint (frontend) (push) Successful in 2m37s
Lint / lint (frontend-embed) (push) Successful in 2m24s
Lint / lint (frontend-shared) (push) Successful in 2m29s
Lint / lint (misskey-js) (push) Successful in 2m26s
Lint / lint (misskey-bubble-game) (push) Successful in 2m54s
Lint / lint (misskey-reversi) (push) Successful in 2m17s
Lint / typecheck (backend) (push) Successful in 2m13s
Lint / lint (sw) (push) Successful in 2m41s
Lint / typecheck (misskey-js) (push) Successful in 1m28s
Lint / typecheck (sw) (push) Successful in 1m32s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 14:41:40 -06:00
24b6849ad1 Merge pull request 'restore activeHalfYear and activeMonth' (#10) from develop into master
All checks were successful
Lint / pnpm_install (push) Successful in 1m47s
Publish Docker image / Build (push) Successful in 4m54s
Test (production install and build) / production (20.16.0) (push) Successful in 1m11s
Test (backend) / unit (20.16.0) (push) Successful in 7m17s
Lint / lint (backend) (push) Successful in 2m23s
Lint / lint (frontend) (push) Successful in 2m22s
Lint / lint (frontend-embed) (push) Successful in 2m16s
Lint / lint (frontend-shared) (push) Successful in 2m22s
Test (backend) / e2e (20.16.0) (push) Successful in 10m49s
Lint / lint (misskey-bubble-game) (push) Successful in 2m26s
Lint / lint (misskey-js) (push) Successful in 2m19s
Lint / lint (misskey-reversi) (push) Successful in 2m21s
Lint / lint (sw) (push) Successful in 2m37s
Lint / typecheck (misskey-js) (push) Successful in 1m28s
Lint / typecheck (backend) (push) Successful in 2m13s
Lint / typecheck (sw) (push) Successful in 1m26s
Reviewed-on: #10
2024-11-10 03:34:35 -06:00
6fda31bc88 Merge branch 'master' into develop
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m11s
Publish Docker image / Build (push) Successful in 4m39s
Lint / pnpm_install (pull_request) Successful in 1m19s
Publish Docker image / Build (pull_request) Successful in 4m29s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m16s
Test (backend) / unit (20.16.0) (pull_request) Successful in 6m57s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m3s
Lint / lint (backend) (pull_request) Successful in 2m17s
Lint / lint (frontend) (pull_request) Successful in 2m23s
Lint / lint (frontend-embed) (pull_request) Successful in 2m27s
Lint / lint (frontend-shared) (pull_request) Successful in 2m19s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m33s
Lint / lint (misskey-js) (pull_request) Successful in 2m20s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m30s
Lint / lint (sw) (pull_request) Successful in 2m12s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m25s
Lint / typecheck (backend) (pull_request) Successful in 2m32s
Lint / typecheck (sw) (pull_request) Successful in 1m39s
2024-11-10 02:51:33 -06:00
18a27e3c47
restore activeHalfYear and activeMonth
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m12s
Test (backend) / unit (20.16.0) (push) Successful in 7m4s
Lint / lint (backend) (push) Successful in 2m25s
Lint / lint (frontend) (push) Successful in 2m9s
Lint / lint (frontend-embed) (push) Successful in 2m17s
Test (backend) / e2e (20.16.0) (push) Successful in 10m49s
Lint / lint (frontend-shared) (push) Successful in 2m30s
Lint / lint (misskey-bubble-game) (push) Successful in 2m20s
Lint / lint (misskey-js) (push) Successful in 2m26s
Lint / lint (misskey-reversi) (push) Successful in 2m25s
Lint / lint (sw) (push) Successful in 2m23s
Lint / typecheck (misskey-js) (push) Successful in 1m29s
Lint / typecheck (backend) (push) Successful in 2m20s
Lint / typecheck (sw) (push) Successful in 1m47s
Lint / pnpm_install (pull_request) Successful in 1m40s
Publish Docker image / Build (pull_request) Successful in 4m59s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m10s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m8s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m6s
Lint / lint (backend) (pull_request) Successful in 2m4s
Lint / lint (frontend) (pull_request) Successful in 2m15s
Lint / lint (frontend-embed) (pull_request) Successful in 2m7s
Lint / lint (frontend-shared) (pull_request) Successful in 2m13s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m8s
Lint / lint (misskey-js) (pull_request) Successful in 2m7s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m11s
Lint / lint (sw) (pull_request) Successful in 2m28s
Lint / typecheck (backend) (pull_request) Successful in 2m19s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m30s
Lint / typecheck (sw) (pull_request) Successful in 1m40s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 02:40:32 -06:00
adca3c686e Merge pull request '2024.11.0-yumechinokuni.1' (#9) from develop into master
All checks were successful
Lint / pnpm_install (push) Successful in 1m19s
Publish Docker image / Build (push) Successful in 4m34s
Test (production install and build) / production (20.16.0) (push) Successful in 1m11s
Test (backend) / unit (20.16.0) (push) Successful in 7m22s
Test (backend) / e2e (20.16.0) (push) Successful in 11m17s
Lint / lint (backend) (push) Successful in 2m15s
Lint / lint (frontend) (push) Successful in 2m35s
Lint / lint (frontend-embed) (push) Successful in 2m31s
Lint / lint (frontend-shared) (push) Successful in 2m23s
Lint / lint (misskey-bubble-game) (push) Successful in 2m20s
Lint / lint (misskey-js) (push) Successful in 2m20s
Lint / lint (misskey-reversi) (push) Successful in 2m20s
Lint / lint (sw) (push) Successful in 2m21s
Lint / typecheck (backend) (push) Successful in 2m23s
Lint / typecheck (misskey-js) (push) Successful in 1m30s
Lint / typecheck (sw) (push) Successful in 1m40s
Reviewed-on: #9
2024-11-10 02:06:12 -06:00
94ce0fd414
tag: 2024.11.0-yumechinokuni.1
All checks were successful
Publish Docker image / Build (push) Successful in 4m54s
Lint / pnpm_install (push) Successful in 1m16s
Test (production install and build) / production (20.16.0) (push) Successful in 1m18s
Lint / lint (backend) (push) Successful in 2m52s
Lint / lint (frontend-embed) (push) Successful in 2m23s
Lint / lint (frontend) (push) Successful in 2m49s
Lint / lint (frontend-shared) (push) Successful in 2m16s
Lint / lint (misskey-js) (push) Successful in 2m19s
Lint / lint (misskey-bubble-game) (push) Successful in 2m43s
Lint / lint (misskey-reversi) (push) Successful in 2m22s
Lint / lint (sw) (push) Successful in 2m21s
Lint / typecheck (backend) (push) Successful in 2m31s
Lint / typecheck (misskey-js) (push) Successful in 1m25s
Lint / typecheck (sw) (push) Successful in 1m35s
Lint / pnpm_install (pull_request) Successful in 1m53s
Publish Docker image / Build (pull_request) Successful in 4m54s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m18s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m55s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m4s
Lint / lint (backend) (pull_request) Successful in 2m9s
Lint / lint (frontend) (pull_request) Successful in 2m21s
Lint / lint (frontend-embed) (pull_request) Successful in 2m18s
Lint / lint (frontend-shared) (pull_request) Successful in 2m25s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m26s
Lint / lint (misskey-js) (pull_request) Successful in 2m27s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m32s
Lint / lint (sw) (pull_request) Successful in 2m30s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m35s
Lint / typecheck (backend) (pull_request) Successful in 2m17s
Lint / typecheck (sw) (pull_request) Successful in 1m52s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 00:55:00 -06:00
39ae9c8f8a Merge remote-tracking branch 'upstream/develop' into develop 2024-11-10 00:50:18 -06:00
48b935e88c Merge pull request 'add git info to build' (#8) from build-git-info into develop
Some checks are pending
Publish Docker image / Build (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Test (production install and build) / production (20.16.0) (push) Waiting to run
Test (backend) / unit (20.16.0) (push) Successful in 7m48s
Test (backend) / e2e (20.16.0) (push) Successful in 12m31s
Reviewed-on: #8
2024-11-10 00:49:58 -06:00
8652fd27b9
add git info to build
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m43s
Publish Docker image / Build (pull_request) Successful in 4m40s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m8s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m13s
Lint / lint (backend) (pull_request) Successful in 2m31s
Lint / lint (frontend) (pull_request) Successful in 2m45s
Lint / lint (frontend-embed) (pull_request) Successful in 2m21s
Lint / lint (frontend-shared) (pull_request) Successful in 2m19s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m14s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m24s
Lint / lint (misskey-js) (pull_request) Successful in 2m33s
Lint / lint (sw) (pull_request) Successful in 2m39s
Lint / lint (misskey-reversi) (pull_request) Successful in 3m7s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m34s
Lint / typecheck (backend) (pull_request) Successful in 2m38s
Lint / typecheck (sw) (pull_request) Successful in 2m2s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 00:41:40 -06:00
dc24536a7c Merge pull request 'more stable ws stream' (#7) from pick/pari-ws-stability into develop
All checks were successful
Lint / pnpm_install (push) Successful in 1m41s
Test (production install and build) / production (20.16.0) (push) Successful in 1m40s
Publish Docker image / Build (push) Successful in 4m27s
Lint / lint (backend) (push) Successful in 2m37s
Lint / lint (frontend) (push) Successful in 2m33s
Lint / lint (frontend-embed) (push) Successful in 2m14s
Lint / lint (frontend-shared) (push) Successful in 2m28s
Lint / lint (misskey-bubble-game) (push) Successful in 2m25s
Lint / lint (misskey-js) (push) Successful in 2m4s
Lint / lint (misskey-reversi) (push) Successful in 2m20s
Lint / lint (sw) (push) Successful in 2m25s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (misskey-js) (push) Successful in 1m49s
Lint / typecheck (sw) (push) Successful in 1m55s
Reviewed-on: #7
2024-11-10 00:21:05 -06:00
997c5b3d35 fix(frontend): eventListener leak
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m27s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m40s
Lint / lint (backend) (pull_request) Successful in 2m9s
Publish Docker image / Build (pull_request) Successful in 4m40s
Lint / lint (frontend) (pull_request) Successful in 2m33s
Lint / lint (frontend-embed) (pull_request) Successful in 2m34s
Lint / lint (frontend-shared) (pull_request) Successful in 2m22s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m29s
Lint / lint (misskey-js) (pull_request) Successful in 2m29s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m23s
Lint / lint (sw) (pull_request) Successful in 2m23s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m26s
Lint / typecheck (backend) (pull_request) Successful in 2m25s
Lint / typecheck (sw) (pull_request) Successful in 1m31s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 00:12:02 -06:00
fly_mc
e74174dea3 more stable ws stream
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 00:11:53 -06:00
ffd5c9066d
autodeploy add profiles
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m35s
Publish Docker image / Build (push) Successful in 4m46s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-10 00:10:52 -06:00
6f53315af1 Merge pull request 'try standarize version' (#6) from develop into master
Some checks failed
Lint / pnpm_install (push) Successful in 1m51s
Publish Docker image / Build (push) Successful in 4m35s
Test (production install and build) / production (20.16.0) (push) Successful in 1m17s
Test (backend) / unit (20.16.0) (push) Failing after 7m39s
Lint / lint (backend) (push) Successful in 2m17s
Lint / lint (frontend) (push) Successful in 2m11s
Lint / lint (frontend-embed) (push) Successful in 2m17s
Lint / lint (frontend-shared) (push) Successful in 2m18s
Lint / lint (misskey-bubble-game) (push) Successful in 2m15s
Test (backend) / e2e (20.16.0) (push) Successful in 11m33s
Lint / lint (misskey-js) (push) Successful in 2m23s
Lint / lint (sw) (push) Successful in 2m34s
Lint / lint (misskey-reversi) (push) Successful in 3m5s
Lint / typecheck (backend) (push) Successful in 2m23s
Lint / typecheck (misskey-js) (push) Successful in 1m44s
Lint / typecheck (sw) (push) Successful in 1m55s
Reviewed-on: #6
2024-11-09 06:08:15 -06:00
5aef6a6238 Merge branch 'master' into develop
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m45s
Lint / pnpm_install (pull_request) Successful in 1m22s
Publish Docker image / Build (push) Successful in 5m35s
Publish Docker image / Build (pull_request) Successful in 5m8s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m20s
Test (backend) / unit (20.16.0) (pull_request) Successful in 8m44s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 12m50s
Lint / lint (backend) (pull_request) Successful in 3m18s
Lint / lint (frontend) (pull_request) Successful in 2m37s
Lint / lint (frontend-embed) (pull_request) Successful in 2m45s
Lint / lint (frontend-shared) (pull_request) Successful in 2m43s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m19s
Lint / lint (misskey-js) (pull_request) Successful in 2m34s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m28s
Lint / lint (sw) (pull_request) Successful in 2m28s
Lint / typecheck (backend) (pull_request) Successful in 2m17s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m36s
Lint / typecheck (sw) (pull_request) Successful in 1m24s
2024-11-09 05:15:08 -06:00
ae46750102
try standarize version
All checks were successful
Lint / pnpm_install (pull_request) Successful in 1m38s
Publish Docker image / Build (pull_request) Successful in 4m58s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m27s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m55s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 11m5s
Test (backend) / unit (20.16.0) (push) Successful in 7m4s
Lint / pnpm_install (push) Successful in 1m11s
Lint / lint (backend) (pull_request) Successful in 2m9s
Lint / lint (frontend) (pull_request) Successful in 2m12s
Lint / lint (frontend-embed) (pull_request) Successful in 2m12s
Lint / lint (frontend-shared) (pull_request) Successful in 2m13s
Test (backend) / e2e (20.16.0) (push) Successful in 10m56s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m22s
Lint / lint (misskey-js) (pull_request) Successful in 2m38s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m31s
Lint / lint (sw) (pull_request) Successful in 2m36s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m37s
Lint / typecheck (backend) (pull_request) Successful in 2m46s
Lint / typecheck (sw) (pull_request) Successful in 1m39s
Lint / lint (backend) (push) Successful in 2m12s
Lint / lint (frontend) (push) Successful in 2m17s
Lint / lint (frontend-embed) (push) Successful in 2m30s
Lint / lint (frontend-shared) (push) Successful in 2m30s
Lint / lint (misskey-bubble-game) (push) Successful in 2m39s
Lint / lint (misskey-js) (push) Successful in 2m35s
Lint / lint (misskey-reversi) (push) Successful in 3m0s
Lint / typecheck (backend) (push) Successful in 2m51s
Lint / lint (sw) (push) Successful in 3m18s
Lint / typecheck (sw) (push) Successful in 1m58s
Lint / typecheck (misskey-js) (push) Successful in 2m41s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-09 05:02:40 -06:00
972e5ef753 Merge pull request 'Merge upstream changes' (#5) from develop into master
All checks were successful
Lint / pnpm_install (push) Successful in 1m35s
Publish Docker image / Build (push) Successful in 5m3s
Test (production install and build) / production (20.16.0) (push) Successful in 1m10s
Test (backend) / unit (20.16.0) (push) Successful in 7m33s
Lint / lint (backend) (push) Successful in 2m11s
Lint / lint (frontend) (push) Successful in 2m11s
Lint / lint (frontend-embed) (push) Successful in 2m12s
Lint / lint (frontend-shared) (push) Successful in 2m15s
Test (backend) / e2e (20.16.0) (push) Successful in 11m3s
Lint / lint (misskey-bubble-game) (push) Successful in 2m14s
Lint / lint (misskey-js) (push) Successful in 2m18s
Lint / lint (misskey-reversi) (push) Successful in 2m13s
Lint / lint (sw) (push) Successful in 2m15s
Lint / typecheck (misskey-js) (push) Successful in 1m20s
Lint / typecheck (backend) (push) Successful in 2m11s
Lint / typecheck (sw) (push) Successful in 1m42s
Reviewed-on: #5
2024-11-08 23:26:55 -06:00
539ce47adb
qualify version
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 2m12s
Lint / pnpm_install (pull_request) Successful in 1m24s
Publish Docker image / Build (push) Successful in 5m10s
Publish Docker image / Build (pull_request) Successful in 5m32s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m19s
Lint / lint (backend) (pull_request) Successful in 2m3s
Test (backend) / unit (20.16.0) (pull_request) Successful in 7m33s
Lint / lint (frontend) (pull_request) Successful in 2m12s
Lint / lint (frontend-embed) (pull_request) Successful in 2m12s
Lint / lint (frontend-shared) (pull_request) Successful in 2m18s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m24s
Lint / lint (misskey-js) (pull_request) Successful in 2m24s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m51s
Lint / typecheck (backend) (pull_request) Successful in 2m42s
Lint / lint (sw) (pull_request) Successful in 2m53s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m54s
Lint / typecheck (sw) (pull_request) Successful in 1m43s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 10m6s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 22:43:14 -06:00
770226a040 Merge tag '2024.11.0-alpha.0' into develop
All checks were successful
Test (backend) / unit (20.16.0) (push) Successful in 6m45s
Test (production install and build) / production (20.16.0) (push) Successful in 1m48s
Lint / lint (backend) (push) Successful in 2m8s
Test (backend) / e2e (20.16.0) (push) Successful in 10m18s
Lint / lint (frontend) (push) Successful in 3m21s
Lint / lint (frontend-embed) (push) Successful in 2m12s
Lint / lint (frontend-shared) (push) Successful in 2m12s
Lint / lint (misskey-bubble-game) (push) Successful in 2m13s
Lint / lint (misskey-js) (push) Successful in 2m13s
Lint / lint (misskey-reversi) (push) Successful in 2m11s
Lint / lint (sw) (push) Successful in 2m10s
Lint / typecheck (backend) (push) Successful in 1m59s
Lint / typecheck (misskey-js) (push) Successful in 1m36s
Lint / typecheck (sw) (push) Successful in 1m22s
Lint / pnpm_install (pull_request) Successful in 1m14s
Publish Docker image / Build (pull_request) Successful in 3m53s
Test (backend) / unit (20.16.0) (pull_request) Successful in 6m33s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m5s
Lint / lint (backend) (pull_request) Successful in 1m59s
Lint / lint (frontend) (pull_request) Successful in 2m0s
Test (backend) / e2e (20.16.0) (pull_request) Successful in 10m26s
Lint / lint (frontend-embed) (pull_request) Successful in 2m3s
Lint / lint (frontend-shared) (pull_request) Successful in 2m8s
Lint / lint (misskey-bubble-game) (pull_request) Successful in 2m1s
Lint / lint (misskey-js) (pull_request) Successful in 2m13s
Lint / lint (misskey-reversi) (pull_request) Successful in 2m6s
Lint / lint (sw) (pull_request) Successful in 2m6s
Lint / typecheck (misskey-js) (pull_request) Successful in 1m16s
Lint / typecheck (backend) (pull_request) Successful in 2m10s
Lint / typecheck (sw) (pull_request) Successful in 1m23s
2024-11-08 21:55:43 -06:00
8e9c175c06
fix branch matching
All checks were successful
Test (production install and build) / production (20.16.0) (push) Successful in 1m10s
Publish Docker image / Build (push) Successful in 4m58s
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m23s
Publish Docker image / Build (pull_request) Successful in 4m26s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 21:26:09 -06:00
616ec50435
auto deployment
Some checks failed
Test (production install and build) / production (20.16.0) (pull_request) Successful in 1m17s
Publish Docker image / Build (pull_request) Successful in 4m30s
Test (production install and build) / production (20.16.0) (push) Successful in 1m18s
Publish Docker image / Build (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 21:00:28 -06:00
c154154645
add docker build
All checks were successful
Lint / pnpm_install (push) Successful in 2m11s
Publish Docker image / Build (push) Successful in 4m23s
Test (backend) / unit (20.16.0) (push) Successful in 6m24s
Test (production install and build) / production (20.16.0) (push) Successful in 1m6s
Lint / lint (backend) (push) Successful in 2m2s
Test (backend) / e2e (20.16.0) (push) Successful in 10m10s
Lint / lint (frontend) (push) Successful in 2m57s
Lint / lint (frontend-embed) (push) Successful in 2m15s
Lint / lint (frontend-shared) (push) Successful in 2m16s
Lint / lint (misskey-bubble-game) (push) Successful in 2m2s
Lint / lint (misskey-js) (push) Successful in 2m4s
Lint / lint (misskey-reversi) (push) Successful in 1m59s
Lint / lint (sw) (push) Successful in 2m13s
Lint / typecheck (backend) (push) Successful in 1m59s
Lint / typecheck (misskey-js) (push) Successful in 1m30s
Lint / typecheck (sw) (push) Successful in 1m25s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 18:33:27 -06:00
3d2f7976ba
use pgroonga image
All checks were successful
Test (backend) / unit (20.16.0) (push) Successful in 6m32s
Test (production install and build) / production (20.16.0) (push) Successful in 1m8s
Test (backend) / e2e (20.16.0) (push) Successful in 10m18s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 17:57:59 -06:00
08a38e0d0b
add test-production
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 17:57:17 -06:00
235cb0af15
test backend on test configuration change
Some checks failed
Test (backend) / unit (20.16.0) (push) Successful in 5m4s
Test (backend) / e2e (20.16.0) (push) Has been cancelled
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 17:51:28 -06:00
a7421ca279
add test-backend workflow
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 17:49:59 -06:00
5125e98699
update ci lint path
All checks were successful
Lint / pnpm_install (push) Successful in 1m47s
Lint / lint (backend) (push) Successful in 3m14s
Lint / lint (frontend-embed) (push) Successful in 2m23s
Lint / lint (frontend-shared) (push) Successful in 2m13s
Lint / lint (misskey-bubble-game) (push) Successful in 2m12s
Lint / lint (frontend) (push) Successful in 11m39s
Lint / lint (misskey-js) (push) Successful in 2m29s
Lint / lint (misskey-reversi) (push) Successful in 2m21s
Lint / lint (sw) (push) Successful in 2m23s
Lint / typecheck (backend) (push) Successful in 2m19s
Lint / typecheck (misskey-js) (push) Successful in 1m25s
Lint / typecheck (sw) (push) Successful in 1m19s
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 09:45:58 -06:00
b6c7c0886a
start ci/cd
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-08 09:44:37 -06:00
283738d5e7
fixup! set reply-to header in automated emails 2024-11-07 01:13:47 -06:00
858800ad2d
lint
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-07 01:09:23 -06:00
28a5dcfa31
set reply-to header in automated emails
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-07 01:08:09 -06:00
b99cf702a0
revert 7f071473b9
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-06 18:52:23 -06:00
6dbc02af9a
fixup! update docker image 2024-11-06 18:27:55 -06:00
7f071473b9
set CREATE INDEX CONCURRENTLY
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-06 18:26:33 -06:00
eb4ccbb662
update docker image
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-06 18:09:41 -06:00
6411e65989
bump Pgroonga migration timestamp
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-06 18:06:43 -06:00
fly_mc
514e7683c0
use PGroonga 2024-11-06 15:37:20 -06:00
6bd7a8ae63
revert set wal_level flag
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-06 02:54:47 -06:00
a59688fab4
add slave profile
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-03 01:36:32 -05:00
44152f876d
revert migration
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-03 00:57:57 -05:00
80209385e8
use custom replication solution
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-03 00:50:33 -05:00
93a9ba8159
consistent ownership
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-01 18:45:19 -05:00
fe58cef568
draft logical replicate
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-01 04:27:57 -05:00
6e7aee287a
Merge branch 'pr-webhook-payload' 2024-11-01 02:38:00 -05:00
411cb8351c
consistency 2024-10-28 20:11:04 -05:00
93a45a5779
fixup! fix(backend): Webhook Test一致性+リアクションhook実装
lint + update misskey-js definitions
2024-10-28 19:54:14 -05:00
4bc97c75c7
fix(backend): Webhook Test一致性+リアクションhook実装
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-28 19:34:18 -05:00
e1ebf46071 Merge tag '2024.10.2-alpha.2' 2024-10-28 13:15:29 -05:00
6c9be945a6
change statement time threshold for logging
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-27 15:10:03 -05:00
c06809c93e Merge remote-tracking branch 'upstream/develop' 2024-10-26 22:34:02 -05:00
9a75e2f53b
fix(backend): Nested proxy requestsを検出した際にブロックするように
This reverts commit 086532218e.

[ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-19 18:11:58 -05:00
acba0bb54c
fix(backend): Serve valid headers for HSTS and HSTS preload
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-19 03:33:47 -05:00
ec060b7a14
Absolutely no leak for media proxies 2024-10-19 01:01:44 -05:00
fly_mc
0bc96fb197
throw unrecoverableError when hit recurition limit 2024-10-18 00:03:44 -05:00
086532218e
Add guard to prevent recursive proxying even when origin=1
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-17 23:56:12 -05:00
fb690169f3
fixup! fix: 外部メディアプロキシ使用時にアバタークロップができない問題を修正 (#10142) 2024-10-17 23:35:42 -05:00
f472f66eea
fixup! fixup! Add screening for relayed mesasges 2024-10-17 12:40:07 -05:00
393487a98a
fixup! Add screening for relayed mesasges 2024-10-17 12:22:06 -05:00
5b9d4cbe01 Revert "Add screening for relayed mesasges"
This reverts commit c8426dca0e.
2024-10-17 11:32:13 -05:00
c8426dca0e
Add screening for relayed mesasges
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-17 10:54:25 -05:00
deb2229dd7
Audit-only firewall (#1)
Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-10-16 21:55:44 -05:00
misskey-release-bot[bot]
b99e13e667
Merge pull request #14741 from misskey-dev/develop
Release: 2024.10.1
2024-10-15 04:53:46 +00:00
misskey-release-bot[bot]
2518cf36d0
Merge pull request #14675 from misskey-dev/develop
Release: 2024.10.0
2024-10-09 05:17:29 +00:00
160 changed files with 8278 additions and 1384 deletions

View file

@ -153,6 +153,13 @@ redis:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -168,12 +175,36 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -147,6 +147,13 @@ redis:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -162,12 +169,36 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -229,6 +229,13 @@ redis:
id: 'aidx'
# ┌──────────┐
#───┘ Metrics └──────────────────────────────────────────
#prometheusMetrics:
# enable: false
# scrapeToken: '' # Set non-empty to require a bearer token for scraping
# ┌────────────────┐
#───┘ Error tracking └──────────────────────────────────────────
@ -244,12 +251,36 @@ id: 'aidx'
# options:
# dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# ──────────────┐
#──┘ Web Security └─────────────────────────────────────
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Enable additional security headers that reduce the risk of XSS attacks or privacy leaks.
# browserSandboxing:
# # Do not send the Referrer header to other domains. The default when browserSandboxing is missing is true.
# strictOriginReferrer: true
# csp:
# # Do not send a CSP header. The default is a strict CSP header that prevents any form of external fetching or execution.
# disable: false
# # Merge additional directives into the CSP header. The default is an empty object.
# # You may want to list your CDN or other trusted domains here.
# # Media proxies are automatically added to the CSP header. This is an exception, things like Sentry will not be automatically added.
# appendDirectives:
# 'script-src':
# - "'unsafe-eval'" # do not use this ... just an example
# - 'https://example.com'
# ┌─────────────────────┐
#───┘ Other configuration └─────────────────────────────────────
# Number of worker processes
#clusterLimit: 1

View file

@ -161,6 +161,12 @@ id: 'aidx'
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Number of worker processes
#clusterLimit: 1

View file

@ -1,8 +1,10 @@
.autogen
.github
.forgejo
.travis
.vscode
.config
Dockerfile
build/
built/
@ -28,3 +30,5 @@ fluent-emojis/
.idea/
packages/*/.vscode/
packages/backend/test/compose.yml
/yume-mods

17
.forgejo/misskey/test.yml Normal file
View file

@ -0,0 +1,17 @@
url: 'http://misskey.local'
setupPassword: example_password_please_change_this_or_you_will_get_hacked
# ローカルでテストするときにポートを被らないようにするためデフォルトのものとは変える(以下同じ)
port: 61812
db:
host: postgres
port: 5432
db: test-misskey
user: postgres
pass: ''
redis:
host: redis
port: 6379
id: aidx

View file

@ -0,0 +1,60 @@
name: Publish Docker image
on:
push:
branches:
- master
- develop
pull_request:
workflow_dispatch:
env:
REGISTRY_IMAGE: l1drm/yumechi-no-kuni
TAGS: |
type=edge
type=ref,event=pr
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
jobs:
# see https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
build:
name: Build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- linux/amd64
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_IMAGE }}
tags: ${{ env.TAGS }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
platforms: ${{ matrix.platform }}
provenance: true
labels: ${{ steps.meta.outputs.labels }}

111
.forgejo/workflows/lint.yml Normal file
View file

@ -0,0 +1,111 @@
name: Lint
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-embed/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/shared/eslint.config.js
- .forgejo/workflows/lint.yml
pull_request:
paths:
- packages/backend/**
- packages/frontend/**
- packages/frontend-shared/**
- packages/frontend-embed/**
- packages/sw/**
- packages/misskey-js/**
- packages/misskey-bubble-game/**
- packages/misskey-reversi/**
- packages/shared/eslint.config.js
- .forgejo/workflows/lint.yml
jobs:
pnpm_install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
lint:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- frontend
- frontend-shared
- frontend-embed
- sw
- misskey-js
- misskey-bubble-game
- misskey-reversi
env:
eslint-cache-version: v1
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Restore eslint cache
uses: actions/cache@v4.1.0
with:
path: ${{ env.eslint-cache-path }}
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location ${{ env.eslint-cache-path }} --cache-strategy content
typecheck:
needs: [pnpm_install]
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
workspace:
- backend
- sw
- misskey-js
steps:
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
submodules: true
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4.0.4
with:
node-version-file: '.node-version'
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- run: pnpm --filter misskey-js run build
if: ${{ matrix.workspace == 'backend' || matrix.workspace == 'sw' }}
- run: pnpm --filter misskey-reversi run build
if: ${{ matrix.workspace == 'backend' }}
- run: pnpm --filter ${{ matrix.workspace }} run typecheck

View file

@ -0,0 +1,98 @@
name: Test (backend)
on:
push:
branches:
- master
- develop
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .forgejo/workflows/test-backend.yml
- .forgejo/misskey/**
pull_request:
paths:
- packages/backend/**
# for permissions
- packages/misskey-js/**
- .forgejo/workflows/test-backend.yml
- .forgejo/misskey/**
jobs:
unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.11.0]
services:
postgres:
image: l1drm/postgres-pgroonga:alpine-15-znver4
env:
POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust
redis:
image: redis:7
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Install FFmpeg
uses: https://github.com/FedericoCarboni/setup-ffmpeg@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .forgejo/misskey/test.yml .config
- name: Build
run: pnpm build
- name: Test
run: pnpm --filter backend test-and-coverage
# e2e:
# runs-on: ubuntu-latest
#
# strategy:
# matrix:
# node-version: [22.11.0]
#
# services:
# postgres:
# image: l1drm/postgres-pgroonga:alpine-15-znver4
# env:
# POSTGRES_DB: test-misskey
# POSTGRES_HOST_AUTH_METHOD: trust
# redis:
# image: redis:7
#
# steps:
# - uses: actions/checkout@v4.1.1
# with:
# submodules: true
# - name: Install pnpm
# uses: pnpm/action-setup@v4
# - name: Use Node.js ${{ matrix.node-version }}
# uses: actions/setup-node@v4.0.4
# with:
# node-version: ${{ matrix.node-version }}
# cache: 'pnpm'
# - run: corepack enable
# - run: pnpm i --frozen-lockfile
# - name: Check pnpm-lock.yaml
# run: git diff --exit-code pnpm-lock.yaml
# - name: Copy Configure
# run: cp .forgejo/misskey/test.yml .config
# - name: Build
# run: pnpm build
# - name: Test
# run: pnpm --filter backend test-and-coverage:e2e

View file

@ -0,0 +1,39 @@
name: Test (production install and build)
on:
push:
branches:
- master
- develop
pull_request:
env:
NODE_ENV: production
jobs:
production:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.11.0]
steps:
- uses: actions/checkout@v4.1.1
with:
submodules: true
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.0.4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: corepack enable
- run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml
- name: Copy Configure
run: cp .github/misskey/test.yml .config/default.yml
- name: Build
run: pnpm build

View file

@ -1,97 +0,0 @@
name: 🐛 Bug Report
description: Create a report to help us improve
labels: ["⚠bug?"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting!
First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
- type: textarea
attributes:
label: 💡 Summary
description: Tell us what the bug is
validations:
required: true
- type: textarea
attributes:
label: 🥰 Expected Behavior
description: Tell us what should happen
validations:
required: true
- type: textarea
attributes:
label: 🤬 Actual Behavior
description: |
Tell us what happens instead of the expected behavior.
Please include errors from the developer console and/or server log files if you have access to them.
validations:
required: true
- type: textarea
attributes:
label: 📝 Steps to Reproduce
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
attributes:
label: 💻 Frontend Environment
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
Examples:
* Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
* Browser: Chrome 113.0.5672.126
* Server URL: misskey.example.com
* Misskey: 2024.x.x
value: |
* Model and OS of the device(s):
* Browser:
* Server URL:
* Misskey:
render: markdown
validations:
required: false
- type: textarea
attributes:
label: 🛰 Backend Environment (for server admin)
description: |
Tell us where on the platform it happens
DO NOT WRITE "latest". Please provide the specific version.
If you are using a managed service, put that after the version.
Examples:
* Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
* Misskey: 2024.x.x
* Node: 20.x.x
* PostgreSQL: 15.x.x
* Redis: 7.x.x
* OS and Architecture: Ubuntu 24.04.2 LTS aarch64
value: |
* Installation Method or Hosting Service:
* Misskey:
* Node:
* PostgreSQL:
* Redis:
* OS and Architecture:
render: markdown
validations:
required: false
- type: checkboxes
attributes:
label: Do you want to address this bug yourself?
options:
- label: Yes, I will patch the bug myself and send a pull request

View file

@ -1,22 +0,0 @@
name: ✨ Feature Request
description: Suggest an idea for this project
labels: ["✨Feature"]
body:
- type: textarea
attributes:
label: Summary
description: Tell us what the suggestion is
validations:
required: true
- type: textarea
attributes:
label: Purpose
description: Describe the specific problem or need you think this feature will solve, and who it will help.
validations:
required: true
- type: checkboxes
attributes:
label: Do you want to implement this feature yourself?
options:
- label: Yes, I will implement this by myself and send a pull request

View file

@ -1,8 +0,0 @@
contact_links:
- name: 💬 Misskey official Discord
url: https://discord.gg/Wp8gVStHW3
about: Chat freely about Misskey
# 仮
- name: 💬 Start discussion
url: https://github.com/misskey-dev/misskey/discussions
about: The official forum to join conversation and ask question

View file

@ -1,23 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View file

@ -1,23 +0,0 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
-->
## What
<!-- このPRで何をしたのか どう変わるのか? -->
<!-- What did you do with this PR? How will it change things? -->
## Why
<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
## Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests

View file

@ -1,20 +0,0 @@
## Summary
This is a release PR.
For more information on the release instructions, please see:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md#release
## For reviewers
- CHANGELOGに抜け漏れは無いか
- バージョンの上げ方は適切か
- 他にこのリリースに含めなければならない変更は無いか
- 全体的な変更内容を俯瞰し問題は無いか
- レビューされていないコミットがある場合は、それが問題ないか
- 最終的な動作確認を行い問題は無いか
などを確認し、リリースする準備が整っていると思われる場合は approve してください。
## Checklist
- [ ] package.jsonのバージョンが正しく更新されている
- [ ] CHANGELOGが過不足無く更新されている
- [ ] CIが全て通っている

View file

@ -1,7 +1,7 @@
<!-- お読みください / README
PRありがとうございます PRを作成する前に、コントリビューションガイドをご確認ください:
Thank you for your PR! Before creating a PR, please check the contribution guide:
https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md
-->
## What
@ -17,7 +17,7 @@ https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
<!-- Test perspective, etc -->
## Checklist
- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
- [ ] Read the [contribution guide](https://forge.yumechi.jp/yume/yumechi-no-kuni/src/branch/master/CONTRIBUTING.md)
- [ ] Test working in a local environment
- [ ] (If needed) Add story of storybook
- [ ] (If needed) Update CHANGELOG.md

View file

@ -1,6 +0,0 @@
build:
misskey:
args:
- NODE_ENV=development
deploy:
- helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development

View file

@ -11,5 +11,9 @@
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"editor.formatOnSave": false
"editor.formatOnSave": false,
"rust-analyzer.linkedProjects": [
"yume-mods/nyuukyou/Cargo.toml",
"yume-mods/misskey-auto-deploy/Cargo.toml",
]
}

View file

@ -1,3 +1,44 @@
## 2024.11.0-yumechinokuni.7
- Misskey Trademark内容をWebUIから削除
- Service Worker キャッシュが正しく動作しない問題を修正
## 2024.11.0-yumechinokuni.6
- Upstream: 2024.11.0-alpha.4 タッグをマージする
- Performance: EmojiのリクエストをProxyでキャッシュするように
- Performance: Service Workerのキャッシュを最適化
- Security: AP Payloadの検証を強化
- Security: Image/Video Processorはドライブ機能だけを使うように
## 2024.11.0-yumechinokuni.5
- Upstream: 2024.11.0-alpha.2 タッグをマージする
- Reliability: Activitypub event deduplication
- DevOps: Prometheus サーバーメトリクス
- Enhance: ハッシュタグランギングを改善
- Enhance: PgroongaのCWサーチ + パフォーマンス改善
## 2024.11.0-yumechinokuni.4
- Upstream: 2024.11.0-alpha.1 タッグをマージする
- DevOps: 管理者アクセストークンがユーザー登録できるようになる (write:admin:create-account)
- Frontend: Stream再接続ロジックdata raceを修正
- Security: CSPにCDNなどの外部ホストはホワイトリストできるように
### 2024.11.0-yumechinokuni.4p1
PgroongaのCWサーチ (github.com/paricafe/misskey#d30db97b59d264450901c1dd86808dcb43875ea9)
### 2024.11.0-yumechinokuni.4p2
- fix(backend): アナウンスメントを作成ときにWebUIフォームの画像URLを後悔できないのを修正 (/admin/announcement/create)
## 2024.11.0-yumechinokuni.3
- Security: CSPの設定を強化
- Fix: flaky testの修正
## 2024.11.0
### Note
@ -30,6 +71,7 @@
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/663)
- Enhance: サイドバーを簡単に展開・折りたたみできるように ( #14981 )
- Enhance: リノートメニューに「リノートの詳細」を追加
- Enhance: 非ログイン状態でMisskeyを開いた際のパフォーマンスを向上
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)

View file

@ -1,49 +1,31 @@
<div align="center">
<a href="https://misskey-hub.net">
<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="300"/>
</a>
# ゆめちのくに
**🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀**
YumechiNoKuni is a fork of Misskey, with a focus on security, observability and reliability.
[Learn more](https://misskey-hub.net/)
[mi.yumechi.jp](https://mi.yumechi.jp) is running this version.
---
[Learn more about Misskey](https://misskey-hub.net/)
<a href="https://misskey-hub.net/servers/">
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
## Main differences
<a href="https://misskey-hub.net/docs/for-admin/install/guides/">
<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
### Unique features
<a href="./CONTRIBUTING.md">
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
- Strict ActivityPub sanitization by whitelisting properties and normalizing all referential properties.
- Strict Content Security Policy.
- Require TLSv1.2+ over port 443 for all ActivityPub requests.
- Strongly-typed inbox filtering in Rust.
- Reduce needless retries by marking more errors as permanent.
- Detailed prometheus metrics for slow requests, DB queries, AP processing, failed auths, etc.
- Disable unauthenticated media processing and use custom AppArmored media proxy.
- Enable active users in nodeinfo back.
- Advertise Git information over nodeinfo for better observability and easy tracking of the actual code running.
- Logical replication for the database over mTLS.
- More atomic operations in API handlers.
<a href="https://discord.gg/Wp8gVStHW3">
<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
### Picked from github.com/paricafe/misskey
<a href="https://www.patreon.com/syuilo">
<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
- pgroonga full-text search (with modifications).
- Better Service Worker caching.
- Better hashtag statistics.
- Better handling of deep recursive AP objects.
</div>
## Thanks
<a href="https://sentry.io/"><img src="https://github.com/misskey-dev/misskey/assets/4439005/98576556-222f-467a-94be-e98dbda1d852" height="30" alt="Sentry" /></a>
Thanks to [Sentry](https://sentry.io/) for providing the error tracking platform that helps us catch unexpected errors.
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" height="30" alt="Chromatic" /></a>
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
<a href="https://about.codecov.io/for/open-source/"><img src="https://about.codecov.io/wp-content/themes/codecov/assets/brand/sentry-cobranding/logos/codecov-by-sentry-logo.svg" height="30" alt="Codecov" /></a>
Thanks to [Codecov](https://about.codecov.io/for/open-source/) for providing the code coverage platform that helps us improve our test coverage.
<a href="https://crowdin.com/"><img src="https://user-images.githubusercontent.com/20679825/230709597-1299a011-171a-4294-a91e-355a9b37c672.svg" height="30" alt="Crowdin" /></a>
Thanks to [Crowdin](https://crowdin.com/) for providing the localization platform that helps us translate Misskey into many languages.
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" height="30" alt="Docker" /></a>
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.

View file

@ -1,15 +1,12 @@
# Reporting Security Issues
If you discover a security issue in Misskey, please report it by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
If you discover a security issue in this project, please use the `git blame` command to identify the source of the issue,
if it was introduced by this fork please contact me at secity<at>yumechi.jp.
This will allow us to assess the risk, and make a fix available before we add a
bug report to the GitHub repository.
For upstream issues please report by **[this form](https://github.com/misskey-dev/misskey/security/advisories/new)**.
Thanks for helping make Misskey safe for everyone.
Thanks for helping make YumechiNoKuni safe for everyone.
## When create a patch
If you can also create a patch to fix the vulnerability, please create a PR on the private fork.
> [!note]
> There is a GitHub bug that prevents merging if a PR not following the develop branch of upstream, so please keep follow the develop branch.
If you can also create a patch to fix the vulnerability, please send a diff file with the report.

View file

@ -182,6 +182,12 @@ id: "aidx"
# Whether disable HSTS
#disableHsts: true
# Whether to enable HSTS preload
# Read these before enabling:
# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security
# - https://hstspreload.org/
#hstsPreload: false
# Number of worker processes
#clusterLimit: 1

View file

@ -15,7 +15,7 @@ services:
db:
restart: always
image: postgres:15-alpine
image: l1drm/postgres-pgroonga:alpine-15-znver4
ports:
- "5432:5432"
env_file:
@ -27,6 +27,18 @@ services:
interval: 5s
retries: 20
# db-publish:
# restart: always
# image: alpine/socat
# ports:
# - "5433:5433"
# volumes:
# - ./replication:/data
# command: [
# "OPENSSL-LISTEN:5433,openssl-min-proto-version=TLS1.2,cert=/data/certs/server.pub.pem,key=/data/certs/server.key.pem,cafile=/data/certs/ca.pem,fork,reuseaddr",
# "TCP4:db:5432"
# ]
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4

View file

@ -1,7 +1,31 @@
services:
web:
build: .
nyuukyou:
build: yume-mods/nyuukyou
restart: always
user: "${MISSKEY_UID}:${MISSKEY_GID}"
profiles: [web]
links:
- web
depends_on:
web:
condition: service_healthy
networks:
- internal_network
- external_network
ports:
- "3001:3001"
volumes:
- ./nyuukyou:/store
web:
build:
context: .
args:
- UID: "${MISSKEY_UID}"
- GID: "${MISSKEY_GID}"
profiles: [web]
restart: always
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
@ -26,6 +50,7 @@ services:
redis:
restart: always
image: redis:7-alpine
user: "${MISSKEY_UID}:${MISSKEY_GID}"
networks:
- internal_network
volumes:
@ -37,7 +62,9 @@ services:
db:
restart: always
image: postgres:15-alpine
image: l1drm/postgres-pgroonga:alpine-15-znver4
user: "${MISSKEY_UID}:${MISSKEY_GID}"
shm_size: 2gb
networks:
- internal_network
env_file:
@ -49,6 +76,106 @@ services:
interval: 5s
retries: 20
replikey:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-master]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
- "5443:5443"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"reverse-proxy",
"--listen",
"0.0.0.0:5443",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
"--redis-sni",
"${MTLS_REDIS_SNI}",
"--redis-target",
"redis:6379",
"--postgres-sni",
"${MTLS_POSTGRES_SNI}",
"--postgres-target",
"db:5432",
]
replikey-postgres-slave:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-slave]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
# - "4001:4001"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"forward-proxy",
"--listen",
"0.0.0.0:4001",
"--sni",
"${MTLS_POSTGRES_SNI}",
"--target",
"db:5432",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
]
replikey-redis-slave:
restart: always
image: l1drm/replikey:latest
profiles: [replikey-slave]
user: "${MISSKEY_UID}:${MISSKEY_GID}"
links:
- db
- redis
networks:
- internal_network
- external_network
ports:
# - "4002:4002"
volumes:
- ./replikey:/etc/replikey:ro
command: [
"network",
"forward-proxy",
"--listen",
"0.0.0.0:4002",
"--sni",
"${MTLS_REDIS_SNI}",
"--target",
"redis:6379",
"--cert",
"/etc/replikey/cert.pem",
"--key",
"/etc/replikey/key.pem",
"--ca",
"/etc/replikey/ca.pem",
]
# mcaptcha:
# restart: always
# image: mcaptcha/mcaptcha:latest

View file

@ -586,6 +586,7 @@ masterVolume: "Volum principal"
notUseSound: "Sense so"
useSoundOnlyWhenActive: "Reproduir sons només quan Misskey estigui actiu"
details: "Detalls"
renoteDetails: "Més informació sobre l'impuls "
chooseEmoji: "Tria un emoji"
unableToProcess: "L'operació no pot ser completada "
recentUsed: "Utilitzat recentment"

View file

@ -1242,6 +1242,7 @@ keepOriginalFilenameDescription: "Wenn diese Einstellung deaktiviert ist, wird d
noDescription: "Keine Beschreibung vorhanden"
tryAgain: "Bitte später erneut versuchen"
confirmWhenRevealingSensitiveMedia: "Das Anzeigen von sensiblen Medien bestätigen"
sensitiveMediaRevealConfirm: "Es könnte sich um sensible Medien handeln. Möchtest du sie anzeigen?"
createdLists: "Erstellte Listen"
createdAntennas: "Erstellte Antennen"
fromX: "Von {x}"
@ -1253,6 +1254,8 @@ thereAreNChanges: "Es gibt {n} Änderung(en)"
signinWithPasskey: "Mit Passkey anmelden"
passkeyVerificationFailed: "Die Passkey-Verifizierung ist fehlgeschlagen."
passkeyVerificationSucceededButPasswordlessLoginDisabled: "Die Verifizierung des Passkeys war erfolgreich, aber die passwortlose Anmeldung ist deaktiviert."
messageToFollower: "Nachricht an die Follower"
testCaptchaWarning: "Diese Funktion ist für CAPTCHA-Testzwecke gedacht.\n<strong>Nicht in einer Produktivumgebung verwenden.</strong>"
prohibitedWordsForNameOfUser: "Verbotene Begriffe für Benutzernamen"
prohibitedWordsForNameOfUserDescription: "Wenn eine Zeichenfolge aus dieser Liste im Namen eines Benutzers enthalten ist, wird der Benutzername abgelehnt. Benutzer mit Moderatorenrechten sind von dieser Einschränkung nicht betroffen."
yourNameContainsProhibitedWords: "Dein Name enthält einen verbotenen Begriff"
@ -1264,6 +1267,7 @@ _accountSettings:
requireSigninToViewContentsDescription1: "Erfordere eine Anmeldung, um alle Notizen und andere Inhalte anzuzeigen, die du erstellt hast. Dadurch wird verhindert, dass Crawler deine Informationen sammeln."
requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern."
makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar"
makeNotesHiddenBefore: "Frühere Notizen privat machen"
mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden."
_abuseUserReport:
forward: "Weiterleiten"
@ -1274,6 +1278,7 @@ _delivery:
stop: "Gesperrt"
_type:
none: "Wird veröffentlicht"
manuallySuspended: "Manuell gesperrt"
_bubbleGame:
howToPlay: "Wie man spielt"
hold: "Halten"

View file

@ -586,6 +586,7 @@ masterVolume: "Master volume"
notUseSound: "Disable sound"
useSoundOnlyWhenActive: "Output sounds only if Misskey is active."
details: "Details"
renoteDetails: "Renote details"
chooseEmoji: "Select an emoji"
unableToProcess: "The operation could not be completed"
recentUsed: "Recently used"
@ -2120,6 +2121,7 @@ _permissions:
"read:flash-likes": "View list of liked Plays"
"write:flash-likes": "Edit list of liked Plays"
"read:admin:abuse-user-reports": "View user reports"
"write:admin:create-account": "Create user account"
"write:admin:delete-account": "Delete user account"
"write:admin:delete-all-files-of-a-user": "Delete all files of a user"
"read:admin:index-stats": "View database index stats"

4
locales/index.d.ts vendored
View file

@ -8250,6 +8250,10 @@ export interface Locale extends ILocale {
*
*/
"read:admin:abuse-user-reports": string;
/**
*
*/
"write:admin:create-account": string;
/**
*
*/

View file

@ -2166,6 +2166,7 @@ _permissions:
"read:flash-likes": "Playのいいねを見る"
"write:flash-likes": "Playのいいねを操作する"
"read:admin:abuse-user-reports": "ユーザーからの通報を見る"
"write:admin:create-account": "ユーザーアカウントを作成する"
"write:admin:delete-account": "ユーザーアカウントを削除する"
"write:admin:delete-all-files-of-a-user": "ユーザーのすべてのファイルを削除する"
"read:admin:index-stats": "データベースインデックスに関する情報を見る"

View file

@ -586,6 +586,7 @@ masterVolume: "마스터 볼륨"
notUseSound: "음소거 하기"
useSoundOnlyWhenActive: "Misskey를 활성화한 때에만 소리를 출력하기"
details: "자세히"
renoteDetails: "리노트 상세 내용"
chooseEmoji: "이모지 선택"
unableToProcess: "작업을 완료할 수 없습니다"
recentUsed: "최근 사용"
@ -1299,6 +1300,7 @@ thisContentsAreMarkedAsSigninRequiredByAuthor: "게시자에 의해 로그인해
lockdown: "잠금"
pleaseSelectAccount: "계정을 선택해주세요."
availableRoles: "사용 가능한 역할"
acknowledgeNotesAndEnable: "활성화 하기 전에 주의 사항을 확인했습니다."
_accountSettings:
requireSigninToViewContents: "콘텐츠 열람을 위해 로그인으 필수로 설정하기"
requireSigninToViewContentsDescription1: "자신이 작성한 모든 노트 등의 콘텐츠를 보기 위해 로그인을 필수로 설정합니다. 크롤러가 정보 수집하는 것을 방지하는 효과를 기대할 수 있습니다."
@ -1455,6 +1457,8 @@ _serverSettings:
reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다."
inquiryUrl: "문의처 URL"
inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다."
openRegistration: "회원 가입을 활성화 하기"
openRegistrationWarning: "회원 가입을 개방하는 것은 리스크가 따릅니다. 서버를 항상 감시할 수 있고, 문제가 발생했을 때 바로 대응할 수 있는 상태에서만 활성화 하는 것을 권장합니다."
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "일정 기간동안 모더레이터의 활동이 감지되지 않는 경우, 스팸 방지를 위해 이 설정은 자동으로 꺼집니다."
_accountMigration:
moveFrom: "다른 계정에서 이 계정으로 이사"
@ -2737,3 +2741,6 @@ _selfXssPrevention:
description1: "여기에 무언가를 붙여넣으면 악의적인 사용자에게 계정을 탈취당하거나 개인정보를 도용당할 수 있습니다."
description2: "붙여 넣으려는 항목이 무엇인지 정확히 이해하지 못하는 경우, %c지금 바로 작업을 중단하고 이 창을 닫으십시오."
description3: "자세한 내용은 여기를 확인해 주세요. {link}"
_followRequest:
recieved: "받은 신청"
sent: "보낸 신청"

View file

@ -1707,9 +1707,9 @@ _achievements:
description: "在元旦登入"
flavor: "今年也请对本服务器多多指教!"
_cookieClicked:
title: "点击饼干小游戏"
title: "饼干点点乐"
description: "点击了饼干"
flavor: "用错软件了?"
flavor: "穿越了?"
_brainDiver:
title: "Brain Diver"
description: "发布了包含 Brain Diver 链接的帖子"

View file

@ -586,6 +586,7 @@ masterVolume: "主音量"
notUseSound: "關閉音效"
useSoundOnlyWhenActive: "瀏覽器在前景運作時Misskey 才會發出音效"
details: "詳細資訊"
renoteDetails: "轉發貼文的細節"
chooseEmoji: "選擇您的表情符號"
unableToProcess: "操作無法完成"
recentUsed: "最近使用"

View file

@ -1,10 +1,10 @@
{
"name": "misskey",
"version": "2024.11.0-alpha.2",
"version": "2024.11.0-yumechinokuni.7",
"codename": "nasubi",
"repository": {
"type": "git",
"url": "https://github.com/misskey-dev/misskey.git"
"url": "https://forge.yumechi.jp/yume.yumechi-no-kuni.git"
},
"packageManager": "pnpm@9.6.0",
"workspaces": [
@ -24,15 +24,15 @@
"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
"build-storybook": "pnpm --filter frontend build-storybook",
"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json --no-build && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
"start": "pnpm check:connect && cd packages/backend && cross-env RUN_MODE=web node ./built/boot/entry.js",
"start:test": "cd packages/backend && cross-env NODE_ENV=test RUN_MODE=test node ./built/boot/entry.js",
"init": "pnpm migrate",
"migrate": "cd packages/backend && pnpm migrate",
"revert": "cd packages/backend && pnpm revert",
"check:connect": "cd packages/backend && pnpm check:connect",
"migrateandstart": "pnpm migrate && pnpm start",
"watch": "pnpm dev",
"dev": "node scripts/dev.mjs",
"dev": "cross-env RUN_MODE=dev node scripts/dev.mjs",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
@ -52,7 +52,7 @@
},
"dependencies": {
"cssnano": "6.1.2",
"execa": "8.0.1",
"execa": "9.5.1",
"fast-glob": "3.3.2",
"ignore-walk": "6.0.5",
"js-yaml": "4.1.0",
@ -61,14 +61,14 @@
"terser": "5.36.0",
"typescript": "5.6.3",
"esbuild": "0.24.0",
"glob": "11.0.0"
"glob": "11.0.0",
"cross-env": "7.0.3"
},
"devDependencies": {
"@misskey-dev/eslint-plugin": "2.0.3",
"@types/node": "22.9.0",
"@typescript-eslint/eslint-plugin": "7.17.0",
"@typescript-eslint/parser": "7.17.0",
"cross-env": "7.0.3",
"cypress": "13.15.2",
"eslint": "9.14.0",
"globals": "15.12.0",

View file

@ -0,0 +1,18 @@
export class Pgroonga1730937958242 {
name = 'Pgroonga1730937958242'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_f27f5d88941e57442be75ba9c8" ON "note" USING "pgroonga" ("text")`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops_v2)`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_065d4d8f3b5adb4a08841eae3c" ON "user" USING "pgroonga" ("name" pgroonga_varchar_full_text_search_ops_v2)`);
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_fcb770976ff8240af5799e3ffc" ON "user_profile" USING "pgroonga" ("description" pgroonga_varchar_full_text_search_ops_v2) `);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_f27f5d88941e57442be75ba9c8"`);
await queryRunner.query(`DROP INDEX "public"."IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`DROP INDEX "public"."IDX_065d4d8f3b5adb4a08841eae3c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_fcb770976ff8240af5799e3ffc"`);
}
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class IncreaseCharacterLimits1731757142918 {
name = 'IncreaseCharacterLimits1731757142918'
async up(queryRunner) {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(32768)`);
await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE text`);
await queryRunner.query(`CREATE INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw")`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE varchar(512)`);
await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(8192)`);
await queryRunner.query(`DROP INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85"`);
await queryRunner.query(`CREATE INDEX "IDX_7cc8d9b0ee7861b4e5dc86ad85" ON "note" USING "pgroonga" ("cw" pgroonga_varchar_full_text_search_ops)`);
}
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project and yumechi
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class IndexUserDeleted1732071810971 {
name = 'IndexUserDeleted1732071810971'
async up(queryRunner) {
await queryRunner.query(`CREATE INDEX IF NOT EXISTS "IDX_199b79e682bdc5ba946f491686" ON "user" ("isDeleted")`);
}
async down(queryRunner) {
await queryRunner.query(`DROP INDEX IF EXISTS "IDX_199b79e682bdc5ba946f491686"`);
}
}

View file

@ -134,8 +134,8 @@
"json5": "2.2.3",
"jsonld": "8.3.2",
"jsrsasign": "11.1.0",
"meilisearch": "0.45.0",
"juice": "11.0.0",
"meilisearch": "0.45.0",
"mfm-js": "0.24.0",
"microformats-parser": "2.0.2",
"mime-types": "2.1.35",
@ -156,6 +156,7 @@
"pg": "8.13.1",
"pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3",
"prom-client": "^15.1.3",
"promise-limit": "2.7.0",
"pug": "3.0.3",
"punycode": "2.3.1",
@ -234,7 +235,7 @@
"aws-sdk-client-mock": "4.0.1",
"cross-env": "7.0.3",
"eslint-plugin-import": "2.30.0",
"execa": "8.0.1",
"execa": "9.5.1",
"fkill": "9.0.0",
"jest": "29.7.0",
"jest-mock": "29.7.0",

View file

@ -22,6 +22,7 @@ async function connectToRedis(redisOptions) {
lazyConnect: true,
reconnectOnError: false,
showFriendlyErrorStack: true,
connectTimeout: 10000,
});
redis.on('error', e => reject(e));
@ -50,7 +51,9 @@ const promises = Array
]))
.map(connectToRedis)
.concat([
connectToPostgres()
connectToPostgres().then(() => { console.log('Connected to PostgreSQL.'); }),
]);
await Promise.allSettled(promises);
process.exit(0);

View file

@ -24,7 +24,7 @@ const $config: Provider = {
const $db: Provider = {
provide: DI.db,
useFactory: async (config) => {
const db = createPostgresDataSource(config);
const db = createPostgresDataSource(config, true);
return await db.initialize();
},
inject: [DI.config],

View file

@ -4,6 +4,7 @@
*/
import { NestFactory } from '@nestjs/core';
import * as prom from 'prom-client';
import { ChartManagementService } from '@/core/chart/ChartManagementService.js';
import { QueueProcessorService } from '@/queue/QueueProcessorService.js';
import { NestLogger } from '@/NestLogger.js';
@ -12,8 +13,9 @@ import { QueueStatsService } from '@/daemons/QueueStatsService.js';
import { ServerStatsService } from '@/daemons/ServerStatsService.js';
import { ServerService } from '@/server/ServerService.js';
import { MainModule } from '@/MainModule.js';
import { MetricsService } from '@/server/api/MetricsService.js';
export async function server() {
export async function server(workerRegistry?: prom.AggregatorRegistry<prom.PrometheusContentType>) {
const app = await NestFactory.createApplicationContext(MainModule, {
logger: new NestLogger(),
});
@ -22,6 +24,9 @@ export async function server() {
await serverService.launch();
if (process.env.NODE_ENV !== 'test') {
if (workerRegistry) {
app.get(MetricsService).setWorkerRegistry(workerRegistry);
}
app.get(ChartManagementService).start();
app.get(QueueStatsService).start();
app.get(ServerStatsService).start();

View file

@ -8,6 +8,7 @@
*/
import cluster from 'node:cluster';
import * as prom from 'prom-client';
import { EventEmitter } from 'node:events';
import chalk from 'chalk';
import Xev from 'xev';
@ -17,6 +18,15 @@ import { masterMain } from './master.js';
import { workerMain } from './worker.js';
import { readyRef } from './ready.js';
const workerRegistry = new prom.AggregatorRegistry<prom.PrometheusContentType>();
prom.collectDefaultMetrics({
labels: {
cluster_type: `${cluster.isPrimary ? 'master' : 'worker'}`,
worker_id: cluster.worker?.id.toString() || 'none'
}
});
import 'reflect-metadata';
process.title = `Misskey (${cluster.isPrimary ? 'master' : 'worker'})`;
@ -69,7 +79,7 @@ process.on('exit', code => {
//#endregion
if (cluster.isPrimary || envOption.disableClustering) {
await masterMain();
await masterMain(workerRegistry);
if (cluster.isPrimary) {
ev.mount();

View file

@ -7,6 +7,7 @@ import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import * as os from 'node:os';
import * as prom from 'prom-client';
import cluster from 'node:cluster';
import chalk from 'chalk';
import chalkTemplate from 'chalk-template';
@ -18,6 +19,7 @@ import type { Config } from '@/config.js';
import { showMachineInfo } from '@/misc/show-machine-info.js';
import { envOption } from '@/env.js';
import { jobQueue, server } from './common.js';
import { metricGauge } from '@/server/api/MetricsService.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -29,6 +31,24 @@ const bootLogger = logger.createSubLogger('boot', 'magenta');
const themeColor = chalk.hex('#86b300');
const mBuildInfo = metricGauge({
name: 'misskey_build_info',
help: 'Misskey build information',
labelNames: ['gitCommit', 'gitDescribe', 'node_version']
});
mBuildInfo?.set({
gitCommit: meta.gitCommit || 'unknown',
gitDescribe: meta.gitDescribe || 'unknown',
node_version: process.version
}, 1);
const mStartupTime = metricGauge({
name: 'misskey_startup_time',
help: 'Misskey startup time',
labelNames: ['pid']
});
function greet() {
if (!envOption.quiet) {
//#region Misskey logo
@ -54,7 +74,7 @@ function greet() {
/**
* Init master process
*/
export async function masterMain() {
export async function masterMain(workerRegistry?: prom.AggregatorRegistry<prom.PrometheusContentType>) {
let config!: Config;
// initialize app
@ -64,6 +84,7 @@ export async function masterMain() {
await showMachineInfo(bootLogger);
showNodejsVersion();
config = loadConfigBoot();
//await connectDb();
if (config.pidFile) fs.writeFileSync(config.pidFile, process.pid.toString());
} catch (e) {
@ -91,13 +112,15 @@ export async function masterMain() {
});
}
mStartupTime?.set({ pid: process.pid }, Date.now());
if (envOption.disableClustering) {
if (envOption.onlyServer) {
await server();
await server(workerRegistry);
} else if (envOption.onlyQueue) {
await jobQueue();
} else {
await server();
await server(workerRegistry);
await jobQueue();
}
} else {
@ -106,7 +129,7 @@ export async function masterMain() {
} else if (envOption.onlyQueue) {
// nop
} else {
await server();
await server(workerRegistry);
}
await spawnWorkers(config.clusterLimit);

View file

@ -4,6 +4,7 @@
*/
import cluster from 'node:cluster';
import { collectDefaultMetrics, AggregatorRegistry, RegistryContentType } from 'prom-client';
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';
import { envOption } from '@/env.js';

View file

@ -9,6 +9,7 @@ import { dirname, resolve } from 'node:path';
import * as yaml from 'js-yaml';
import * as Sentry from '@sentry/node';
import type { RedisOptions } from 'ioredis';
import { type CSPHashed, hashResource, hashSourceFile } from './server/csp.js';
type RedisOptionsSource = Partial<RedisOptions> & {
host: string;
@ -19,6 +20,18 @@ type RedisOptionsSource = Partial<RedisOptions> & {
prefix?: string;
};
type BrowserSandboxing = {
// send Referrer-Policy: strict-origin
strictOriginReferrer?: boolean;
csp?: {
disable?: boolean;
appendDirectives?: {
[directive: string]: string | string[];
}
};
};
/**
*
*/
@ -28,6 +41,7 @@ type Source = {
socket?: string;
chmodSocket?: string;
disableHsts?: boolean;
hstsPreload?: boolean;
db: {
host: string;
port: number;
@ -58,11 +72,16 @@ type Source = {
index: string;
scope?: 'local' | 'global' | string[];
};
prometheusMetrics?: { enable: boolean, scrapeToken?: string };
sentryForBackend?: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; };
sentryForFrontend?: { options: Partial<Sentry.NodeOptions> };
publishTarballInsteadOfProvideRepositoryUrl?: boolean;
browserSandboxing?: BrowserSandboxing;
setupPassword?: string;
proxy?: string;
@ -107,6 +126,7 @@ export type Config = {
socket: string | undefined;
chmodSocket: string | undefined;
disableHsts: boolean | undefined;
hstsPreload: boolean | undefined;
db: {
host: string;
port: number;
@ -152,7 +172,13 @@ export type Config = {
proxyRemoteFiles: boolean | undefined;
signToActivityPubGet: boolean | undefined;
browserSandboxing: BrowserSandboxing;
cspPrerenderedContent: Map<string, CSPHashed>;
version: string;
gitDescribe: string;
gitCommit: string;
publishTarballInsteadOfProvideRepositoryUrl: boolean;
setupPassword: string | undefined;
host: string;
@ -176,8 +202,12 @@ export type Config = {
redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource;
redisForReactions: RedisOptions & RedisOptionsSource;
prometheusMetrics : { enable: boolean, scrapeToken?: string } | undefined;
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
@ -216,7 +246,7 @@ export function loadConfig(): Config {
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
const version = meta.version;
const { version, gitDescribe, gitCommit } = meta;
const host = url.host;
const hostname = url.hostname;
const scheme = url.protocol.replace(/:$/, '');
@ -231,9 +261,21 @@ export function loadConfig(): Config {
: null;
const internalMediaProxy = `${scheme}://${host}/proxy`;
const redis = convertRedisOptions(config.redis, host);
const htmlScriptPrelude = `var VERSION = ${JSON.stringify(version)}; var CLIENT_ENTRY = ${JSON.stringify(frontendManifest['src/_boot_.ts'].file)};`;
const htmlEmbedScriptPrelude = `var VERSION = ${JSON.stringify(version)}; var CLIENT_ENTRY = ${JSON.stringify(frontendEmbedManifest['src/boot.ts'].file)};`;
const cspPrerenderedContent = new Map([
['.prelude.js', hashResource(htmlScriptPrelude)],
['.prelude.embed.js', hashResource(htmlEmbedScriptPrelude)],
...['boot.js', 'style.css', 'style.embed.css', 'boot.embed.js',
'bios.css', 'bios.js', 'cli.css', 'cli.js', 'error.css'
].map((file) => [file, hashSourceFile(`${_dirname}/server/web/${file}`)] as [string, CSPHashed]),
]);
return {
version,
gitCommit,
gitDescribe,
browserSandboxing: config.browserSandboxing ?? { strictOriginReferrer: true },
publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
setupPassword: config.setupPassword,
url: url.origin,
@ -241,6 +283,8 @@ export function loadConfig(): Config {
socket: config.socket,
chmodSocket: config.chmodSocket,
disableHsts: config.disableHsts,
hstsPreload: config.hstsPreload ?? false,
cspPrerenderedContent,
host,
hostname,
scheme,
@ -258,6 +302,7 @@ export function loadConfig(): Config {
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
prometheusMetrics: config.prometheusMetrics,
sentryForBackend: config.sentryForBackend,
sentryForFrontend: config.sentryForFrontend,
id: config.id,

View file

@ -36,7 +36,7 @@ import { GlobalEventService } from './GlobalEventService.js';
import { HashtagService } from './HashtagService.js';
import { HttpRequestService } from './HttpRequestService.js';
import { IdService } from './IdService.js';
import { ImageProcessingService } from './ImageProcessingService.js';
import { __YUME_PRIVATE_ImageProcessingService } from './ImageProcessingService.js';
import { InstanceActorService } from './InstanceActorService.js';
import { InternalStorageService } from './InternalStorageService.js';
import { MetaService } from './MetaService.js';
@ -67,7 +67,7 @@ import { UserMutingService } from './UserMutingService.js';
import { UserRenoteMutingService } from './UserRenoteMutingService.js';
import { UserSuspendService } from './UserSuspendService.js';
import { UserAuthService } from './UserAuthService.js';
import { VideoProcessingService } from './VideoProcessingService.js';
import { __YUME_PRIVATE_VideoProcessingService } from './VideoProcessingService.js';
import { UserWebhookService } from './UserWebhookService.js';
import { ProxyAccountService } from './ProxyAccountService.js';
import { UtilityService } from './UtilityService.js';
@ -179,7 +179,7 @@ const $GlobalEventService: Provider = { provide: 'GlobalEventService', useExisti
const $HashtagService: Provider = { provide: 'HashtagService', useExisting: HashtagService };
const $HttpRequestService: Provider = { provide: 'HttpRequestService', useExisting: HttpRequestService };
const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
const $ImageProcessingService: Provider = { provide: '__YUME_PRIVATE_ImageProcessingService', useExisting: __YUME_PRIVATE_ImageProcessingService };
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
@ -212,7 +212,7 @@ const $UserRenoteMutingService: Provider = { provide: 'UserRenoteMutingService',
const $UserSearchService: Provider = { provide: 'UserSearchService', useExisting: UserSearchService };
const $UserSuspendService: Provider = { provide: 'UserSuspendService', useExisting: UserSuspendService };
const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: UserAuthService };
const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
const $VideoProcessingService: Provider = { provide: '__YUME_PRIVATE_VideoProcessingService', useExisting: __YUME_PRIVATE_VideoProcessingService };
const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService };
@ -330,7 +330,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
HashtagService,
HttpRequestService,
IdService,
ImageProcessingService,
__YUME_PRIVATE_ImageProcessingService,
InstanceActorService,
InternalStorageService,
MetaService,
@ -363,7 +363,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserSearchService,
UserSuspendService,
UserAuthService,
VideoProcessingService,
__YUME_PRIVATE_VideoProcessingService,
UserWebhookService,
SystemWebhookService,
WebhookTestService,
@ -625,7 +625,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
HashtagService,
HttpRequestService,
IdService,
ImageProcessingService,
__YUME_PRIVATE_ImageProcessingService,
InstanceActorService,
InternalStorageService,
MetaService,
@ -658,7 +658,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
UserSearchService,
UserSuspendService,
UserAuthService,
VideoProcessingService,
__YUME_PRIVATE_VideoProcessingService,
UserWebhookService,
SystemWebhookService,
WebhookTestService,

View file

@ -47,6 +47,10 @@ export class DeleteAccountService {
});
}
if (!(await this.usersRepository.update({ id: user.id, isDeleted: false }, { isDeleted: true })).affected) {
return;
}
// 物理削除する前にDelete activityを送信する
if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信

View file

@ -6,7 +6,6 @@
import * as fs from 'node:fs';
import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common';
import ipaddr from 'ipaddr.js';
import chalk from 'chalk';
import got, * as Got from 'got';
import { parse } from 'content-disposition';
@ -61,7 +60,6 @@ export class DownloadService {
request: operationTimeout, // whole operation timeout
},
agent: {
http: this.httpRequestService.httpAgent,
https: this.httpRequestService.httpsAgent,
},
http2: false, // default
@ -70,13 +68,6 @@ export class DownloadService {
},
enableUnixSockets: false,
}).on('response', (res: Got.Response) => {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) {
if (this.isPrivateIp(res.ip)) {
this.logger.warn(`Blocked address: ${res.ip}`);
req.destroy();
}
}
const contentLength = res.headers['content-length'];
if (contentLength != null) {
const size = Number(contentLength);
@ -139,18 +130,4 @@ export class DownloadService {
cleanup();
}
}
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}

View file

@ -22,8 +22,8 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { contentDisposition } from '@/misc/content-disposition.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import { __YUME_PRIVATE_VideoProcessingService } from '@/core/VideoProcessingService.js';
import { __YUME_PRIVATE_ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { QueueService } from '@/core/QueueService.js';
import type { MiDriveFolder } from '@/models/DriveFolder.js';
@ -87,9 +87,9 @@ type UploadFromUrlArgs = {
@Injectable()
export class DriveService {
public static NoSuchFolderError = class extends Error {};
public static InvalidFileNameError = class extends Error {};
public static CannotUnmarkSensitiveError = class extends Error {};
public static NoSuchFolderError = class extends Error { };
public static InvalidFileNameError = class extends Error { };
public static CannotUnmarkSensitiveError = class extends Error { };
private registerLogger: Logger;
private downloaderLogger: Logger;
private deleteLogger: Logger;
@ -120,8 +120,8 @@ export class DriveService {
private downloadService: DownloadService,
private internalStorageService: InternalStorageService,
private s3Service: S3Service,
private imageProcessingService: ImageProcessingService,
private videoProcessingService: VideoProcessingService,
private privateImageProcessingService: __YUME_PRIVATE_ImageProcessingService,
private privateVideoProcessingService: __YUME_PRIVATE_VideoProcessingService,
private globalEventService: GlobalEventService,
private queueService: QueueService,
private roleService: RoleService,
@ -147,11 +147,11 @@ export class DriveService {
*/
@bindThis
private async save(file: MiDriveFile, path: string, name: string, type: string, hash: string, size: number): Promise<MiDriveFile> {
// thunbnail, webpublic を必要なら生成
// thunbnail, webpublic を必要なら生成
const alts = await this.generateAlts(path, type, !file.uri);
if (this.meta.useObjectStorage) {
//#region ObjectStorage params
//#region ObjectStorage params
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
if (ext === '') {
@ -170,11 +170,11 @@ export class DriveService {
}
const baseUrl = this.meta.objectStorageBaseUrl
?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
?? `${this.meta.objectStorageUseSSL ? 'https' : 'http'}://${this.meta.objectStorageEndpoint}${this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : ''}/${this.meta.objectStorageBucket}`;
// for original
const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
const url = `${ baseUrl }/${ key }`;
const url = `${baseUrl}/${key}`;
// for alts
let webpublicKey: string | null = null;
@ -191,7 +191,7 @@ export class DriveService {
if (alts.webpublic) {
webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
webpublicUrl = `${baseUrl}/${webpublicKey}`;
this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
uploads.push(this.upload(webpublicKey, alts.webpublic.data, alts.webpublic.type, alts.webpublic.ext, name));
@ -199,7 +199,7 @@ export class DriveService {
if (alts.thumbnail) {
thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
thumbnailUrl = `${baseUrl}/${thumbnailKey}`;
this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`));
@ -277,7 +277,7 @@ export class DriveService {
}
try {
const thumbnail = await this.videoProcessingService.generateVideoThumbnail(path);
const thumbnail = await this.privateVideoProcessingService.generateVideoThumbnail(path);
return {
webpublic: null,
thumbnail,
@ -311,9 +311,9 @@ export class DriveService {
satisfyWebpublic = !!(
type !== 'image/svg+xml' && // security reason
type !== 'image/avif' && // not supported by Mastodon and MS Edge
!(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) &&
metadata.width && metadata.width <= 2048 &&
metadata.height && metadata.height <= 2048
!(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) &&
metadata.width && metadata.width <= 2048 &&
metadata.height && metadata.height <= 2048
);
} catch (err) {
this.registerLogger.warn(`sharp failed: ${err}`);
@ -331,9 +331,9 @@ export class DriveService {
try {
if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) {
webpublic = await this.imageProcessingService.convertSharpToWebp(img, 2048, 2048);
webpublic = await this.privateImageProcessingService.convertSharpToWebp(img, 2048, 2048);
} else if (['image/png', 'image/bmp', 'image/svg+xml'].includes(type)) {
webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048);
webpublic = await this.privateImageProcessingService.convertSharpToPng(img, 2048, 2048);
} else {
this.registerLogger.debug('web image not created (not an required image)');
}
@ -352,9 +352,9 @@ export class DriveService {
try {
if (isAnimated) {
thumbnail = await this.imageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 });
thumbnail = await this.privateImageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 });
} else {
thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422);
thumbnail = await this.privateImageProcessingService.convertSharpToWebp(img, 498, 422);
}
} catch (err) {
this.registerLogger.warn('thumbnail not created (an error occurred)', err as Error);
@ -470,11 +470,11 @@ export class DriveService {
const info = await this.fileInfoService.getFileInfo(path, {
skipSensitiveDetection: skipNsfwCheck,
sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる
this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
0.5,
this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
0.5,
sensitiveThresholdForPorn: 0.75,
enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos,
});
@ -494,7 +494,7 @@ export class DriveService {
);
if (user && !force) {
// Check if there is a file with the same hash
// Check if there is a file with the same hash
const matched = await this.driveFilesRepository.findOneBy({
md5: info.md5,
userId: user.id,
@ -582,7 +582,7 @@ export class DriveService {
file.maybePorn = info.porn;
file.isSensitive = user
? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
sensitive ?? false
sensitive ?? false
: false;
if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true;
@ -616,7 +616,7 @@ export class DriveService {
file = await this.driveFilesRepository.insertOne(file);
} catch (err) {
// duplicate key error (when already registered)
// duplicate key error (when already registered)
if (isDuplicateKeyValueError(err)) {
this.registerLogger.info(`already registered ${file.uri}`);

View file

@ -145,6 +145,7 @@ export class EmailService {
try {
// TODO: htmlサニタイズ
const info = await transporter.sendMail({
replyTo: this.meta.maintainerEmail ? { name: this.meta.maintainerName || 'Instance Maintainer', address: this.meta.maintainerEmail } : undefined,
from: this.meta.email!,
to: to,
subject: subject,
@ -312,6 +313,7 @@ export class EmailService {
Accept: 'application/json',
Authorization: truemailAuthKey,
},
isLocalAddressAllowed: true,
});
const json = (await res.json()) as {

View file

@ -12,7 +12,7 @@ import { bindThis } from '@/decorators.js';
const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
export const GALLERY_POSTS_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60 * 48; // 48時間ごと
const featuredEpoc = new Date('2023-01-01T00:00:00Z').getTime();

View file

@ -6,41 +6,122 @@
import * as http from 'node:http';
import * as https from 'node:https';
import * as net from 'node:net';
import ipaddr from 'ipaddr.js';
import CacheableLookup from 'cacheable-lookup';
import fetch from 'node-fetch';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import { HttpsProxyAgent } from 'hpagent';
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { Response } from 'node-fetch';
import type { URL } from 'node:url';
import { URL } from 'node:url';
export type HttpRequestSendOptions = {
throwErrorWhenResponseNotOk: boolean;
validators?: ((res: Response) => void)[];
};
declare module 'node:http' {
interface Agent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket;
}
}
class HttpRequestServiceAgent extends http.Agent {
constructor(
private config: Config,
options?: http.AgentOptions,
) {
super(options);
}
@bindThis
public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback)
.on('connect', () => {
const address = socket.remoteAddress;
if (process.env.NODE_ENV === 'production') {
if (address && ipaddr.isValid(address)) {
if (this.isPrivateIp(address)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
}
});
return socket;
};
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
class HttpsRequestServiceAgent extends https.Agent {
constructor(
private config: Config,
options?: https.AgentOptions,
) {
super(options);
}
@bindThis
public createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback)
.on('connect', () => {
const address = socket.remoteAddress;
if (process.env.NODE_ENV === 'production') {
if (address && ipaddr.isValid(address)) {
if (this.isPrivateIp(address)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
}
});
return socket;
};
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
@Injectable()
export class HttpRequestService {
/**
* Get http non-proxy agent
* Get https non-proxy agent (without local address filtering)
*/
private http: http.Agent;
private httpsNative: https.Agent;
/**
* Get https non-proxy agent
*/
private https: https.Agent;
/**
* Get http proxy or non-proxy agent
*/
public httpAgent: http.Agent;
/**
* Get https proxy or non-proxy agent
*/
@ -56,34 +137,20 @@ export class HttpRequestService {
lookup: false, // nativeのdns.lookupにfallbackしない
});
this.http = new http.Agent({
const agentOption = {
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
localAddress: config.outgoingAddress,
});
minVersion: 'TLSv1.2' as const,
};
this.https = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
localAddress: config.outgoingAddress,
});
this.httpsNative = new https.Agent(agentOption);
this.https = new HttpsRequestServiceAgent(config, agentOption);
const maxSockets = Math.max(256, config.deliverJobConcurrency ?? 128);
this.httpAgent = config.proxy
? new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
localAddress: config.outgoingAddress,
})
: this.http;
this.httpsAgent = config.proxy
? new HttpsProxyAgent({
keepAlive: true,
@ -103,16 +170,22 @@ export class HttpRequestService {
* @param bypassProxy Allways bypass proxy
*/
@bindThis
public getAgentByUrl(url: URL, bypassProxy = false): http.Agent | https.Agent {
public getAgentByUrl(url: URL, bypassProxy = false): https.Agent {
if (url.protocol !== 'https:') {
throw new Error('Invalid protocol');
}
if (url.port && url.port !== '443') {
throw new Error('Invalid port');
}
if (bypassProxy || (this.config.proxyBypassHosts ?? []).includes(url.hostname)) {
return url.protocol === 'http:' ? this.http : this.https;
return this.https;
} else {
return url.protocol === 'http:' ? this.httpAgent : this.httpsAgent;
return this.httpsAgent;
}
}
@bindThis
public async getActivityJson(url: string): Promise<IObject> {
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObject> {
const res = await this.send(url, {
method: 'GET',
headers: {
@ -120,16 +193,22 @@ export class HttpRequestService {
},
timeout: 5000,
size: 1024 * 256,
isLocalAddressAllowed: isLocalAddressAllowed,
}, {
throwErrorWhenResponseNotOk: true,
validators: [validateContentTypeSetAsActivityPub],
});
return await res.json() as IObject;
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
assertActivityMatchesUrls(activity, [finalUrl]);
return activity;
}
@bindThis
public async getJson<T = unknown>(url: string, accept = 'application/json, */*', headers?: Record<string, string>): Promise<T> {
public async getJson<T = unknown>(url: string, accept = 'application/json, */*', headers?: Record<string, string>, isLocalAddressAllowed = false): Promise<T> {
const res = await this.send(url, {
method: 'GET',
headers: Object.assign({
@ -137,19 +216,21 @@ export class HttpRequestService {
}, headers ?? {}),
timeout: 5000,
size: 1024 * 256,
isLocalAddressAllowed: isLocalAddressAllowed,
});
return await res.json() as T;
}
@bindThis
public async getHtml(url: string, accept = 'text/html, */*', headers?: Record<string, string>): Promise<string> {
public async getHtml(url: string, accept = 'text/html, */*', headers?: Record<string, string>, isLocalAddressAllowed = false): Promise<string> {
const res = await this.send(url, {
method: 'GET',
headers: Object.assign({
Accept: accept,
}, headers ?? {}),
timeout: 5000,
isLocalAddressAllowed: isLocalAddressAllowed,
});
return await res.text();
@ -164,6 +245,7 @@ export class HttpRequestService {
headers?: Record<string, string>,
timeout?: number,
size?: number,
isLocalAddressAllowed?: boolean,
} = {},
extra: HttpRequestSendOptions = {
throwErrorWhenResponseNotOk: true,
@ -177,6 +259,16 @@ export class HttpRequestService {
controller.abort();
}, timeout);
const urlParsed = new URL(url);
if (urlParsed.protocol !== 'https:') {
throw new Error('Invalid protocol');
}
if (urlParsed.port && urlParsed.port !== '443') {
throw new Error('Invalid port');
}
const res = await fetch(url, {
method: args.method ?? 'GET',
headers: {
@ -185,7 +277,7 @@ export class HttpRequestService {
},
body: args.body,
size: args.size ?? 10 * 1024 * 1024,
agent: (url) => this.getAgentByUrl(url),
agent: (url) => this.getAgentByUrl(url, false),
signal: controller.signal,
});

View file

@ -46,7 +46,9 @@ import { bindThis } from '@/decorators.js';
import { Readable } from 'node:stream';
@Injectable()
export class ImageProcessingService {
// Prevent accidental import by upstream merge
// eslint-disable-next-line
export class __YUME_PRIVATE_ImageProcessingService {
constructor(
) {
}

View file

@ -7,6 +7,7 @@ import { setImmediate } from 'node:timers/promises';
import * as mfm from 'mfm-js';
import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis';
import * as Bull from 'bullmq';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
@ -56,6 +57,7 @@ import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { CacheService } from '@/core/CacheService.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -217,6 +219,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private instanceChart: InstanceChart,
private utilityService: UtilityService,
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
) {
this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount);
}
@ -291,7 +294,7 @@ export class NoteCreateService implements OnApplicationShutdown {
case 'followers':
// 他人のfollowers noteはreject
if (data.renote.userId !== user.id) {
throw new Error('Renote target is not public or home');
throw new Bull.UnrecoverableError('Renote target is not public or home');
}
// Renote対象がfollowersならfollowersにする
@ -299,7 +302,7 @@ export class NoteCreateService implements OnApplicationShutdown {
break;
case 'specified':
// specified / direct noteはreject
throw new Error('Renote target is not public or home');
throw new Bull.UnrecoverableError('Renote target is not public or home');
}
}
@ -543,13 +546,21 @@ export class NoteCreateService implements OnApplicationShutdown {
this.followingsRepository.findBy({
followeeId: user.id,
notify: 'normal',
}).then(followings => {
}).then(async followings => {
if (note.visibility !== 'specified') {
const isPureRenote = this.isRenote(data) && !this.isQuote(data) ? true : false;
for (const following of followings) {
// TODO: ワードミュート考慮
this.notificationService.createNotification(following.followerId, 'note', {
noteId: note.id,
}, user.id);
let isRenoteMuted = false;
if (isPureRenote) {
const userIdsWhoMeMutingRenotes = await this.cacheService.renoteMutingsCache.fetch(following.followerId);
isRenoteMuted = userIdsWhoMeMutingRenotes.has(user.id);
}
if (!isRenoteMuted) {
this.notificationService.createNotification(following.followerId, 'note', {
noteId: note.id,
}, user.id);
}
}
}
});

View file

@ -21,6 +21,13 @@ import type { Config } from '@/config.js';
import { UserListService } from '@/core/UserListService.js';
import type { FilterUnionByProperty } from '@/types.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { metricCounter } from '@/server/api/MetricsService.js';
const mNotificationsCreated = metricCounter({
name: 'misskey_notifications_created',
help: 'Notifications created',
labelNames: ['event_type'],
});
@Injectable()
export class NotificationService implements OnApplicationShutdown {
@ -165,6 +172,8 @@ export class NotificationService implements OnApplicationShutdown {
if (packed == null) return null;
mNotificationsCreated?.inc({ event_type: notification.type });
// Publish notification event
this.globalEventService.publishMainStream(notifieeId, 'notification', packed);

View file

@ -13,6 +13,19 @@ import { getNoteSummary } from '@/misc/get-note-summary.js';
import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { RedisKVCache } from '@/misc/cache.js';
import { metricCounter } from '@/server/api/MetricsService.js';
const mWebPushCreated = metricCounter({
name: 'misskey_webpush_created',
help: 'WebPush event',
labelNames: ['event_type'],
});
const mWebPushError = metricCounter({
name: 'misskey_webpush_error',
help: 'WebPush error',
labelNames: ['event_type', 'status'],
});
// Defined also packages/sw/types.ts#L13
type PushNotificationsTypes = {
@ -95,6 +108,8 @@ export class PushNotificationService implements OnApplicationShutdown {
},
};
mWebPushCreated?.inc({ event_type: type });
push.sendNotification(pushSubscription, JSON.stringify({
type,
body: (type === 'notification' || type === 'unreadAntennaNote') ? truncateBody(type, body) : body,
@ -116,6 +131,8 @@ export class PushNotificationService implements OnApplicationShutdown {
}).then(() => {
this.refreshCache(userId);
});
} else {
mWebPushError?.inc({ event_type: type, status: err.statusCode || 'unknown' });
}
});
}

View file

@ -18,6 +18,7 @@ import {
SystemWebhookDeliverJobData,
} from '../queue/types.js';
import type { Provider } from '@nestjs/common';
import { mActiveJobs, mDelayedJobs, mFailedJobs, mJobBlockedCounter, mWaitingJobs } from '@/queue/metrics.js';
export type SystemQueue = Bull.Queue<Record<string, unknown>>;
export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
@ -29,57 +30,74 @@ export type ObjectStorageQueue = Bull.Queue;
export type UserWebhookDeliverQueue = Bull.Queue<UserWebhookDeliverJobData>;
export type SystemWebhookDeliverQueue = Bull.Queue<SystemWebhookDeliverJobData>;
function withMetrics<T>(queue: Bull.Queue<T>): Bull.Queue<T> {
if (process.env.NODE_ENV !== 'test') {
setInterval(async () => {
mActiveJobs?.set({ queue: queue.name }, await queue.getActiveCount());
mDelayedJobs?.set({ queue: queue.name }, await queue.getDelayedCount());
mWaitingJobs?.set({ queue: queue.name }, await queue.getWaitingCount());
mFailedJobs?.set({ queue: queue.name }, await queue.getFailedCount());
}, 2000);
queue.on('waiting', () => {
mJobBlockedCounter?.inc({ queue: queue.name });
});
}
return queue;
}
const $system: Provider = {
provide: 'queue:system',
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM))),
inject: [DI.config],
};
const $endedPollNotification: Provider = {
provide: 'queue:endedPollNotification',
useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION))),
inject: [DI.config],
};
const $deliver: Provider = {
provide: 'queue:deliver',
useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER))),
inject: [DI.config],
};
const $inbox: Provider = {
provide: 'queue:inbox',
useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX))),
inject: [DI.config],
};
const $db: Provider = {
provide: 'queue:db',
useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB))),
inject: [DI.config],
};
const $relationship: Provider = {
provide: 'queue:relationship',
useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP))),
inject: [DI.config],
};
const $objectStorage: Provider = {
provide: 'queue:objectStorage',
useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE))),
inject: [DI.config],
};
const $userWebhookDeliver: Provider = {
provide: 'queue:userWebhookDeliver',
useFactory: (config: Config) => new Bull.Queue(QUEUE.USER_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.USER_WEBHOOK_DELIVER)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.USER_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.USER_WEBHOOK_DELIVER))),
inject: [DI.config],
};
const $systemWebhookDeliver: Provider = {
provide: 'queue:systemWebhookDeliver',
useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.SYSTEM_WEBHOOK_DELIVER)),
useFactory: (config: Config) => withMetrics(new Bull.Queue(QUEUE.SYSTEM_WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.SYSTEM_WEBHOOK_DELIVER))),
inject: [DI.config],
};

View file

@ -30,6 +30,9 @@ import { trackPromise } from '@/misc/promise-tracker.js';
import { isQuote, isRenote } from '@/misc/is-renote.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
import { UserWebhookPayload, UserWebhookService } from './UserWebhookService.js';
import { QueueService } from './QueueService.js';
import { Packed } from '@/misc/json-schema.js';
const FALLBACK = '\u2764';
@ -94,6 +97,8 @@ export class ReactionService {
private reactionsBufferingService: ReactionsBufferingService,
private idService: IdService,
private featuredService: FeaturedService,
private queueService: QueueService,
private webhookService: UserWebhookService,
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
private apDeliverManagerService: ApDeliverManagerService,
@ -254,12 +259,33 @@ export class ReactionService {
userId: user.id,
});
// リアクションされたユーザーがローカルユーザーなら通知を作成
// リアクションされたユーザーがローカルユーザーなら通知を作成してWebhookを送信
if (note.userHost === null) {
this.notificationService.createNotification(note.userId, 'reaction', {
noteId: note.id,
reaction: reaction,
}, user.id);
this.webhookService.getActiveWebhooks().then(async webhooks => {
webhooks = webhooks.filter(x => x.userId === note.userId && x.on.includes('reaction'));
if (webhooks.length === 0) return;
const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
const userObj = await this.userEntityService.pack(user.id, null, { schema: 'UserLite' });
const payload: UserWebhookPayload<'reaction'> = {
note: noteObj,
reaction: {
id: record.id,
user: userObj,
reaction: reaction,
},
};
for (const webhook of webhooks) {
this.queueService.userWebhookDeliver(webhook, 'reaction', payload);
}
});
}
//#region 配信

View file

@ -18,6 +18,7 @@ import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import { bindThis } from '@/decorators.js';
import { ApResolverService } from './activitypub/ApResolverService.js';
@Injectable()
export class RemoteUserResolveService {
@ -35,6 +36,7 @@ export class RemoteUserResolveService {
private remoteLoggerService: RemoteLoggerService,
private apDbResolverService: ApDbResolverService,
private apPersonService: ApPersonService,
private apResolverService: ApResolverService,
) {
this.logger = this.remoteLoggerService.logger.createSubLogger('resolve-user');
}
@ -91,7 +93,7 @@ export class RemoteUserResolveService {
}
this.logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
return await this.apPersonService.createPerson(self.href);
return await this.apPersonService.createPerson(self.href, this.apResolverService.createResolver());
}
// ユーザー情報が古い場合は、WebFingerからやりなおして返す

View file

@ -488,6 +488,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
return ids.length > 0
? await this.usersRepository.findBy({
id: In(ids),
isDeleted: false,
})
: [];
}

View file

@ -4,7 +4,7 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { Brackets, In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
@ -215,7 +215,10 @@ export class SearchService {
}
query
.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` })
.andWhere(new Brackets((qb => {
qb.where('note.text &@~ :q', { q: sqlLikeEscape(q) })
.orWhere('note.cw &@~ :q', { q: sqlLikeEscape(q) });
})))
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')

View file

@ -14,6 +14,14 @@ import type { OnApplicationShutdown } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'reaction' ? {
reaction: {
id: string,
user: Packed<'UserLite'>,
reaction: string,
}
note: Packed<'Note'>,
}:
T extends 'note' | 'reply' | 'renote' |'mention' ? {
note: Packed<'Note'>,
} :

View file

@ -7,19 +7,21 @@ import { Inject, Injectable } from '@nestjs/common';
import FFmpeg from 'fluent-ffmpeg';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import { __YUME_PRIVATE_ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { createTempDir } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
import { appendQuery, query } from '@/misc/prelude/url.js';
@Injectable()
export class VideoProcessingService {
// Prevent accidental import by upstream merge
// eslint-disable-next-line
export class __YUME_PRIVATE_VideoProcessingService {
constructor(
@Inject(DI.config)
private config: Config,
private imageProcessingService: ImageProcessingService,
private imageProcessingService: __YUME_PRIVATE_ImageProcessingService,
) {
}

View file

@ -253,6 +253,20 @@ function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailed
};
}
function generateDummyReactionPayload(note_override?: Partial<MiNote>): UserWebhookPayload<'reaction'> {
const dummyNote = generateDummyNote(note_override);
const dummyReaction = {
id: 'dummy-reaction-1',
user: toPackedUserLite(generateDummyUser()),
reaction: 'test_reaction',
};
return {
note: toPackedNote(dummyNote),
reaction: dummyReaction,
};
}
const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({
id: 'dummy-user-2',
@ -279,6 +293,10 @@ const dummyUser3 = generateDummyUser({
notesCount: 15900,
});
function wrapBodyEnum<T extends string, U>(tag: T, body: U): { [K in T]: U } {
return { [tag]: body } as { [K in T]: U };
}
@Injectable()
export class WebhookTestService {
public static NoSuchWebhookError = class extends Error {

View file

@ -16,6 +16,7 @@ import { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { getApId } from './type.js';
import { ApPersonService } from './models/ApPersonService.js';
import type { IObject } from './type.js';
import { toASCII } from 'node:punycode';
export type UriParseResult = {
/** wether the URI was generated by us */
@ -63,7 +64,9 @@ export class ApDbResolverService implements OnApplicationShutdown {
const separator = '/';
const uri = new URL(getApId(value));
if (uri.origin !== this.config.url) return { local: false, uri: uri.href };
if (toASCII(uri.host) !== toASCII(this.config.host)) {
return { local: false, uri: uri.href };
}
const [, type, id, ...rest] = uri.pathname.split(separator);
return {

View file

@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import * as prom from 'prom-client';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { UserFollowingService } from '@/core/UserFollowingService.js';
@ -28,7 +29,7 @@ import { bindThis } from '@/decorators.js';
import type { MiRemoteUser } from '@/models/User.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { AbuseReportService } from '@/core/AbuseReportService.js';
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { getApHrefNullable, getApId, getApIds, getApType, isActor, isCollection, isCollectionOrOrderedCollection, isPost, isTombstone, validActor, validPost, yumeDowncastAccept, yumeDowncastAdd, yumeDowncastAnnounce, yumeDowncastBlock, yumeDowncastCreate, yumeDowncastDelete, yumeDowncastFlag, yumeDowncastFollow, yumeDowncastLike, yumeDowncastMove, yumeDowncastReject, yumeDowncastRemove, yumeDowncastUndo, yumeDowncastUpdate } from './type.js';
import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js';
import { ApDbResolverService } from './ApDbResolverService.js';
@ -38,6 +39,13 @@ import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js';
import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
import { metricCounter } from '@/server/api/MetricsService.js';
const mInboxReceived = metricCounter({
name: 'misskey_ap_inbox_received_total',
help: 'Total number of activities received by AP inbox',
labelNames: ['host', 'type'],
});
@Injectable()
export class ApInboxService {
@ -130,37 +138,92 @@ export class ApInboxService {
public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
if (actor.isSuspended) return;
if (isCreate(activity)) {
return await this.create(actor, activity);
} else if (isDelete(activity)) {
return await this.delete(actor, activity);
} else if (isUpdate(activity)) {
return await this.update(actor, activity);
} else if (isFollow(activity)) {
return await this.follow(actor, activity);
} else if (isAccept(activity)) {
return await this.accept(actor, activity);
} else if (isReject(activity)) {
return await this.reject(actor, activity);
} else if (isAdd(activity)) {
return await this.add(actor, activity);
} else if (isRemove(activity)) {
return await this.remove(actor, activity);
} else if (isAnnounce(activity)) {
return await this.announce(actor, activity);
} else if (isLike(activity)) {
return await this.like(actor, activity);
} else if (isUndo(activity)) {
return await this.undo(actor, activity);
} else if (isBlock(activity)) {
return await this.block(actor, activity);
} else if (isFlag(activity)) {
return await this.flag(actor, activity);
} else if (isMove(activity)) {
return await this.move(actor, activity);
} else {
return `unrecognized activity type: ${activity.type}`;
const create = yumeDowncastCreate(activity);
if (create) {
mInboxReceived?.inc({ host: actor.host, type: 'create' });
return await this.create(actor, create);
}
const update = yumeDowncastUpdate(activity);
if (update) {
mInboxReceived?.inc({ host: actor.host, type: 'update' });
return await this.update(actor, update);
}
const del = yumeDowncastDelete(activity);
if (del) {
mInboxReceived?.inc({ host: actor.host, type: 'delete' });
return await this.delete(actor, del);
}
const follow = yumeDowncastFollow(activity);
if (follow) {
mInboxReceived?.inc({ host: actor.host, type: 'follow' });
return await this.follow(actor, follow);
}
const accept = yumeDowncastAccept(activity);
if (accept) {
mInboxReceived?.inc({ host: actor.host, type: 'accept' });
return await this.accept(actor, accept);
}
const reject = yumeDowncastReject(activity);
if (reject) {
mInboxReceived?.inc({ host: actor.host, type: 'reject' });
return await this.reject(actor, reject);
}
const add = yumeDowncastAdd(activity);
if (add) {
mInboxReceived?.inc({ host: actor.host, type: 'add' });
return await this.add(actor, add);
}
const remove = yumeDowncastRemove(activity);
if (remove) {
mInboxReceived?.inc({ host: actor.host, type: 'remove' });
return await this.remove(actor, remove);
}
const announce = yumeDowncastAnnounce(activity);
if (announce) {
mInboxReceived?.inc({ host: actor.host, type: 'announce' });
return await this.announce(actor, announce);
}
const like = yumeDowncastLike(activity);
if (like) {
mInboxReceived?.inc({ host: actor.host, type: 'like' });
return await this.like(actor, like);
}
const move = yumeDowncastMove(activity);
if (move) {
mInboxReceived?.inc({ host: actor.host, type: 'move' });
return await this.move(actor, move);
}
const undo = yumeDowncastUndo(activity);
if (undo) {
mInboxReceived?.inc({ host: actor.host, type: 'undo' });
return await this.undo(actor, undo);
}
const block = yumeDowncastBlock(activity);
if (block) {
mInboxReceived?.inc({ host: actor.host, type: 'block' });
return await this.block(actor, block);
}
const flag = yumeDowncastFlag(activity);
if (flag) {
mInboxReceived?.inc({ host: actor.host, type: 'flag' });
return await this.flag(actor, flag);
}
mInboxReceived?.inc({ host: actor.host, type: 'unknown' });
return `unrecognized activity type: ${activity.type}`;
}
@bindThis
@ -211,7 +274,8 @@ export class ApInboxService {
throw err;
});
if (isFollow(object)) return await this.acceptFollow(actor, object);
const follow = yumeDowncastFollow(object);
if (follow) return await this.acceptFollow(actor, follow);
return `skip: Unknown Accept type: ${getApType(object)}`;
}
@ -423,7 +487,7 @@ export class ApInboxService {
const exist = await this.apNoteService.fetchNote(note);
if (exist) return 'skip: note exists';
await this.apNoteService.createNote(note, resolver, silent);
await this.apNoteService.createNote(note, actor, resolver, silent);
return 'ok';
} catch (err) {
if (err instanceof StatusError && !err.isRetryable) {
@ -469,9 +533,9 @@ export class ApInboxService {
formerType = 'Note';
}
if (validPost.includes(formerType)) {
if (validPost?.includes(formerType)) {
return await this.deleteNote(actor, uri);
} else if (validActor.includes(formerType)) {
} else if (validActor?.includes(formerType)) {
return await this.deleteActor(actor, uri);
} else {
return `Unknown type ${formerType}`;
@ -486,19 +550,12 @@ export class ApInboxService {
return `skip: delete actor ${actor.uri} !== ${uri}`;
}
const user = await this.usersRepository.findOneBy({ id: actor.id });
if (user == null) {
return 'skip: actor not found';
} else if (user.isDeleted) {
if (!(await this.usersRepository.update({ id: actor.id, isDeleted: false }, { isDeleted: true })).affected) {
return 'skip: already deleted';
}
const job = await this.queueService.createDeleteAccountJob(actor);
await this.usersRepository.update(actor.id, {
isDeleted: true,
});
this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
return `ok: queued ${job.name} ${job.id}`;
@ -567,7 +624,8 @@ export class ApInboxService {
throw e;
});
if (isFollow(object)) return await this.rejectFollow(actor, object);
const follow = yumeDowncastFollow(object);
if (follow) return await this.rejectFollow(actor, follow);
return `skip: Unknown Reject type: ${getApType(object)}`;
}
@ -634,11 +692,20 @@ export class ApInboxService {
});
// don't queue because the sender may attempt again when timeout
if (isFollow(object)) return await this.undoFollow(actor, object);
if (isBlock(object)) return await this.undoBlock(actor, object);
if (isLike(object)) return await this.undoLike(actor, object);
if (isAnnounce(object)) return await this.undoAnnounce(actor, object);
if (isAccept(object)) return await this.undoAccept(actor, object);
const follow = yumeDowncastFollow(object);
if (follow) return await this.undoFollow(actor, follow);
const block = yumeDowncastBlock(object);
if (block) return await this.undoBlock(actor, block);
const like = yumeDowncastLike(object);
if (like) return await this.undoLike(actor, like);
const announce = yumeDowncastAnnounce(object);
if (announce) return await this.undoAnnounce(actor, announce);
const accept = yumeDowncastAccept(object);
if (accept) return await this.undoAccept(actor, accept);
return `skip: unknown object type ${getApType(object)}`;
}
@ -768,7 +835,7 @@ export class ApInboxService {
await this.apPersonService.updatePerson(actor.uri, resolver, object);
return 'ok: Person updated';
} else if (getApType(object) === 'Question') {
await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err));
await this.apQuestionService.updateQuestion(object, actor, resolver).catch(err => console.error(err));
return 'ok: Question updated';
} else {
return `skip: Unknown type: ${getApType(object)}`;

View file

@ -30,7 +30,7 @@ import { IdService } from '@/core/IdService.js';
import { JsonLdService } from './JsonLdService.js';
import { ApMfmService } from './ApMfmService.js';
import { CONTEXT } from './misc/contexts.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
import { markOutgoing, type IAccept, type IActivity, type IAdd, type IAnnounce, type IApDocument, type IApEmoji, type IApHashtag, type IApImage, type IApMention, type IBlock, type ICreate, type IDelete, type IFlag, type IFollow, type IKey, type ILike, type IMove, type IObject, type IPost, type IQuestion, type IReject, type IRemove, type ITombstone, type IUndo, type IUpdate } from './type.js';
@Injectable()
export class ApRendererService {
@ -66,21 +66,21 @@ export class ApRendererService {
@bindThis
public renderAccept(object: string | IObject, user: { id: MiUser['id']; host: null }): IAccept {
return {
return markOutgoing({
type: 'Accept',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
};
}, undefined);
}
@bindThis
public renderAdd(user: MiLocalUser, target: string | IObject | undefined, object: string | IObject): IAdd {
return {
return markOutgoing({
type: 'Add',
actor: this.userEntityService.genLocalUserUri(user.id),
target,
object,
};
}, undefined);
}
@bindThis
@ -103,7 +103,7 @@ export class ApRendererService {
throw new Error('renderAnnounce: cannot render non-public note');
}
return {
return markOutgoing({
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Announce',
@ -111,7 +111,7 @@ export class ApRendererService {
to,
cc,
object,
};
}, undefined);
}
/**
@ -125,23 +125,23 @@ export class ApRendererService {
throw new Error('renderBlock: missing blockee uri');
}
return {
return markOutgoing({
type: 'Block',
id: `${this.config.url}/blocks/${block.id}`,
actor: this.userEntityService.genLocalUserUri(block.blockerId),
object: block.blockee.uri,
};
}, undefined);
}
@bindThis
public renderCreate(object: IObject, note: MiNote): ICreate {
const activity: ICreate = {
const activity: ICreate = markOutgoing({
id: `${this.config.url}/notes/${note.id}/activity`,
actor: this.userEntityService.genLocalUserUri(note.userId),
type: 'Create',
published: this.idService.parse(note.id).date.toISOString(),
object,
};
}, undefined);
if (object.to) activity.to = object.to;
if (object.cc) activity.cc = object.cc;
@ -151,28 +151,28 @@ export class ApRendererService {
@bindThis
public renderDelete(object: IObject | string, user: { id: MiUser['id']; host: null }): IDelete {
return {
return markOutgoing({
type: 'Delete',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
published: new Date().toISOString(),
};
}, undefined);
}
@bindThis
public renderDocument(file: MiDriveFile): IApDocument {
return {
return markOutgoing({
type: 'Document',
mediaType: file.webpublicType ?? file.type,
url: this.driveFileEntityService.getPublicUrl(file),
name: file.comment,
sensitive: file.isSensitive,
};
}, undefined);
}
@bindThis
public renderEmoji(emoji: MiEmoji): IApEmoji {
return {
return markOutgoing( {
id: `${this.config.url}/emojis/${emoji.name}`,
type: 'Emoji',
name: `:${emoji.name}:`,
@ -183,28 +183,28 @@ export class ApRendererService {
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
},
};
}, undefined);
}
// to anonymise reporters, the reporting actor must be a system user
@bindThis
public renderFlag(user: MiLocalUser, object: IObject | string, content: string): IFlag {
return {
return markOutgoing({
type: 'Flag',
actor: this.userEntityService.genLocalUserUri(user.id),
content,
object,
};
}, undefined);
}
@bindThis
public renderFollowRelay(relay: MiRelay, relayActor: MiLocalUser): IFollow {
return {
return markOutgoing({
id: `${this.config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow',
actor: this.userEntityService.genLocalUserUri(relayActor.id),
object: 'https://www.w3.org/ns/activitystreams#Public',
};
}, undefined);
}
/**
@ -223,36 +223,36 @@ export class ApRendererService {
followee: MiPartialLocalUser | MiPartialRemoteUser,
requestId?: string,
): IFollow {
return {
return markOutgoing({
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow',
actor: this.userEntityService.getUserUri(follower),
object: this.userEntityService.getUserUri(followee),
};
}, undefined);
}
@bindThis
public renderHashtag(tag: string): IApHashtag {
return {
return markOutgoing({
type: 'Hashtag',
href: `${this.config.url}/tags/${encodeURIComponent(tag)}`,
name: `#${tag}`,
};
}, undefined);
}
@bindThis
public renderImage(file: MiDriveFile): IApImage {
return {
return markOutgoing({
type: 'Image',
url: this.driveFileEntityService.getPublicUrl(file),
sensitive: file.isSensitive,
name: file.comment,
};
}, undefined);
}
@bindThis
public renderKey(user: MiLocalUser, key: MiUserKeypair, postfix?: string): IKey {
return {
return markOutgoing({
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
type: 'Key',
owner: this.userEntityService.genLocalUserUri(user.id),
@ -260,21 +260,21 @@ export class ApRendererService {
type: 'spki',
format: 'pem',
}),
};
}, undefined);
}
@bindThis
public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise<ILike> {
const reaction = noteReaction.reaction;
const object: ILike = {
const object: ILike = markOutgoing({
type: 'Like',
id: `${this.config.url}/likes/${noteReaction.id}`,
actor: `${this.config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`,
content: reaction,
_misskey_reaction: reaction,
};
}, undefined);
if (reaction.startsWith(':')) {
const name = reaction.replaceAll(':', '');
@ -288,11 +288,11 @@ export class ApRendererService {
@bindThis
public renderMention(mention: MiPartialLocalUser | MiPartialRemoteUser): IApMention {
return {
return markOutgoing({
type: 'Mention',
href: this.userEntityService.getUserUri(mention),
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as MiLocalUser).username}`,
};
}, undefined);
}
@bindThis
@ -302,13 +302,13 @@ export class ApRendererService {
): IMove {
const actor = this.userEntityService.getUserUri(src);
const target = this.userEntityService.getUserUri(dst);
return {
return markOutgoing({
id: `${this.config.url}/moves/${src.id}/${dst.id}`,
actor,
type: 'Move',
object: actor,
target,
};
}, undefined);
}
@bindThis
@ -422,7 +422,7 @@ export class ApRendererService {
})),
} as const : {};
return {
return markOutgoing({
id: `${this.config.url}/notes/${note.id}`,
type: 'Note',
attributedTo,
@ -445,7 +445,7 @@ export class ApRendererService {
sensitive: note.cw != null || files.some(file => file.isSensitive),
tag,
...asPoll,
};
}, undefined);
}
@bindThis
@ -529,7 +529,7 @@ export class ApRendererService {
@bindThis
public renderQuestion(user: { id: MiUser['id'] }, note: MiNote, poll: MiPoll): IQuestion {
return {
return markOutgoing({
type: 'Question',
id: `${this.config.url}/questions/${note.id}`,
actor: this.userEntityService.genLocalUserUri(user.id),
@ -542,78 +542,78 @@ export class ApRendererService {
totalItems: poll.votes[i],
},
})),
};
}, 'question');
}
@bindThis
public renderReject(object: string | IObject, user: { id: MiUser['id'] }): IReject {
return {
return markOutgoing({
type: 'Reject',
actor: this.userEntityService.genLocalUserUri(user.id),
object,
};
}, undefined);
}
@bindThis
public renderRemove(user: { id: MiUser['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove {
return {
return markOutgoing({
type: 'Remove',
actor: this.userEntityService.genLocalUserUri(user.id),
target,
object,
};
}, undefined);
}
@bindThis
public renderTombstone(id: string): ITombstone {
return {
return markOutgoing({
id,
type: 'Tombstone',
};
}, undefined);
}
@bindThis
public renderUndo(object: string | IObject, user: { id: MiUser['id'] }): IUndo {
const id = typeof object !== 'string' && typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
return {
return markOutgoing({
type: 'Undo',
...(id ? { id } : {}),
actor: this.userEntityService.genLocalUserUri(user.id),
object,
published: new Date().toISOString(),
};
}, undefined);
}
@bindThis
public renderUpdate(object: string | IObject, user: { id: MiUser['id'] }): IUpdate {
return {
return markOutgoing( {
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Update',
to: ['https://www.w3.org/ns/activitystreams#Public'],
object,
published: new Date().toISOString(),
};
}, undefined);
}
@bindThis
public renderVote(user: { id: MiUser['id'] }, vote: MiPollVote, note: MiNote, poll: MiPoll, pollOwner: MiRemoteUser): ICreate {
return {
return markOutgoing({
id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
actor: this.userEntityService.genLocalUserUri(user.id),
type: 'Create',
to: [pollOwner.uri],
published: new Date().toISOString(),
object: {
object: markOutgoing({
id: `${this.config.url}/users/${user.id}#votes/${vote.id}`,
type: 'Note',
attributedTo: this.userEntityService.genLocalUserUri(user.id),
to: [pollOwner.uri],
inReplyTo: note.uri,
name: poll.choices[vote.choice],
},
};
}, undefined),
}, undefined);
}
@bindThis

View file

@ -11,11 +11,14 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { MiUser } from '@/models/User.js';
import { UserKeypairService } from '@/core/UserKeypairService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
import type { IObject } from './type.js';
type Request = {
url: string;
@ -145,6 +148,7 @@ export class ApRequestService {
private userKeypairService: UserKeypairService,
private httpRequestService: HttpRequestService,
private loggerService: LoggerService,
private utilityService: UtilityService,
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
this.logger = this.loggerService?.getLogger('ap-request'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
@ -251,7 +255,11 @@ export class ApRequestService {
//#endregion
validateContentTypeSetAsActivityPub(res);
const finalUrl = res.url; // redirects may have been involved
const activity = await res.json() as IObject;
return await res.json();
assertActivityMatchesUrls(activity, [url, finalUrl]);
return activity;
}
}

View file

@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { IsNull, Not } from 'typeorm';
import * as Bull from 'bullmq';
import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
@ -15,11 +16,13 @@ import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { LoggerService } from '@/core/LoggerService.js';
import type Logger from '@/logger.js';
import { isCollectionOrOrderedCollection } from './type.js';
import { isCollectionOrOrderedCollection, yumeNormalizeObject } from './type.js';
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import type { IObject, ICollection, IOrderedCollection, IUnsanitizedObject } from './type.js';
import { toASCII } from 'node:punycode';
import { yumeAssertAcceptableURL } from './misc/validator.js';
export class Resolver {
private history: Set<string>;
@ -52,6 +55,11 @@ export class Resolver {
return Array.from(this.history);
}
@bindThis
public getRecursionLimit(): number {
return this.recursionLimit;
}
@bindThis
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
const collection = typeof value === 'string'
@ -66,7 +74,7 @@ export class Resolver {
}
@bindThis
public async resolve(value: string | IObject): Promise<IObject> {
public async resolveNotNormalized(value: string | IObject): Promise<IUnsanitizedObject> {
if (typeof value !== 'string') {
return value;
}
@ -83,7 +91,7 @@ export class Resolver {
}
if (this.history.size > this.recursionLimit) {
throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
throw new Bull.UnrecoverableError(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
}
this.history.add(value);
@ -102,8 +110,8 @@ export class Resolver {
}
const object = (this.user
? await this.apRequestService.signedGet(value, this.user) as IObject
: await this.httpRequestService.getActivityJson(value)) as IObject;
? await this.apRequestService.signedGet(value, this.user) as IUnsanitizedObject
: await this.httpRequestService.getActivityJson(value)) as IUnsanitizedObject;
if (
Array.isArray(object['@context']) ?
@ -113,9 +121,31 @@ export class Resolver {
throw new Error('invalid response');
}
// HttpRequestService / ApRequestService have already checked that
// `object.id` or `object.url` matches the URL used to fetch the
// object after redirects; here we double-check that no redirects
// bounced between hosts
if (object.id == null) {
throw new Error('invalid AP object: missing id');
}
const idURL = yumeAssertAcceptableURL(object.id);
const valueURL = yumeAssertAcceptableURL(value);
if (toASCII(idURL.host) !== toASCII(valueURL.host)) {
throw new Bull.UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
}
return object;
}
@bindThis
public async resolve(value: string | IObject): Promise<IObject> {
const object = await this.resolveNotNormalized(value);
return yumeNormalizeObject(object);
}
@bindThis
private resolveLocal(url: string): Promise<IObject> {
const parsed = this.apDbResolverService.parseUri(url);

View file

@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: dakkar and sharkey-project and yumechi
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { metricCounter } from '@/server/api/MetricsService.js';
import type { IObject } from '../type.js';
const mFetchBadActivityUrl = metricCounter({
name: 'misskey_ap_fetch_bad_activity_url',
help: 'Fetches that failed because the activity URL did not match the expected Host',
labelNames: ['host_received', 'host_expected'],
});
function getHrefFrom(one: IObject|string): string | undefined {
if (typeof(one) === 'string') return one;
return one.href;
}
export function assertActivityMatchesUrls(activity: IObject, urls: string[]) {
const idOk = activity.id !== undefined && urls.includes(activity.id);
if (idOk) return;
const url = activity.url;
if (url) {
// `activity.url` can be an `ApObject = IObject | string | (IObject
// | string)[]`, we have to look inside it
const activityUrls = Array.isArray(url) ? url.map(getHrefFrom) : [getHrefFrom(url)];
const goodUrl = activityUrls.find(u => u && urls.includes(u));
if (goodUrl) return;
}
const hosts = urls.map(u => {
try {
return new URL(u).host;
} catch (e) {
return '[invalid]';
}
});
const sortDedup = (arr: string[]) => Array.from(new Set(arr)).sort();
mFetchBadActivityUrl?.inc({ host_received: sortDedup(hosts).join('|'), host_expected: sortDedup(urls).join('|') });
throw new Error(`bad Activity: neither id(${activity?.id}) nor url(${JSON.stringify(activity?.url)}) match location(${urls})`);
}

View file

@ -4,6 +4,28 @@
*/
import type { Response } from 'node-fetch';
import * as Bull from 'bullmq';
import { toASCII } from 'node:punycode';
export function yumeAssertAcceptableURL(url: string | URL): URL {
const urlParsed = url instanceof URL ? url : new URL(url);
if (urlParsed.search.length + urlParsed.pathname.length > 1024) {
throw new Bull.UnrecoverableError('URL is too long');
}
if (urlParsed.protocol !== 'https:') {
throw new Bull.UnrecoverableError('URL protocol is not https');
}
if (urlParsed.port && urlParsed.port !== '443') {
throw new Bull.UnrecoverableError('URL port is not 443');
}
urlParsed.hostname = toASCII(urlParsed.hostname);
return urlParsed;
}
export function validateContentTypeSetAsActivityPub(response: Response): void {
const contentType = (response.headers.get('content-type') ?? '').toLowerCase();

View file

@ -36,6 +36,7 @@ import { ApQuestionService } from './ApQuestionService.js';
import { ApImageService } from './ApImageService.js';
import type { Resolver } from '../ApResolverService.js';
import type { IObject, IPost } from '../type.js';
import { yumeAssertAcceptableURL } from '../misc/validator.js';
@Injectable()
export class ApNoteService {
@ -77,7 +78,7 @@ export class ApNoteService {
}
@bindThis
public validateNote(object: IObject, uri: string): Error | null {
public validateNote(object: IObject, uri: string, actor?: MiRemoteUser): Error | null {
const expectHost = this.utilityService.extractDbHost(uri);
const apType = getApType(object);
@ -98,6 +99,14 @@ export class ApNoteService {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
}
if (actor) {
const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri;
if (attribution !== actor.uri) {
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attribution does not match the actor that send it. attribution: ${attribution}, actor: ${actor.uri}`);
}
}
return null;
}
@ -115,14 +124,14 @@ export class ApNoteService {
* Noteを作成します
*/
@bindThis
public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<MiNote | null> {
public async createNote(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver, silent = false): Promise<MiNote | null> {
// eslint-disable-next-line no-param-reassign
if (resolver == null) resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(value);
const entryUri = getApId(value);
const err = this.validateNote(object, entryUri);
const err = this.validateNote(object, entryUri, actor);
if (err) {
this.logger.error(err.message, {
resolver: { history: resolver.getHistory() },
@ -136,14 +145,27 @@ export class ApNoteService {
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
if (note.id && !checkHttps(note.id)) {
if (note.id == null) {
throw new Error('Refusing to create note without id');
}
if (!checkHttps(note.id)) {
throw new Error('unexpected schema of note.id: ' + note.id);
}
const url = getOneApHrefNullable(note.url);
if (url && !checkHttps(url)) {
throw new Error('unexpected schema of note url: ' + url);
if (url != null) {
if (!checkHttps(url)) {
throw new Error('unexpected schema of note url: ' + url);
}
const actUrl = yumeAssertAcceptableURL(url);
const noteUrl = yumeAssertAcceptableURL(note.id);
if (noteUrl.host !== actUrl.host) {
throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
}
}
this.logger.info(`Creating the Note: ${note.id}`);
@ -156,8 +178,9 @@ export class ApNoteService {
const uri = getOneApId(note.attributedTo);
// ローカルで投稿者を検索し、もし凍結されていたらスキップ
const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser;
if (cachedActor && cachedActor.isSuspended) {
// eslint-disable-next-line no-param-reassign
actor ??= await this.apPersonService.fetchPerson(uri) as MiRemoteUser | undefined;
if (actor && actor.isSuspended) {
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
}
@ -189,7 +212,8 @@ export class ApNoteService {
}
//#endregion
const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
// eslint-disable-next-line no-param-reassign
actor ??= await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
// 解決した投稿者が凍結されていたらスキップ
if (actor.isSuspended) {
@ -348,15 +372,11 @@ export class ApNoteService {
if (exist) return exist;
//#endregion
if (uri.startsWith(this.config.url)) {
throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note');
}
// リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
const createFrom = options.sentFrom?.origin === new URL(uri).origin ? value : uri;
return await this.createNote(createFrom, options.resolver, true);
return await this.createNote(createFrom, undefined, options.resolver, true);
} finally {
unlock();
}

View file

@ -277,16 +277,13 @@ export class ApPersonService implements OnModuleInit {
* Personを作成します
*/
@bindThis
public async createPerson(uri: string, resolver?: Resolver): Promise<MiRemoteUser> {
public async createPerson(uri: string, resolver: Resolver): Promise<MiRemoteUser> {
if (typeof uri !== 'string') throw new Error('uri is not string');
if (uri.startsWith(this.config.url)) {
throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user');
}
// eslint-disable-next-line no-param-reassign
if (resolver == null) resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(uri);
if (object.id == null) throw new Error('invalid object.id: ' + object.id);
@ -557,7 +554,9 @@ export class ApPersonService implements OnModuleInit {
if (moving) updates.movedAt = new Date();
// Update user
await this.usersRepository.update(exist.id, updates);
if (!(await this.usersRepository.update({ id: exist.id, isDeleted: false }, updates)).affected) {
return 'skip';
}
if (person.publicKey) {
await this.userPublickeysRepository.update({ userId: exist.id }, {

View file

@ -5,16 +5,19 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { NotesRepository, PollsRepository } from '@/models/_.js';
import type { UsersRepository, NotesRepository, PollsRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { IPoll } from '@/models/Poll.js';
import type { MiRemoteUser } from '@/models/User.js';
import type Logger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { isQuestion } from '../type.js';
import { getOneApId, isQuestion } from '../type.js';
import { ApLoggerService } from '../ApLoggerService.js';
import { ApResolverService } from '../ApResolverService.js';
import type { Resolver } from '../ApResolverService.js';
import type { IObject, IQuestion } from '../type.js';
import type { IObject } from '../type.js';
import { yumeAssertAcceptableURL } from '../misc/validator.js';
import { toASCII } from 'punycode';
@Injectable()
export class ApQuestionService {
@ -24,6 +27,9 @@ export class ApQuestionService {
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
@ -65,28 +71,41 @@ export class ApQuestionService {
* @returns true if updated
*/
@bindThis
public async updateQuestion(value: string | IObject, resolver?: Resolver): Promise<boolean> {
const uri = typeof value === 'string' ? value : value.id;
if (uri == null) throw new Error('uri is null');
public async updateQuestion(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver): Promise<boolean> {
const uriIn = typeof value === 'string' ? value : value.id;
if (uriIn == null) throw new Error('uri is null');
// URIがこのサーバーを指しているならスキップ
if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local');
const uri = yumeAssertAcceptableURL(uriIn);
if (toASCII(this.config.host) === uri.host) throw new Error('uri points local');
//#region このサーバーに既に登録されているか
const note = await this.notesRepository.findOneBy({ uri });
const note = await this.notesRepository.findOneBy({ uri: uriIn });
if (note == null) throw new Error('Question is not registered');
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
if (poll == null) throw new Error('Question is not registered');
const user = await this.usersRepository.findOneBy({ id: poll.userId });
if (user == null) throw new Error('Question is not registered');
//#endregion
// resolve new Question object
// eslint-disable-next-line no-param-reassign
if (resolver == null) resolver = this.apResolverService.createResolver();
const question = await resolver.resolve(value) as IQuestion;
const question = await resolver.resolve(value);
this.logger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
if (question.type !== 'Question') throw new Error('object is not a Question');
if (!isQuestion(question)) throw new Error('object is not a Question');
const attribution = (question.attributedTo) ? getOneApId(question.attributedTo) : user.uri;
const attributionMatchesExisting = attribution === user.uri;
const actorMatchesAttribution = (actor) ? attribution === actor.uri : true;
if (!attributionMatchesExisting || !actorMatchesAttribution) {
throw new Error('Refusing to ingest update for poll by different user');
}
const apChoices = question.oneOf ?? question.anyOf;
if (apChoices == null) throw new Error('invalid apChoices: ' + apChoices);
@ -96,7 +115,7 @@ export class ApQuestionService {
for (const choice of poll.choices) {
const oldCount = poll.votes[poll.choices.indexOf(choice)];
const newCount = apChoices.filter(ap => ap.name === choice).at(0)?.replies?.totalItems;
if (newCount == null) throw new Error('invalid newCount: ' + newCount);
if (newCount == null || !(Number.isInteger(newCount) && newCount >= 0)) throw new Error('invalid newCount: ' + newCount);
if (oldCount !== newCount) {
changed = true;

View file

@ -3,20 +3,45 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { target } from "happy-dom/lib/PropertySymbol.js";
import { toASCII } from "node:punycode";
import * as bull from "bullmq";
export type Obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[];
export interface IObject {
export interface MisskeyVendorKeys {
_misskey_summary: string;
_misskey_followedMessage: string | null;
_misskey_requireSigninToViewContents: boolean;
_misskey_makeNotesFollowersOnlyBefore: number | null;
_misskey_makeNotesHiddenBefore: number | null;
_misskey_quote: string;
_misskey_content: string;
_misskey_reaction: string;
_misskey_votes: number;
}
function extractMisskeyVendorKeys(object: IObject): Partial<MisskeyVendorKeys> {
return {
_misskey_summary: object._misskey_summary,
_misskey_followedMessage: object._misskey_followedMessage,
_misskey_requireSigninToViewContents: object._misskey_requireSigninToViewContents,
_misskey_makeNotesFollowersOnlyBefore: object._misskey_makeNotesFollowersOnlyBefore,
_misskey_makeNotesHiddenBefore: object._misskey_makeNotesHiddenBefore,
_misskey_quote: object._misskey_quote,
_misskey_content: object._misskey_content,
_misskey_reaction: object._misskey_reaction,
_misskey_votes: object._misskey_votes,
};
}
export interface IUnsanitizedObject extends Partial<MisskeyVendorKeys> {
'@context'?: string | string[] | Obj | Obj[];
type: string | string[];
id?: string;
name?: string | null;
summary?: string;
_misskey_summary?: string;
_misskey_followedMessage?: string | null;
_misskey_requireSigninToViewContents?: boolean;
_misskey_makeNotesFollowersOnlyBefore?: number | null;
_misskey_makeNotesHiddenBefore?: number | null;
published?: string;
cc?: ApObject;
to?: ApObject;
@ -34,6 +59,73 @@ export interface IObject {
href?: string;
tag?: IObject | IObject[];
sensitive?: boolean;
visibility?: string;
mentionedUsers?: any[];
visibleUsers?: any[];
}
export interface IObject extends IUnsanitizedObject {
__yume_normalized_object: true | 'outgoing';
};
export interface YumeDowncastSanitizedBadge<L extends 'question' | undefined> {
__yume_normalized_badge: L | 'outgoing';
};
export function markOutgoing<T, L extends 'question' | undefined>(object: T, _badge: L): T & IObject & YumeDowncastSanitizedBadge<L> {
return object as T & IObject & YumeDowncastSanitizedBadge<L>;
}
export function yumeNormalizeURL(url: string): string {
const u = new URL(url);
u.host = toASCII(u.host);
if (u.protocol && u.protocol !== 'https:') {
throw new bull.UnrecoverableError('protocol is not https');
}
u.protocol = 'https:';
if (u.port && u.port !== '443') {
throw new bull.UnrecoverableError('port is not 443');
}
return u.toString();
}
export function yumeNormalizeRecursive<O extends IUnsanitizedObject | string | (IUnsanitizedObject | string)[]>(object: O, depth = 0):
IObject | string | (IObject | string)[] {
if (depth > 16) {
throw new bull.UnrecoverableError('recursion limit exceeded');
}
if (typeof object === 'string') {
return yumeNormalizeURL(object);
}
if (Array.isArray(object)) {
if (object.length > 64) {
throw new bull.UnrecoverableError('array length limit exceeded');
}
return object.flatMap(yumeNormalizeRecursive);
}
return yumeNormalizeObject(object);
}
export function yumeNormalizeObject(object: IUnsanitizedObject): IObject {
if (object.cc) {
object.cc = yumeNormalizeRecursive(object.cc);
}
if (object.id) {
object.id = yumeNormalizeURL(object.id);
}
if (object.url) {
object.url = yumeNormalizeRecursive(object.url);
}
if (object.inReplyTo) {
object.inReplyTo = yumeNormalizeRecursive(object.inReplyTo);
}
return object as IObject;
}
/**
@ -80,7 +172,7 @@ export function getOneApHrefNullable(value: ApObject | undefined): string | unde
}
export function getApHrefNullable(value: string | IObject | undefined): string | undefined {
if (typeof value === 'string') return value;
if (typeof value === 'string') return value;
if (typeof value?.href === 'string') return value.href;
return undefined;
}
@ -101,6 +193,28 @@ export interface IActivity extends IObject {
};
}
export interface SafeList {
id: string;
content: string | null;
tag: IObject | IObject[];
published: string;
visibility: string;
mentionedUsers: any[];
visibleUsers: any[];
}
function extractSafe(object: IObject): Partial<SafeList> {
return {
id: object.id,
content: object.content,
tag: object.tag,
published: object.published,
visibility: object.visibility,
mentionedUsers: object.mentionedUsers,
visibleUsers: object.visibleUsers,
};
}
export interface ICollection extends IObject {
type: 'Collection';
totalItems: number;
@ -122,7 +236,7 @@ export const isPost = (object: IObject): object is IPost => {
return type != null && validPost.includes(type);
};
export interface IPost extends IObject {
export interface IPost extends IObject{
type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event';
source?: {
content: string;
@ -133,7 +247,7 @@ export interface IPost extends IObject {
quoteUrl?: string;
}
export interface IQuestion extends IObject {
export interface IUnsanitizedQuestion extends IObject {
type: 'Note' | 'Question';
actor: string;
source?: {
@ -148,7 +262,25 @@ export interface IQuestion extends IObject {
closed?: Date;
}
export const isQuestion = (object: IObject): object is IQuestion =>
export interface IQuestion extends IUnsanitizedQuestion, YumeDowncastSanitizedBadge<'question'> {}
export function yumeSanitizeQuestion(object: IUnsanitizedQuestion): IQuestion {
return {
type: object.type,
actor: yumeNormalizeURL(object.actor),
source: object.source,
_misskey_quote: object._misskey_quote,
quoteUrl: object.quoteUrl ? yumeNormalizeURL(object.quoteUrl) : '',
oneOf: object.oneOf,
anyOf: object.anyOf,
endTime: object.endTime,
closed: object.closed,
__yume_normalized_object: true,
__yume_normalized_badge: 'question',
};
}
export const isQuestion = (object: IObject): object is IUnsanitizedQuestion =>
getApType(object) === 'Note' || getApType(object) === 'Question';
interface IQuestionChoice {
@ -264,88 +396,307 @@ export const isDocument = (object: IObject): object is IApDocument => {
return type != null && validDocumentTypes.includes(type);
};
export interface IApImage extends IApDocument {
export interface IApImage extends IApDocument, Partial<SafeList> {
type: 'Image';
}
export interface ICreate extends IActivity {
export interface ICreate extends IActivity, Partial<SafeList> {
type: 'Create';
}
export interface IDelete extends IActivity {
export interface IDelete extends IActivity, Partial<SafeList> {
type: 'Delete';
}
export interface IUpdate extends IActivity {
export interface IUpdate extends IActivity, Partial<SafeList> {
type: 'Update';
}
export interface IRead extends IActivity {
export interface IRead extends IActivity, Partial<SafeList> {
type: 'Read';
}
export interface IUndo extends IActivity {
export interface IUndo extends IActivity, Partial<SafeList> {
type: 'Undo';
}
export interface IFollow extends IActivity {
export interface IFollow extends IActivity, Partial<SafeList> {
type: 'Follow';
}
export interface IAccept extends IActivity {
export interface IAccept extends IActivity, Partial<SafeList> {
type: 'Accept';
}
export interface IReject extends IActivity {
export interface IReject extends IActivity, Partial<SafeList> {
type: 'Reject';
}
export interface IAdd extends IActivity {
export interface IAdd extends IActivity, Partial<SafeList> {
type: 'Add';
}
export interface IRemove extends IActivity {
export interface IRemove extends IActivity, Partial<SafeList> {
type: 'Remove';
}
export interface ILike extends IActivity {
export interface ILike extends IActivity, Partial<SafeList> {
type: 'Like' | 'EmojiReaction' | 'EmojiReact';
_misskey_reaction?: string;
}
export interface IAnnounce extends IActivity {
export interface IAnnounce extends IActivity, Partial<SafeList> {
type: 'Announce';
}
export interface IBlock extends IActivity {
export interface IBlock extends IActivity, Partial<SafeList> {
type: 'Block';
}
export interface IFlag extends IActivity {
export interface IFlag extends IActivity, Partial<SafeList> {
type: 'Flag';
}
export interface IMove extends IActivity {
export interface IMove extends IActivity, Partial<SafeList> {
type: 'Move';
target: IObject | string;
}
export const isCreate = (object: IObject): object is ICreate => getApType(object) === 'Create';
export const isDelete = (object: IObject): object is IDelete => getApType(object) === 'Delete';
export const isUpdate = (object: IObject): object is IUpdate => getApType(object) === 'Update';
export const isRead = (object: IObject): object is IRead => getApType(object) === 'Read';
export const isUndo = (object: IObject): object is IUndo => getApType(object) === 'Undo';
export const isFollow = (object: IObject): object is IFollow => getApType(object) === 'Follow';
export const isAccept = (object: IObject): object is IAccept => getApType(object) === 'Accept';
export const isReject = (object: IObject): object is IReject => getApType(object) === 'Reject';
export const isAdd = (object: IObject): object is IAdd => getApType(object) === 'Add';
export const isRemove = (object: IObject): object is IRemove => getApType(object) === 'Remove';
export const isLike = (object: IObject): object is ILike => {
const type = getApType(object);
return type != null && ['Like', 'EmojiReaction', 'EmojiReact'].includes(type);
};
export const isAnnounce = (object: IObject): object is IAnnounce => getApType(object) === 'Announce';
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
export const isNote = (object: IObject): object is IPost => getApType(object) === 'Note';
export function yumeDowncastCreate(object: IObject): ICreate | null {
if (getApType(object) !== 'Create') return null;
const obj = object as ICreate;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Create',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastDelete(object: IObject): IDelete | null {
if (getApType(object) !== 'Delete') return null;
const obj = object as IDelete;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Delete',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastUpdate(object: IObject): IUpdate | null {
if (getApType(object) !== 'Update') return null;
const obj = object as IUpdate;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Update',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastRead(object: IObject): IRead | null {
if (getApType(object) !== 'Read') return null;
const obj = object as IRead;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Read',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastUndo(object: IObject): IUndo | null {
if (getApType(object) !== 'Undo') return null;
const obj = object as IUndo;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Undo',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastFollow(object: IObject): IFollow | null {
if (getApType(object) !== 'Follow') return null;
const obj = object as IFollow;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Follow',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastAccept(object: IObject): IAccept | null {
if (getApType(object) !== 'Accept') return null;
const obj = object as IAccept;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Accept',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastReject(object: IObject): IReject | null {
if (getApType(object) !== 'Reject') return null;
const obj = object as IReject;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Reject',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastAdd(object: IObject): IAdd | null {
if (getApType(object) !== 'Add') return null;
const obj = object as IAdd;
if (!obj.actor || !obj.object ) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Add',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastRemove(object: IObject): IRemove | null {
if (getApType(object) !== 'Remove') return null;
const obj = object as IRemove;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Remove',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastLike(object: IObject): ILike | null {
if (getApType(object) !== 'Like') return null;
const obj = object as ILike;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Like',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastAnnounce(object: IObject): IAnnounce | null {
if (getApType(object) !== 'Announce') return null;
const obj = object as IAnnounce;
if (!obj.actor || !obj.object) return null;
return {
// ...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Announce',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastBlock(object: IObject): IBlock | null {
if (getApType(object) !== 'Block') return null;
const obj = object as IBlock;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Block',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastFlag(object: IObject): IFlag | null {
if (getApType(object) !== 'Flag') return null;
const obj = object as IFlag;
if (!obj.actor || !obj.object) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Flag',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: obj.target ? (typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target)) : undefined,
__yume_normalized_object: true,
};
}
export function yumeDowncastMove(object: IObject): IMove | null {
if (getApType(object) !== 'Move') return null;
const obj = object as IMove;
if (!obj.actor || !obj.object || !obj.target) return null;
return {
...extractMisskeyVendorKeys(object),
...extractSafe(object),
type: 'Move',
actor: typeof obj.actor === 'string' ? yumeNormalizeURL(obj.actor) : yumeNormalizeObject(obj.actor),
object: typeof obj.object === 'string' ? yumeNormalizeURL(obj.object) : yumeNormalizeObject(obj.object),
target: typeof obj.target === 'string' ? yumeNormalizeURL(obj.target) : yumeNormalizeObject(obj.target),
__yume_normalized_object: true,
};
}
export function yumeDowncastMention(object: IObject): IApMention | null {
if (getApType(object) !== 'Mention') {
return null;
}
const href = getApHrefNullable(object);
return {
...object,
type: 'Mention',
href: href ? yumeNormalizeURL(href) : '',
name: object.name ?? '',
};
}

View file

@ -18,7 +18,6 @@ import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { IdService } from '@/core/IdService.js';
import { UtilityService } from '../UtilityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFolderEntityService } from './DriveFolderEntityService.js';
@ -43,7 +42,6 @@ export class DriveFileEntityService {
private utilityService: UtilityService,
private driveFolderEntityService: DriveFolderEntityService,
private videoProcessingService: VideoProcessingService,
private idService: IdService,
) {
}
@ -86,11 +84,7 @@ export class DriveFileEntityService {
@bindThis
public getThumbnailUrl(file: MiDriveFile): string | null {
if (file.type.startsWith('video')) {
if (file.thumbnailUrl) return file.thumbnailUrl;
return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url);
} else if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
// 動画ではなくリモートかつメディアプロキシ
return this.getProxiedUrl(file.uri, 'static');
}

View file

@ -30,7 +30,8 @@ export class DriveFolderEntityService {
public async pack(
src: MiDriveFolder['id'] | MiDriveFolder,
options?: {
detail: boolean
detail: boolean,
maxDepth?: number,
},
): Promise<Packed<'DriveFolder'>> {
const opts = Object.assign({
@ -55,7 +56,8 @@ export class DriveFolderEntityService {
...(folder.parentId ? {
parent: this.pack(folder.parentId, {
detail: true,
detail: (options?.maxDepth ? options.maxDepth > 0 : true),
maxDepth: options?.maxDepth || 32,
}),
} : {}),
} : {}),

View file

@ -10,46 +10,85 @@ import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
import type { MiEmoji } from '@/models/Emoji.js';
import { bindThis } from '@/decorators.js';
import { In } from 'typeorm';
import type { Config } from '@/config.js';
@Injectable()
export class EmojiEntityService {
constructor(
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,
@Inject(DI.config)
private config: Config,
) {
}
private stripProxyIfOrigin(url: string): string {
try {
const u = new URL(url);
let origin = u.origin;
if (u.origin === new URL(this.config.mediaProxy).origin) {
const innerUrl = u.searchParams.get('url');
if (innerUrl) {
origin = new URL(innerUrl).origin;
}
}
if (origin === u.origin) {
return url;
}
} catch (e) {
return url;
}
return url;
}
@bindThis
public packSimpleNoQuery(
emoji: MiEmoji,
): Packed<'EmojiSimple'> {
return {
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: this.stripProxyIfOrigin(emoji.publicUrl || emoji.originalUrl),
localOnly: emoji.localOnly ? true : undefined,
isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
};
}
@bindThis
public async packSimple(
src: MiEmoji['id'] | MiEmoji,
): Promise<Packed<'EmojiSimple'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return {
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
localOnly: emoji.localOnly ? true : undefined,
isSensitive: emoji.isSensitive ? true : undefined,
roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
};
return this.packSimpleNoQuery(emoji);
}
@bindThis
public packSimpleMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packSimple(x)));
public async packSimpleMany(
emojis: MiEmoji['id'][] | MiEmoji[],
): Promise<Packed<'EmojiSimple'>[]> {
if (emojis.length === 0) {
return [];
}
if (typeof emojis[0] === 'string') {
const res = await this.emojisRepository.findBy({ id: In(emojis as MiEmoji['id'][]) });
return res.map(this.packSimpleNoQuery);
}
return (emojis as MiEmoji[]).map(this.packSimpleNoQuery);
}
@bindThis
public async packDetailed(
src: MiEmoji['id'] | MiEmoji,
): Promise<Packed<'EmojiDetailed'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
public packDetailedNoQuery(
emoji: MiEmoji,
): Packed<'EmojiDetailed'> {
return {
id: emoji.id,
aliases: emoji.aliases,
@ -57,7 +96,7 @@ export class EmojiEntityService {
category: emoji.category,
host: emoji.host,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
url: this.stripProxyIfOrigin(emoji.publicUrl || emoji.originalUrl),
license: emoji.license,
isSensitive: emoji.isSensitive,
localOnly: emoji.localOnly,
@ -66,10 +105,28 @@ export class EmojiEntityService {
}
@bindThis
public packDetailedMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packDetailed(x)));
public async packDetailed(
src: MiEmoji['id'] | MiEmoji,
): Promise<Packed<'EmojiDetailed'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return this.packDetailedNoQuery(emoji);
}
@bindThis
public async packDetailedMany(
emojis: MiEmoji['id'][] | MiEmoji[],
) : Promise<Packed<'EmojiDetailed'>[]> {
if (emojis.length === 0) {
return [];
}
if (typeof emojis[0] === 'string') {
const res = await this.emojisRepository.findBy({ id: In(emojis as MiEmoji['id'][]) });
return res.map(this.packDetailedNoQuery);
}
return (emojis as MiEmoji[]).map(this.packDetailedNoQuery);
}
}

View file

@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project and yumechi
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { aidRegExp } from "./id/aid.js";
import { aidxRegExp } from "./id/aidx.js";
export function sanitizeRequestURI(uri: string): string {
const vite = /^\/vite\/.+\.([a-z0-9]{1,4})$/;
const embed_vite = /^\/embed_vite\/.+\.([a-z0-9]{1,4})$/;
if (vite.test(uri)) {
return '[vite]';
}
if (embed_vite.test(uri)) {
return '[embed_vite]';
}
if (uri.startsWith('/emoji/')) {
return '/emoji/[emoji]';
}
if (uri.startsWith('/identicon/')) {
return '/identicon/[identicon]';
}
if (uri.startsWith('/tags/')) {
return '/tags/[tag]';
}
if (uri.startsWith('/user-tags/')) {
return '/user-tags/[tag]';
}
if (uri.startsWith('/page/')) {
return '/page/[page]';
}
if (uri.startsWith('/fluent-emoji/')) {
return '/fluent-emoji/[fluent-emoji]';
}
if (uri.startsWith('/twemoji/')) {
return '/twemoji/[twemoji]';
}
if (uri.startsWith('/twemoji-badge/')) {
return '/twemoji-badge/[twemoji-badge]';
}
if (!uri.startsWith('/api/')) {
return '[other]';
}
const uuid = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/g;
const username_local = /\/@\w+(\/|$)/;
const username_remote = /\/@\w+@[a-zA-Z0-9-.]+\.[a-zA-Z]{2,4}(\/|$)/;
const token = /=[0-9a-zA-Z]{16}/g;
const aidx = new RegExp(`/${aidxRegExp.source.replace(/^\^/, '').replace(/\$$/, '')}(\/|$)`, 'g');
const aid = new RegExp(`/${aidRegExp.source.replace(/^\^/, '').replace(/\$$/, '')}(\/|$)`, 'g');
return uri
.replace(aidx, '/[aidx]/')
.replace(aid, '/[aid]/')
.replace(token, '=[token]')
.replace(uuid, '[uuid]')
.replace(username_local, '/[user_local]/')
.replace(username_remote, '/[user_remote]/');
}

View file

@ -61,7 +61,7 @@ export class MiDriveFile {
public size: number;
@Column('varchar', {
length: 512, nullable: true,
length: 32768, nullable: true,
comment: 'The comment of the DriveFile.',
})
public comment: string | null;

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne, ViewEntity } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';
@ -98,3 +98,4 @@ export class MiFollowing {
public followeeSharedInbox: string | null;
//#endregion
}

View file

@ -50,6 +50,7 @@ export class MiNote {
public threadId: string | null;
// TODO: varcharにしたい
@Index() // USING pgroonga
@Column('text', {
nullable: true,
})
@ -60,6 +61,7 @@ export class MiNote {
})
public name: string | null;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column('varchar', {
length: 512, nullable: true,
})

View file

@ -49,6 +49,7 @@ export class MiUser {
})
public usernameLower: string;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column('varchar', {
length: 128, nullable: true,
comment: 'The name of the User.',

View file

@ -36,6 +36,7 @@ export class MiUserProfile {
})
public birthday: string | null;
@Index() // USING pgroonga pgroonga_varchar_full_text_search_ops_v2
@Column('varchar', {
length: 2048, nullable: true,
comment: 'The description (bio) of the User.',

View file

@ -7,6 +7,7 @@
import pg from 'pg';
import { DataSource, Logger } from 'typeorm';
import * as highlight from 'cli-highlight';
import { createHash } from 'crypto';
import { entities as charts } from '@/core/chart/entities.js';
import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
@ -82,6 +83,8 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
import { Config } from '@/config.js';
import MisskeyLogger from '@/logger.js';
import { bindThis } from '@/decorators.js';
import { MemoryKVCache } from './misc/cache.js';
import { metricCounter, metricHistogram } from './server/api/MetricsService.js';
pg.types.setTypeParser(20, Number);
@ -89,7 +92,56 @@ export const dbLogger = new MisskeyLogger('db');
const sqlLogger = dbLogger.createSubLogger('sql', 'gray');
type QueryTagCache = {
join: string;
from: string;
};
function dedupConsecutive<T>(arr: T[]): T[] {
return arr.filter((v, i, a) => i === 0 || a[i - 1] !== v);
}
function simplifyIdentifiers(sql: string) {
return sql.replace(/"([a-zA-Z_]+)"/g, '$1');
}
function extractQueryTags(query: string): QueryTagCache {
const joins = query.matchAll(/(LEFT|RIGHT|INNER|OUTER)[\s\S]+JOIN[\s\r\n]+([a-zA-Z0-9_"`.]+)/ig);
const froms = query.matchAll(/FROM[\s\r\n]+([a-zA-Z0-9_"`.]+)/ig);
const join = Array.from(joins).map(j => `${j[1]}:${simplifyIdentifiers(j[2])}`).join('|');
const from = dedupConsecutive(Array.from(froms).map(f => simplifyIdentifiers(f[1]))).join('|');
return {
join,
from,
};
}
const mQueryCounter = metricCounter({
name: 'misskey_postgres_query_total',
help: 'Total queries to postgres',
labelNames: ['join', 'from'],
});
const mQueryErrorCounter = metricCounter({
name: 'misskey_postgres_query_error_total',
help: 'Total errors in queries to postgres',
labelNames: ['join', 'from'],
});
const mSlowQueryHisto = metricHistogram({
name: 'misskey_postgres_query_slow_duration_seconds',
help: 'Duration of slow queries to postgres',
labelNames: ['join', 'from'],
buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 300],
});
class MyCustomLogger implements Logger {
constructor(private metricOnly = true) {}
private queryHashCache = new MemoryKVCache<QueryTagCache>(1000 * 60 * 5); // 5m
@bindThis
private highlight(sql: string) {
return highlight.highlight(sql, {
@ -97,33 +149,69 @@ class MyCustomLogger implements Logger {
});
}
@bindThis
private getQueryTags(query: string): QueryTagCache {
const existing = this.queryHashCache.get(query);
if (existing) {
return existing;
}
const result = extractQueryTags(query);
this.queryHashCache.set(query, result);
return result;
}
@bindThis
public logQuery(query: string, parameters?: any[]) {
mQueryCounter?.inc(this.getQueryTags(query));
if (this.metricOnly) {
return;
}
sqlLogger.info(this.highlight(query).substring(0, 100));
}
@bindThis
public logQueryError(error: string, query: string, parameters?: any[]) {
mQueryErrorCounter?.inc(this.getQueryTags(query));
if (this.metricOnly) {
return;
}
sqlLogger.error(this.highlight(query));
}
@bindThis
public logQuerySlow(time: number, query: string, parameters?: any[]) {
mSlowQueryHisto?.observe(this.getQueryTags(query), time);
if (this.metricOnly) {
return;
}
sqlLogger.warn(this.highlight(query));
}
@bindThis
public logSchemaBuild(message: string) {
if (this.metricOnly) {
return;
}
sqlLogger.info(message);
}
@bindThis
public log(message: string) {
if (this.metricOnly) {
return;
}
sqlLogger.info(message);
}
@bindThis
public logMigration(message: string) {
if (this.metricOnly) {
return;
}
sqlLogger.info(message);
}
}
@ -203,7 +291,7 @@ export const entities = [
const log = process.env.NODE_ENV !== 'production';
export function createPostgresDataSource(config: Config) {
export function createPostgresDataSource(config: Config, isMain = false) {
return new DataSource({
type: 'postgres',
host: config.db.host,
@ -246,9 +334,9 @@ export function createPostgresDataSource(config: Config) {
db: config.redis.db ?? 0,
},
} : false,
logging: log,
logger: log ? new MyCustomLogger() : undefined,
maxQueryExecutionTime: 300,
logging: true,
logger: new MyCustomLogger(!log),
maxQueryExecutionTime: 500,
entities: entities,
migrations: ['../../migration/*.js'],
});

View file

@ -45,6 +45,7 @@ import { CleanProcessorService } from './processors/CleanProcessorService.js';
import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
import { QueueLoggerService } from './QueueLoggerService.js';
import { QUEUE, baseQueueOptions } from './const.js';
import { mStalledWorkerCounter } from './metrics.js';
// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
function httpRelatedBackoff(attemptsMade: number) {
@ -194,7 +195,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.SYSTEM });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -251,7 +255,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.DB });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -291,7 +298,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.DELIVER });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -331,7 +341,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.INBOX });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -371,7 +384,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.USER_WEBHOOK_DELIVER });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -411,7 +427,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.SYSTEM_WEBHOOK_DELIVER });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -458,7 +477,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.RELATIONSHIP });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion
@ -499,7 +521,10 @@ export class QueueProcessorService implements OnApplicationShutdown {
}
})
.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
.on('stalled', (jobId) => {
mStalledWorkerCounter?.inc({ queue: QUEUE.OBJECT_STORAGE });
logger.warn(`stalled id=${jobId}`);
});
}
//#endregion

View file

@ -0,0 +1,37 @@
import { metricCounter, metricGauge } from '@/server/api/MetricsService.js';
export const mJobBlockedCounter = metricCounter({
name: 'misskey_queue_jobs_blocked_total',
help: 'Total number of jobs waiting for a worker',
labelNames: ['queue'],
});
export const mActiveJobs = metricGauge({
name: 'misskey_queue_active_jobs',
help: 'Number of active jobs in queue',
labelNames: ['queue'],
});
export const mDelayedJobs = metricGauge({
name: 'misskey_queue_delayed_jobs',
help: 'Number of delayed jobs in queue',
labelNames: ['queue'],
});
export const mWaitingJobs = metricGauge({
name: 'misskey_queue_waiting_jobs',
help: 'Number of waiting jobs in queue',
labelNames: ['queue'],
});
export const mFailedJobs = metricGauge({
name: 'misskey_queue_failed_jobs',
help: 'Total number of failed jobs',
labelNames: ['queue'],
});
export const mStalledWorkerCounter = metricCounter({
name: 'misskey_queue_stalled_workers_total',
help: 'Total number of stalled workers',
labelNames: ['queue'],
});

View file

@ -4,9 +4,9 @@
*/
import { Inject, Injectable } from '@nestjs/common';
import { MoreThan } from 'typeorm';
import { DataSource, MoreThan, QueryFailedError, TypeORMError } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { MiUser, type DriveFilesRepository, type NotesRepository, type UserProfilesRepository, type UsersRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
import { DriveService } from '@/core/DriveService.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
@ -26,6 +26,9 @@ export class DeleteAccountProcessorService {
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.db)
private db: DataSource,
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
@ -52,6 +55,14 @@ export class DeleteAccountProcessorService {
return;
}
if (!user.isDeleted) {
this.logger.warn('User is not pre-marked as deleted, this is likely a bug');
if (process.env.NODE_ENV !== 'production') {
throw new Error('User is not pre-marked as deleted'); // make some noise to make sure tests fail
}
await this.usersRepository.update({ id: user.id }, { isDeleted: true });
}
{ // Delete notes
let cursor: MiNote['id'] | null = null;
@ -121,13 +132,46 @@ export class DeleteAccountProcessorService {
}
}
// soft指定されている場合は物理削除しない
if (job.data.soft) {
// nop
} else {
await this.usersRepository.delete(job.data.user.id);
// Deadlockが発生した場合にリトライする
for (let remaining = 3; remaining > 0; remaining--) {
try {
// soft指定されている場合は物理削除しない
await this.db.transaction(async txn => {
// soft指定してもデータをすべで削除する
await txn.delete(MiUser, user.id);
if (job.data.soft) {
await txn.insert(MiUser, {
...user,
isRoot: false,
updatedAt: new Date(),
emojis: [],
hideOnlineStatus: true,
followersCount: 0,
followingCount: 0,
avatarUrl: null,
avatarId: null,
notesCount: 0,
inbox: null,
sharedInbox: null,
featured: null,
uri: null,
followersUri: null,
token: null,
isDeleted: true,
});
}
});
return 'Account deleted';
} catch (e) {
// 40P01 = deadlock_detected
// https://www.postgresql.org/docs/current/errcodes-appendix.html
if (remaining > 0 && e instanceof QueryFailedError && e.driverError.code === '40P01') {
this.logger.warn(`Deadlock occurred, retrying after 1s... [${remaining - 1} remaining]`);
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
throw e;
}
}
return 'Account deleted';
}
}

View file

@ -7,6 +7,7 @@ import { URL } from 'node:url';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import httpSignature from '@peertube/http-signature';
import * as Bull from 'bullmq';
import * as prom from 'prom-client';
import type Logger from '@/logger.js';
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
@ -29,6 +30,7 @@ import { CollapsedQueue } from '@/misc/collapsed-queue.js';
import { MiNote } from '@/models/Note.js';
import { MiMeta } from '@/models/Meta.js';
import { DI } from '@/di-symbols.js';
import { metricCounter, metricHistogram } from '@/server/api/MetricsService.js';
import { QueueLoggerService } from '../QueueLoggerService.js';
import type { InboxJobData } from '../types.js';
@ -37,6 +39,37 @@ type UpdateInstanceJob = {
shouldUnsuspend: boolean,
};
const mIncomingApProcessingTime = metricHistogram({
name: 'misskey_incoming_ap_processing_time',
help: 'Incoming AP processing time in seconds',
labelNames: ['incoming_host', 'incoming_type', 'success'],
buckets: [1, 10, 60, 300, 1800],
});
const mIncomingApEvent = metricCounter({
name: 'misskey_incoming_ap_event',
help: 'Incoming AP event',
labelNames: ['incoming_host', 'incoming_type'],
});
const mIncomingApEventAccepted = metricCounter({
name: 'misskey_incoming_ap_event_accepted',
help: 'Incoming AP event accepted',
labelNames: ['incoming_host', 'incoming_type'],
});
const mIncomingApReject = metricCounter({
name: 'misskey_incoming_ap_reject',
help: 'Incoming AP reject',
labelNames: ['incoming_host', 'incoming_type', 'reason'],
});
const mincomingApProcessingError = metricCounter({
name: 'misskey_incoming_ap_processing_error',
help: 'Incoming AP processing error',
labelNames: ['incoming_host', 'incoming_type'],
});
@Injectable()
export class InboxProcessorService implements OnApplicationShutdown {
private logger: Logger;
@ -66,7 +99,6 @@ export class InboxProcessorService implements OnApplicationShutdown {
public async process(job: Bull.Job<InboxJobData>): Promise<string> {
const signature = job.data.signature; // HTTP-signature
let activity = job.data.activity;
//#region Log
const info = Object.assign({}, activity);
delete info['@context'];
@ -75,12 +107,34 @@ export class InboxProcessorService implements OnApplicationShutdown {
const host = this.utilityService.toPuny(new URL(signature.keyId).hostname);
const incCounter = <T extends 'incoming_host' | 'incoming_type', U>(counter: prom.Counter<T> | null, addn_labels: U) => {
if (Array.isArray(activity.type)) {
for (const t of activity.type) {
counter?.inc({ incoming_host: host.toString(), incoming_type: t, ...addn_labels });
}
} else {
counter?.inc({ incoming_host: host.toString(), incoming_type: activity.type ?? 'unknown', ...addn_labels });
}
};
const observeHistogram = <T extends 'incoming_host' | 'incoming_type', U>(histogram: prom.Histogram<T> | null, addn_labels: U, value: number) => {
if (Array.isArray(activity.type)) {
for (const t of activity.type) {
histogram?.observe({ incoming_host: host.toString(), incoming_type: t, ...addn_labels }, value);
}
} else {
histogram?.observe({ incoming_host: host.toString(), incoming_type: activity.type ?? 'unknown', ...addn_labels }, value);
}
};
if (!this.utilityService.isFederationAllowedHost(host)) {
incCounter(mIncomingApReject, { reason: 'host_not_allowed' });
return `Blocked request: ${host}`;
}
const keyIdLower = signature.keyId.toLowerCase();
if (keyIdLower.startsWith('acct:')) {
incCounter(mIncomingApReject, { reason: 'keyid_acct' });
return `Old keyId is no longer supported. ${keyIdLower}`;
}
@ -100,6 +154,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
if (!err.isRetryable) {
throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
}
incCounter(mIncomingApReject, { reason: 'actor_key_unresolvable' });
throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`);
}
}
@ -107,11 +162,13 @@ export class InboxProcessorService implements OnApplicationShutdown {
// それでもわからなければ終了
if (authUser == null) {
incCounter(mIncomingApReject, { reason: 'actor_unresolvable' });
throw new Bull.UnrecoverableError('skip: failed to resolve user');
}
// publicKey がなくても終了
if (authUser.key == null) {
incCounter(mIncomingApReject, { reason: 'publickey_unresolvable' });
throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
}
@ -124,6 +181,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
const ldSignature = activity.signature;
if (ldSignature) {
if (ldSignature.type !== 'RsaSignature2017') {
incCounter(mIncomingApReject, { reason: 'ld_signature_unsupported' });
throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${ldSignature.type}`);
}
@ -137,10 +195,12 @@ export class InboxProcessorService implements OnApplicationShutdown {
// keyIdからLD-Signatureのユーザーを取得
authUser = await this.apDbResolverService.getAuthUserFromKeyId(ldSignature.creator);
if (authUser == null) {
incCounter(mIncomingApReject, { reason: 'ld_signature_user_unresolvable' });
throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
}
if (authUser.key == null) {
incCounter(mIncomingApReject, { reason: 'ld_signature_publickey_unavailable' });
throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
}
@ -149,6 +209,7 @@ export class InboxProcessorService implements OnApplicationShutdown {
// LD-Signature検証
const verified = await jsonLd.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
if (!verified) {
incCounter(mIncomingApReject, { reason: 'ld_signature_verification_failed' });
throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
}
@ -171,14 +232,17 @@ export class InboxProcessorService implements OnApplicationShutdown {
// もう一度actorチェック
if (authUser.user.uri !== activity.actor) {
incCounter(mIncomingApReject, { reason: 'ld_signature_actor_mismatch' });
throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
}
const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
if (!this.utilityService.isFederationAllowedHost(ldHost)) {
incCounter(mIncomingApReject, { reason: 'fed_host_not_allowed' });
throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
}
} else {
incCounter(mIncomingApReject, { reason: 'ld_signature_unavailable' });
throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`);
}
}
@ -188,8 +252,11 @@ export class InboxProcessorService implements OnApplicationShutdown {
const signerHost = this.utilityService.extractDbHost(authUser.user.uri!);
const activityIdHost = this.utilityService.extractDbHost(activity.id);
if (signerHost !== activityIdHost) {
incCounter(mIncomingApReject, 'host_signature_mismatch');
throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`);
}
} else {
throw new Bull.UnrecoverableError('skip: activity id is not a string');
}
this.apRequestChart.inbox();
@ -215,7 +282,10 @@ export class InboxProcessorService implements OnApplicationShutdown {
this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
});
incCounter(mIncomingApEvent, {});
// アクティビティを処理
const begin = +new Date();
try {
const result = await this.apInboxService.performActivity(authUser.user, activity);
if (result && !result.startsWith('ok')) {
@ -225,17 +295,26 @@ export class InboxProcessorService implements OnApplicationShutdown {
} catch (e) {
if (e instanceof IdentifiableError) {
if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
incCounter(mIncomingApReject, { reason: 'blocked_notes_with_prohibited_words' });
return 'blocked notes with prohibited words';
}
if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') {
incCounter(mIncomingApReject, { reason: 'actor_suspended' });
return 'actor has been suspended';
}
if (e.id === 'd450b8a9-48e4-4dab-ae36-f4db763fda7c') { // invalid Note
incCounter(mIncomingApReject, { reason: 'invalid_note' });
return e.message;
}
}
const end = +new Date();
observeHistogram(mIncomingApProcessingTime, { success: 'false' }, (end - begin) / 1000);
incCounter(mincomingApProcessingError, {});
throw e;
}
observeHistogram(mIncomingApProcessingTime, { success: 'true' }, (+new Date() - begin) / 1000);
incCounter(mIncomingApEventAccepted, {});
return 'ok';
}

View file

@ -105,7 +105,7 @@ export class ActivityPubServerService {
let signature;
try {
signature = httpSignature.parseRequest(request.raw, { 'headers': [] });
signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'host', 'date'], authorizationHeaderName: 'signature' });
} catch (e) {
reply.code(401);
return;

View file

@ -8,27 +8,19 @@ import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Inject, Injectable } from '@nestjs/common';
import rename from 'rename';
import sharp from 'sharp';
import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
import type { Config } from '@/config.js';
import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { createTemp } from '@/misc/create-temp.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
import { StatusError } from '@/misc/status-error.js';
import type Logger from '@/logger.js';
import { DownloadService } from '@/core/DownloadService.js';
import { IImageStreamable, ImageProcessingService, webpDefault } from '@/core/ImageProcessingService.js';
import { VideoProcessingService } from '@/core/VideoProcessingService.js';
import { InternalStorageService } from '@/core/InternalStorageService.js';
import { contentDisposition } from '@/misc/content-disposition.js';
import { FileInfoService } from '@/core/FileInfoService.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { isMimeImage } from '@/misc/is-mime-image.js';
import { correctFilename } from '@/misc/correct-filename.js';
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
import { InternalStorageService } from '@/core/InternalStorageService.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
@ -46,11 +38,8 @@ export class FileServerService {
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
private fileInfoService: FileInfoService,
private downloadService: DownloadService,
private imageProcessingService: ImageProcessingService,
private videoProcessingService: VideoProcessingService,
private internalStorageService: InternalStorageService,
private fileInfoService: FileInfoService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('server', 'gray');
@ -134,159 +123,72 @@ export class FileServerService {
return;
}
try {
if (file.state === 'remote') {
let image: IImageStreamable | null = null;
if (file.state === 'remote') {
const url = new URL(`${this.config.mediaProxy}/`);
if (file.fileRole === 'thumbnail') {
if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) {
reply.header('Cache-Control', 'max-age=31536000, immutable');
url.searchParams.set('url', file.url);
const url = new URL(`${this.config.mediaProxy}/static.webp`);
url.searchParams.set('url', file.url);
url.searchParams.set('static', '1');
return await reply.redirect(url.toString(), 301);
}
file.cleanup();
return await reply.redirect(url.toString(), 301);
} else if (file.mime.startsWith('video/')) {
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
if (externalThumbnail) {
file.cleanup();
return await reply.redirect(externalThumbnail, 301);
}
if (file.fileRole !== 'original') {
const filename = rename(file.filename, {
suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web',
extname: file.ext ? `.${file.ext}` : '.unknown',
}).toString();
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
}
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream');
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
if (file.fileRole === 'webpublic') {
if (['image/svg+xml'].includes(file.mime)) {
reply.header('Cache-Control', 'max-age=31536000, immutable');
const url = new URL(`${this.config.mediaProxy}/svg.webp`);
url.searchParams.set('url', file.url);
file.cleanup();
return await reply.redirect(url.toString(), 301);
}
}
if (!image) {
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
}
if ('pipe' in image.data && typeof image.data.pipe === 'function') {
// image.dataがstreamなら、stream終了後にcleanup
image.data.on('end', file.cleanup);
image.data.on('close', file.cleanup);
} else {
// image.dataがstreamでないなら直ちにcleanup
file.cleanup();
}
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
reply.header('Content-Length', file.file.size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(file.filename, image.ext),
),
);
return image.data;
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
if (file.fileRole !== 'original') {
const filename = rename(file.filename, {
suffix: file.fileRole === 'thumbnail' ? '-thumb' : '-web',
extname: file.ext ? `.${file.ext}` : '.unknown',
}).toString();
return fs.createReadStream(file.path);
} else {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream');
reply.header('Content-Length', file.file.size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', file.filename));
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream');
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
return fs.createReadStream(file.path);
} else {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream');
reply.header('Content-Length', file.file.size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', file.filename));
if (request.headers.range && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
return fs.createReadStream(file.path);
const chunksize = end - start + 1;
const fileStream = fs.createReadStream(file.path, {
start,
end,
});
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
return fileStream;
}
} catch (e) {
if ('cleanup' in file) file.cleanup();
throw e;
return fs.createReadStream(file.path);
}
}
@ -302,224 +204,56 @@ export class FileServerService {
// アバタークロップなど、どうしてもオリジンである必要がある場合
const mustOrigin = 'origin' in request.query;
if (this.config.externalMediaProxyEnabled && !mustOrigin) {
// 外部のメディアプロキシが有効なら、そちらにリダイレクト
if (!this.config.mediaProxy) {
reply.code(501);
}
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
const proxiedURL = new URL(`${this.config.mediaProxy}/?url=${encodeURIComponent(url)}`);
const url = new URL(`${this.config.mediaProxy}/${request.params.url || ''}`);
for (const [key, value] of Object.entries(request.query)) {
url.searchParams.append(key, value);
}
for (const [key, value] of Object.entries(request.query)) {
if (key.toLowerCase() === 'url') continue;
proxiedURL.searchParams.append(key, value);
}
if (!mustOrigin) {
return await reply.redirect(
url.toString(),
proxiedURL.toString(),
301,
);
}
reply.header('Cache-Control', 'public, max-age=259200'); // 3 days
if (!request.headers['user-agent']) {
throw new StatusError('User-Agent is required', 400, 'User-Agent is required');
} else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) {
throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive');
}
if (!request.headers['user-agent']) {
throw new StatusError('User-Agent is required', 400, 'User-Agent is required');
} else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) {
throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive');
}
// Create temp file
const file = await this.getStreamAndTypeFromUrl(url);
if (file === '404') {
reply.code(404);
reply.header('Cache-Control', 'max-age=86400');
return reply.sendFile('/dummy.png', assets);
}
if (file === '204') {
reply.code(204);
reply.header('Cache-Control', 'max-age=86400');
return;
}
try {
const isConvertibleImage = isMimeImage(file.mime, 'sharp-convertible-image-with-bmp');
const isAnimationConvertibleImage = isMimeImage(file.mime, 'sharp-animation-convertible-image-with-bmp');
if (
'emoji' in request.query ||
'avatar' in request.query ||
'static' in request.query ||
'preview' in request.query ||
'badge' in request.query
) {
if (!isConvertibleImage) {
// 画像でないなら404でお茶を濁す
throw new StatusError('Unexpected mime', 404);
}
}
let image: IImageStreamable | null = null;
if ('emoji' in request.query || 'avatar' in request.query) {
if (!isAnimationConvertibleImage && !('static' in request.query)) {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
} else {
const data = (await sharpBmp(file.path, file.mime, { animated: !('static' in request.query) }))
.resize({
height: 'emoji' in request.query ? 128 : 320,
withoutEnlargement: true,
})
.webp(webpDefault);
image = {
data,
ext: 'webp',
type: 'image/webp',
};
}
} else if ('static' in request.query) {
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422);
} else if ('preview' in request.query) {
image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200);
} else if ('badge' in request.query) {
const mask = (await sharpBmp(file.path, file.mime))
.resize(96, 96, {
fit: 'contain',
position: 'centre',
withoutEnlargement: false,
})
.greyscale()
.normalise()
.linear(1.75, -(128 * 1.75) + 128) // 1.75x contrast
.flatten({ background: '#000' })
.toColorspace('b-w');
const stats = await mask.clone().stats();
if (stats.entropy < 0.1) {
// エントロピーがあまりない場合は404にする
throw new StatusError('Skip to provide badge', 404);
}
const data = sharp({
create: { width: 96, height: 96, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
})
.pipelineColorspace('b-w')
.boolean(await mask.png().toBuffer(), 'eor');
image = {
data: await data.png().toBuffer(),
ext: 'png',
type: 'image/png',
};
} else if (file.mime === 'image/svg+xml') {
image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048);
} else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) {
throw new StatusError('Rejected type', 403, 'Rejected type');
}
if (!image) {
if (request.headers.range && file.file && file.file.size > 0) {
const range = request.headers.range as string;
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
if (end > file.file.size) {
end = file.file.size - 1;
}
const chunksize = end - start + 1;
image = {
data: fs.createReadStream(file.path, {
start,
end,
}),
ext: file.ext,
type: file.mime,
};
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize);
reply.code(206);
} else {
image = {
data: fs.createReadStream(file.path),
ext: file.ext,
type: file.mime,
};
}
}
if ('cleanup' in file) {
if ('pipe' in image.data && typeof image.data.pipe === 'function') {
// image.dataがstreamなら、stream終了後にcleanup
image.data.on('end', file.cleanup);
image.data.on('close', file.cleanup);
} else {
// image.dataがstreamでないなら直ちにcleanup
file.cleanup();
}
}
reply.header('Content-Type', image.type);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition',
contentDisposition(
'inline',
correctFilename(file.filename, image.ext),
),
);
return image.data;
} catch (e) {
if ('cleanup' in file) file.cleanup();
throw e;
}
}
@bindThis
private async getStreamAndTypeFromUrl(url: string): Promise<
{ state: 'remote'; fileRole?: 'thumbnail' | 'webpublic' | 'original'; file?: MiDriveFile; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; }
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; }
| '404'
| '204'
> {
if (url.startsWith(`${this.config.url}/files/`)) {
const key = url.replace(`${this.config.url}/files/`, '').split('/').shift();
if (!key) throw new StatusError('Invalid File Key', 400, 'Invalid File Key');
return await this.getFileFromKey(key);
}
return await this.downloadAndDetectTypeFromUrl(url);
}
@bindThis
private async downloadAndDetectTypeFromUrl(url: string): Promise<
{ state: 'remote' ; mime: string; ext: string | null; path: string; cleanup: () => void; filename: string; }
> {
const [path, cleanup] = await createTemp();
try {
const { filename } = await this.downloadService.downloadUrl(url, path);
const { mime, ext } = await this.fileInfoService.detectType(path);
return {
state: 'remote',
mime, ext,
path, cleanup,
filename,
};
} catch (e) {
cleanup();
throw e;
// directly proxy request through
const res = await fetch(proxiedURL, {
headers: {
'X-Forwarded-For': request.headers['x-forwarded-for']?.at(0) ?? request.ip,
'User-Agent': request.headers['user-agent'],
},
});
reply.code(res.status);
for (const [key, value] of res.headers.entries()) {
reply.header(key, value);
}
reply.send(res.body);
}
@bindThis
private async getFileFromKey(key: string): Promise<
{ state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; url: string; mime: string; ext: string | null; path: string; cleanup: () => void; }
{ state: 'remote'; fileRole: 'thumbnail' | 'webpublic' | 'original'; filename: string; url: string; }
| { state: 'stored_internal'; fileRole: 'thumbnail' | 'webpublic' | 'original'; file: MiDriveFile; filename: string; mime: string; ext: string | null; path: string; }
| '404'
| '204'
@ -538,15 +272,10 @@ export class FileServerService {
if (!file.storedInternal) {
if (!(file.isLink && file.uri)) return '204';
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
file.size = (await fs.promises.stat(result.path)).size; // DB file.sizeは正確とは限らないので
return {
...result,
url: file.uri,
fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original',
file,
filename: file.name,
};
return { state: 'remote',
fileRole: isThumbnail ? 'thumbnail' : isWebpublic ? 'webpublic' : 'original',
filename: file.name
, url: file.uri };
}
const path = this.internalStorageService.resolvePath(key);

View file

@ -15,6 +15,8 @@ import NotesChart from '@/core/chart/charts/notes.js';
import UsersChart from '@/core/chart/charts/users.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { IsNull, MoreThan, Not } from 'typeorm';
import type { NotesRepository, UsersRepository } from '@/models/_.js';
const nodeinfo2_1path = '/nodeinfo/2.1';
const nodeinfo2_0path = '/nodeinfo/2.0';
@ -25,6 +27,10 @@ export class NodeinfoServerService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private userEntityService: UserEntityService,
private metaService: MetaService,
@ -58,18 +64,16 @@ export class NodeinfoServerService {
const [
meta,
//activeHalfyear,
//activeMonth,
activeHalfyear,
activeMonth,
localComments,
] = await Promise.all([
this.metaService.fetch(true),
// 重い
//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
this.notesRepository.count({ where: { userHost: IsNull(), replyId: Not(IsNull()) } }),
]);
const activeHalfyear = null;
const activeMonth = null;
const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;
const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
@ -91,7 +95,7 @@ export class NodeinfoServerService {
usage: {
users: { total, activeHalfyear, activeMonth },
localPosts,
localComments: 0,
localComments,
},
metadata: {
nodeName: meta.name,
@ -105,6 +109,8 @@ export class NodeinfoServerService {
name: meta.maintainerName,
email: meta.maintainerEmail,
},
gitCommit: this.config.gitCommit,
gitDescribe: this.config.gitDescribe,
langs: meta.langs,
tosUrl: meta.termsOfServiceUrl,
privacyPolicyUrl: meta.privacyPolicyUrl,

View file

@ -47,6 +47,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
import { ReversiChannelService } from './api/stream/channels/reversi.js';
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
import { MetricsService } from './api/MetricsService.js';
@Module({
imports: [
@ -94,6 +95,7 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
UserListChannelService,
OpenApiServerService,
OAuth2ProviderService,
MetricsService,
],
exports: [
ServerService,

View file

@ -31,9 +31,89 @@ import { HealthServerService } from './HealthServerService.js';
import { ClientServerService } from './web/ClientServerService.js';
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
import { makeHstsHook } from './hsts.js';
import { generateCSP } from './csp.js';
import { sanitizeRequestURI } from '@/misc/log-sanitization.js';
import { metricCounter, metricGauge, metricHistogram, MetricsService } from './api/MetricsService.js';
const _dirname = fileURLToPath(new URL('.', import.meta.url));
function categorizeRequestPath(path: string): 'api' | 'health' | 'vite' | 'other' {
if (path === '/healthz') {
return 'health';
}
if (path.startsWith('/vite/') || path.startsWith('/embed_vite/')) {
return 'vite';
}
if (path === '/api' || path.startsWith('/api/')) {
return 'api';
}
return 'other';
}
const mRequestTime = metricHistogram({
name: 'misskey_http_request_duration_seconds',
help: 'Duration of handling HTTP requests in seconds',
labelNames: ['host', 'cate', 'method', 'path'],
buckets: [0.001, 0.1, 0.5, 1, 2, 5],
});
const mRequestsReceived = metricCounter({
name: 'misskey_http_requests_received_total',
help: 'Total number of HTTP requests received',
labelNames: [],
});
const mNotFoundServed = metricCounter({
name: 'misskey_http_not_found_served_total',
help: 'Total number of HTTP 404 responses served',
labelNames: ['method', 'cate'],
});
const mMethodNotAllowedServed = metricCounter({
name: 'misskey_http_method_not_allowed_served_total',
help: 'Total number of HTTP 405 responses served',
labelNames: ['method', 'cate'],
});
const mTooManyRequestsServed = metricCounter({
name: 'misskey_http_too_many_requests_served_total',
help: 'Total number of HTTP 429 responses served',
labelNames: ['method', 'cate'],
});
const mAggregateRequestsServed = metricCounter({
name: 'misskey_http_requests_served_total',
help: 'Total number of HTTP requests served including invalid requests',
labelNames: ['host', 'cate', 'status'],
});
const mRequestsServedByPath = metricCounter({
name: 'misskey_http_requests_served_by_path',
help: 'Total number of HTTP requests served',
labelNames: ['host', 'cate', 'method', 'path', 'status'],
});
const mFatalErrorCount = metricCounter({
name: 'misskey_fatal_http_errors_total',
help: 'Total number of HTTP errors that propagate to the top level',
labelNames: ['host', 'cate', 'method', 'path'],
});
const mLastSuccessfulRequest = metricGauge({
name: 'misskey_http_last_successful_request_timestamp_seconds',
help: 'Unix Timestamp of the last successful HTTP request',
labelNames: [],
});
// This function is used to determine if a path is safe to redirect to.
function redirectSafePath(path: string): boolean {
return ['/files/', '/identicon/', '/proxy/', '/static-assets/', '/vite/', '/embed_vite/'].some(prefix => path.startsWith(prefix));
}
@Injectable()
export class ServerService implements OnApplicationShutdown {
private logger: Logger;
@ -68,6 +148,7 @@ export class ServerService implements OnApplicationShutdown {
private globalEventService: GlobalEventService,
private loggerService: LoggerService,
private oauth2ProviderService: OAuth2ProviderService,
private metricsService: MetricsService,
) {
this.logger = this.loggerService.getLogger('server', 'gray');
}
@ -80,11 +161,142 @@ export class ServerService implements OnApplicationShutdown {
});
this.#fastify = fastify;
if (this.config.prometheusMetrics?.enable) {
fastify.addHook('onRequest', (_request, reply, done) => {
reply.header('x-request-received', (+new Date()).toString());
mRequestsReceived?.inc();
done();
});
fastify.addHook('onError', (request, _reply, error, done) => {
const url = new URL(request.url, this.config.url);
const logPath = sanitizeRequestURI(url.pathname);
mFatalErrorCount?.inc({
host: request.hostname,
method: request.method,
path: logPath,
cate: categorizeRequestPath(logPath),
});
done();
});
fastify.addHook('onResponse', (request, reply, done) => {
const url = new URL(request.url, this.config.url);
const logPath = sanitizeRequestURI(url.pathname);
const cate = categorizeRequestPath(logPath);
const received = reply.getHeader('x-request-received') as string;
mAggregateRequestsServed?.inc({
host: request.hostname,
cate,
status: reply.statusCode,
});
if (reply.statusCode === 429) {
mTooManyRequestsServed?.inc({
method: request.method,
cate,
});
done();
return;
}
if (reply.statusCode === 404) {
mNotFoundServed?.inc({
method: request.method,
cate,
});
if (received) {
const duration = (+new Date()) - parseInt(received);
mRequestTime?.observe({
host: request.hostname,
method: request.method,
cate,
}, duration / 1000);
}
done();
return;
}
if (reply.statusCode === 405) {
mMethodNotAllowedServed?.inc({
method: request.method,
cate,
});
done();
return;
}
if (received) {
const duration = (+new Date()) - parseInt(received);
mRequestTime?.observe({
host: request.hostname,
method: request.method,
cate,
path: logPath,
}, duration / 1000);
}
const addSlash = logPath + (logPath.endsWith('/') ? '' : '/');
if (addSlash.startsWith('/metrics/') || addSlash.startsWith('/healthz/')) {
done();
return;
}
if (reply.statusCode <= 299) {
mLastSuccessfulRequest?.set(+new Date() / 1000);
}
mRequestsServedByPath?.inc({
host: request.hostname,
method: request.method,
path: logPath,
cate,
status: reply.statusCode >= 500 ? '5xx' :
reply.statusCode === 401 ? '401' :
reply.statusCode === 403 ? '403' :
reply.statusCode >= 400 ?
'4xx' : '2xx',
});
done();
});
}
// HSTS
// 6months (15552000sec)
if (this.config.url.startsWith('https') && !this.config.disableHsts) {
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('strict-transport-security', 'max-age=15552000; preload');
const preload = this.config.hstsPreload;
const host = new URL(this.config.url).host;
fastify.addHook('onRequest', makeHstsHook(host, preload));
}
// Other Security/Privacy Headers
fastify.addHook('onRequest', (_, reply, done) => {
reply.header('x-content-type-options', 'nosniff');
reply.header('permissions-policy', 'interest-cohort=()'); // Disable FLoC
if (this.config.browserSandboxing.strictOriginReferrer) {
reply.header('referrer-policy', 'strict-origin');
}
done();
});
// CSP
if (process.env.NODE_ENV === 'production' && !this.config.browserSandboxing.csp?.disable) {
console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent);
const generatedCSP = generateCSP(this.config.cspPrerenderedContent, {
mediaProxy: this.config.mediaProxy ? `https://${new URL(this.config.mediaProxy).host}` : undefined,
script_src: [
`https://${new URL(this.config.url).host}/embed_vite/`,
`https://${new URL(this.config.url).host}/vite/`,
],
});
fastify.addHook('onRequest', (_, reply, done) => {
reply.header('content-security-policy', generatedCSP);
done();
});
}
@ -140,7 +352,7 @@ export class ServerService implements OnApplicationShutdown {
name: name,
});
reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
reply.header('Content-Security-Policy', 'default-src \'none\'');
if (emoji == null) {
if ('fallback' in request.query) {
@ -151,16 +363,26 @@ export class ServerService implements OnApplicationShutdown {
}
}
const dbUrl = emoji?.publicUrl || emoji?.originalUrl;
const dbUrlParsed = new URL(dbUrl);
const instanceUrl = new URL(this.config.url);
if (dbUrlParsed.origin === instanceUrl.origin) {
if (!redirectSafePath(dbUrlParsed.pathname)) {
return await reply.status(508);
}
return await reply.redirect(dbUrl, 301);
}
let url: URL;
if ('badge' in request.query) {
url = new URL(`${this.config.mediaProxy}/emoji.png`);
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
url.searchParams.set('url', dbUrl);
url.searchParams.set('badge', '1');
} else {
url = new URL(`${this.config.mediaProxy}/emoji.webp`);
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url.searchParams.set('url', emoji.publicUrl || emoji.originalUrl);
url.searchParams.set('url', dbUrl);
url.searchParams.set('emoji', '1');
if ('static' in request.query) url.searchParams.set('static', '1');
}
@ -184,6 +406,16 @@ export class ServerService implements OnApplicationShutdown {
reply.header('Cache-Control', 'public, max-age=86400');
if (user) {
const dbUrl = user?.avatarUrl ?? this.userEntityService.getIdenticonUrl(user);
const dbUrlParsed = new URL(dbUrl);
const instanceUrl = new URL(this.config.url);
if (dbUrlParsed.origin === instanceUrl.origin) {
if (!redirectSafePath(dbUrlParsed.pathname)) {
return await reply.status(508);
}
return await reply.redirect(dbUrl, 301);
}
reply.redirect(user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user));
} else {
reply.redirect('/static-assets/user-unknown.png');
@ -227,6 +459,8 @@ export class ServerService implements OnApplicationShutdown {
fastify.register(this.clientServerService.createServer);
fastify.register(this.metricsService.createServer);
this.streamingApiServerService.attach(fastify.server);
fastify.server.on('error', err => {

View file

@ -5,6 +5,7 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import * as prom from 'prom-client';
import type { AccessTokensRepository, AppsRepository, UsersRepository } from '@/models/_.js';
import type { MiLocalUser } from '@/models/User.js';
import type { MiAccessToken } from '@/models/AccessToken.js';
@ -13,6 +14,7 @@ import type { MiApp } from '@/models/App.js';
import { CacheService } from '@/core/CacheService.js';
import isNativeToken from '@/misc/is-native-token.js';
import { bindThis } from '@/decorators.js';
import { metricCounter } from './MetricsService.js';
export class AuthenticationError extends Error {
constructor(message: string) {
@ -21,6 +23,12 @@ export class AuthenticationError extends Error {
}
}
const mAuthenticationFailureCounter = metricCounter({
name: 'misskey_authentication_failure_total',
help: 'Total number of authentication failures',
labelNames: ['cred_ty'],
});
@Injectable()
export class AuthenticateService implements OnApplicationShutdown {
private appCache: MemoryKVCache<MiApp>;
@ -51,6 +59,7 @@ export class AuthenticateService implements OnApplicationShutdown {
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
if (user == null) {
mAuthenticationFailureCounter?.inc({ cred_ty: 'native' });
throw new AuthenticationError('user not found');
}
@ -65,6 +74,7 @@ export class AuthenticateService implements OnApplicationShutdown {
});
if (accessToken == null) {
mAuthenticationFailureCounter?.inc({ cred_ty: 'access_token' });
throw new AuthenticationError('invalid signature');
}

View file

@ -0,0 +1,112 @@
import { Inject, Injectable } from "@nestjs/common";
import * as prom from 'prom-client';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from "@/decorators.js";
import type { FastifyInstance, FastifyPluginOptions } from "fastify";
export function metricGauge<K extends string>(conf: prom.GaugeConfiguration<K>) : prom.Gauge<K> | null {
if (!process.env.RUN_MODE) {
return null;
}
return new prom.Gauge(conf);
}
export function metricCounter<K extends string>(conf: prom.CounterConfiguration<K>) : prom.Counter<K> | null {
if (!process.env.RUN_MODE) {
return null;
}
return new prom.Counter(conf);
}
export function metricHistogram<K extends string>(conf: prom.HistogramConfiguration<K>) : prom.Histogram<K> | null {
if (!process.env.RUN_MODE) {
return null;
}
return new prom.Histogram(conf);
}
/*
* SPDX-FileCopyrightText: syuilo and misskey-project and yumechi
* SPDX-License-Identifier: AGPL-3.0-only
*/
@Injectable()
export class MetricsService {
private workerRegistry: prom.AggregatorRegistry<prom.PrometheusContentType> | null = null;
constructor(
@Inject(DI.config)
private config: Config,
) {}
@bindThis
public setWorkerRegistry(workerRegistry: prom.AggregatorRegistry<prom.PrometheusContentType>) {
this.workerRegistry = workerRegistry;
}
@bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
if (this.config.prometheusMetrics?.enable) {
const token = this.config.prometheusMetrics.scrapeToken;
fastify.get('/metrics', async (request, reply) => {
if (token) {
const bearer = request.headers.authorization;
if (!bearer) {
reply.code(401);
return;
}
const [type, t] = bearer.split(' ');
if (type !== 'Bearer' || t !== token) {
reply.code(403);
return;
}
}
try {
reply.header('Content-Type', prom.register.contentType);
reply.send(await prom.register.metrics());
} catch (err) {
reply.code(500);
}
});
fastify.get('/metrics/cluster', async (request, reply) => {
if (token) {
const bearer = request.headers.authorization;
if (!bearer) {
reply.code(401);
return;
}
const [type, t] = bearer.split(' ');
if (type !== 'Bearer' || t !== token) {
reply.code(403);
return;
}
}
if (!this.workerRegistry) {
reply.code(404);
return;
}
try {
reply.header('Content-Type', this.workerRegistry.contentType);
reply.send(await this.workerRegistry.clusterMetrics());
} catch (err) {
reply.code(500);
}
});
}
done();
}
}

View file

@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import bcrypt from 'bcryptjs';
import { IsNull } from 'typeorm';
import * as prom from 'prom-client';
import * as Misskey from 'misskey-js';
import { DI } from '@/di-symbols.js';
import type {
@ -28,6 +29,13 @@ import { RateLimiterService } from './RateLimiterService.js';
import { SigninService } from './SigninService.js';
import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { metricCounter } from './MetricsService.js';
const mSigninFailureCounter = metricCounter({
name: 'misskey_misskey_signin_failure',
help: 'The number of failed sign-ins',
labelNames: ['reason'],
});
@Injectable()
export class SigninApiService {
@ -93,6 +101,7 @@ export class SigninApiService {
// not more than 1 attempt per second and not more than 10 attempts per hour
await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
} catch (err) {
mSigninFailureCounter?.inc({ reason: 'rate_limit' });
reply.code(429);
return {
error: {
@ -104,11 +113,13 @@ export class SigninApiService {
}
if (typeof username !== 'string') {
mSigninFailureCounter?.inc({ reason: 'bad_form' });
reply.code(400);
return;
}
if (token != null && typeof token !== 'string') {
mSigninFailureCounter?.inc({ reason: 'bad_form' });
reply.code(400);
return;
}
@ -120,12 +131,14 @@ export class SigninApiService {
}) as MiLocalUser;
if (user == null) {
mSigninFailureCounter?.inc({ reason: 'user_not_found' });
return error(404, {
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
});
}
if (user.isSuspended) {
mSigninFailureCounter?.inc({ reason: 'user_suspended' });
return error(403, {
id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
});
@ -150,6 +163,7 @@ export class SigninApiService {
}
if (typeof password !== 'string') {
mSigninFailureCounter?.inc({ reason: 'bad_form' });
reply.code(400);
return;
}
@ -167,6 +181,7 @@ export class SigninApiService {
success: false,
});
mSigninFailureCounter?.inc({ reason: failure?.id ?? `unknown_error_${status ?? 500}` });
return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
};
@ -174,30 +189,35 @@ export class SigninApiService {
if (process.env.NODE_ENV !== 'test') {
if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
mSigninFailureCounter?.inc({ reason: 'captcha_verification_failed_hcaptcha' });
throw new FastifyReplyError(400, err);
});
}
if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
mSigninFailureCounter?.inc({ reason: 'captcha_verification_failed_mcaptcha' });
throw new FastifyReplyError(400, err);
});
}
if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
mSigninFailureCounter?.inc({ reason: 'captcha_verification_failed_recaptcha' });
throw new FastifyReplyError(400, err);
});
}
if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
mSigninFailureCounter?.inc({ reason: 'captcha_verification_failed_turnstile' });
throw new FastifyReplyError(400, err);
});
}
if (this.meta.enableTestcaptcha) {
await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => {
mSigninFailureCounter?.inc({ reason: 'captcha_verification_failed_testcaptcha' });
throw new FastifyReplyError(400, err);
});
}

View file

@ -15,18 +15,21 @@ import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { ApiError } from '@/server/api/error.js';
import { Packed } from '@/misc/json-schema.js';
import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin'],
errors: {
accessDenied: {
httpStatusCode: 403,
message: 'Access denied.',
code: 'ACCESS_DENIED',
id: '1fb7cb09-d46a-4fff-b8df-057708cce513',
},
wrongInitialPassword: {
httpStatusCode: 401,
message: 'Initial password is incorrect.',
code: 'INCORRECT_INITIAL_PASSWORD',
id: '97147c55-1ae1-4f6f-91d6-e1c3e0e76d62',
@ -65,6 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private roleService: RoleService,
private userEntityService: UserEntityService,
private signupService: SignupService,
private instanceActorService: InstanceActorService,
@ -85,8 +89,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// 初期パスワードが設定されていないのに初期パスワードが入力された場合
throw new ApiError(meta.errors.wrongInitialPassword);
}
} else if ((realUsers && !me?.isRoot) || token !== null) {
// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
} else if (!(me?.isRoot) && !await this.roleService.isAdministrator(me)) {
// 管理者でない場合
throw new ApiError(meta.errors.accessDenied);
} else if (token && !token?.permission.includes('write:admin:create-account')) {
// access token を使うときは write:admin:create-account 権限が必要
throw new ApiError(meta.errors.accessDenied);
}

View file

@ -11,6 +11,7 @@ import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
export const meta = {
tags: ['federation'],
requireAdmin: true,
requireCredential: true,
kind: 'read:federation',

View file

@ -118,6 +118,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
]));
if (local != null) return local;
const host = this.utilityService.extractDbHost(uri);
// local object, not found in db? fail
if (this.utilityService.isSelfHost(host)) return null;
// リモートから一旦オブジェクトフェッチ
const resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(uri) as any;
@ -134,8 +139,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return await this.mergePack(
me,
isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null,
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null,
isActor(object) ? await this.apPersonService.createPerson(getApId(object), resolver) : null,
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, resolver, true) : null,
);
}

Some files were not shown because too many files have changed in this diff Show more