Compare commits

..

79 Commits

Author SHA1 Message Date
standardci b1665779b5 chore(release): publish new version
- @standardnotes/api-gateway@1.87.4
 - @standardnotes/home-server@1.22.7
 - @standardnotes/syncing-server@1.127.5
2023-12-01 08:53:10 +00:00
Karol Sójko a82192db42 fix(api-gateway): home server home page (#949)
* feat: add home server home page message

* fix: upgrade yarn
2023-12-01 09:32:56 +01:00
Karol Sójko 589b740f49 fix(syncing-server): remove the dice roll on whether to inform the client on items changed (#950) 2023-12-01 09:24:20 +01:00
Karol Sójko 3c10de3e5d fix: localstack version in docker compose example 2023-11-30 13:45:59 +01:00
standardci 41a04062c9 chore(release): publish new version
- @standardnotes/home-server@1.22.6
 - @standardnotes/syncing-server@1.127.4
2023-11-30 11:36:39 +00:00
Karol Sójko db9d10c302 fix: upgrade localstack to latest version 2023-11-30 12:16:08 +01:00
Karol Sójko 5596d04040 fix: add outputing docker compose logs 2023-11-30 11:33:10 +01:00
Karol Sójko 341f69e301 Revert "chore: upgrade deps"
This reverts commit ef0464690b.
2023-11-30 10:49:55 +01:00
Karol Sójko ef0464690b chore: upgrade deps 2023-11-30 10:06:56 +01:00
Karol Sójko 199ebeb4ea fix(syncing-server): increase chances for notifying of items change to 70% 2023-11-30 09:54:47 +01:00
standardci c949670d4c chore(release): publish new version
- @standardnotes/home-server@1.22.5
 - @standardnotes/syncing-server@1.127.3
2023-11-29 08:55:04 +00:00
Karol Sójko 9dcd583b58 fix(syncing-server): dice roll specs 2023-11-29 09:32:14 +01:00
Karol Sójko 097e320490 fix(syncing-server): increase chances for notifying of items change 2023-11-29 07:54:43 +01:00
standardci c9bfda91f4 chore(release): publish new version
- @standardnotes/home-server@1.22.4
 - @standardnotes/syncing-server@1.127.2
2023-11-28 10:43:30 +00:00
Karol Sójko 2d6a3ebf45 fix(syncing-server): add debug logs about sending items changed event 2023-11-28 11:21:54 +01:00
standardci d0d4bd23fb chore(release): publish new version
- @standardnotes/analytics@2.34.3
 - @standardnotes/api-gateway@1.87.3
 - @standardnotes/auth-server@1.174.3
 - @standardnotes/domain-events-infra@1.22.3
 - @standardnotes/domain-events@2.138.1
 - @standardnotes/files-server@1.36.3
 - @standardnotes/home-server@1.22.3
 - @standardnotes/revisions-server@1.51.3
 - @standardnotes/scheduler-server@1.27.8
 - @standardnotes/security@1.17.2
 - @standardnotes/syncing-server@1.127.1
 - @standardnotes/websockets-server@1.21.1
2023-11-28 10:08:27 +00:00
Karol Sójko edb0a768d0 fix(auth): pass session uuid to web sockets controller 2023-11-28 10:46:39 +01:00
Karol Sójko 4cc647ac07 fix(api-gateway): add session to response locals from web socket middleware 2023-11-28 10:44:56 +01:00
Karol Sójko bcd1d830e6 fix: pass session uuid to websockets token 2023-11-28 10:39:25 +01:00
standardci 2597324876 chore(release): publish new version
- @standardnotes/analytics@2.34.2
 - @standardnotes/api-gateway@1.87.2
 - @standardnotes/auth-server@1.174.2
 - @standardnotes/domain-events-infra@1.22.2
 - @standardnotes/domain-events@2.138.0
 - @standardnotes/files-server@1.36.2
 - @standardnotes/home-server@1.22.2
 - @standardnotes/revisions-server@1.51.2
 - @standardnotes/scheduler-server@1.27.7
 - @standardnotes/syncing-server@1.127.0
 - @standardnotes/websockets-server@1.21.0
2023-11-28 08:53:10 +00:00
Karol Sójko 69b404f5d4 feat: send event to client upon items change on server (#941)
* feat(websockets): persist connections in mysql

* fix: add sending event to client upon items changed on server

* fix payload

* fix: add cathcing errors

* fix: send changed items event only on a 10% dice roll
2023-11-28 09:31:42 +01:00
standardci e94b0d0b02 chore(release): publish new version
- @standardnotes/analytics@2.34.1
 - @standardnotes/api-gateway@1.87.1
 - @standardnotes/auth-server@1.174.1
 - @standardnotes/common@1.52.1
 - @standardnotes/domain-core@1.41.1
 - @standardnotes/domain-events-infra@1.22.1
 - @standardnotes/domain-events@2.137.1
 - @standardnotes/files-server@1.36.1
 - @standardnotes/grpc@1.3.1
 - @standardnotes/home-server@1.22.1
 - @standardnotes/predicates@1.8.1
 - @standardnotes/revisions-server@1.51.1
 - @standardnotes/scheduler-server@1.27.6
 - @standardnotes/security@1.17.1
 - @standardnotes/settings@1.23.1
 - @standardnotes/sncrypto-node@1.16.1
 - @standardnotes/syncing-server@1.126.1
 - @standardnotes/time@1.18.1
 - @standardnotes/websockets-server@1.20.4
2023-11-27 09:14:33 +00:00
Karol Sójko ed1bf37287 fix: repository config in package.json files 2023-11-27 09:53:23 +01:00
standardci 3946f56261 chore(release): publish new version
- @standardnotes/analytics@2.34.0
 - @standardnotes/api-gateway@1.87.0
 - @standardnotes/auth-server@1.174.0
 - @standardnotes/common@1.52.0
 - @standardnotes/domain-core@1.41.0
 - @standardnotes/domain-events-infra@1.22.0
 - @standardnotes/domain-events@2.137.0
 - @standardnotes/files-server@1.36.0
 - @standardnotes/grpc@1.3.0
 - @standardnotes/home-server@1.22.0
 - @standardnotes/predicates@1.8.0
 - @standardnotes/revisions-server@1.51.0
 - @standardnotes/scheduler-server@1.27.5
 - @standardnotes/security@1.17.0
 - @standardnotes/settings@1.23.0
 - @standardnotes/sncrypto-node@1.16.0
 - @standardnotes/syncing-server@1.126.0
 - @standardnotes/time@1.18.0
 - @standardnotes/websockets-server@1.20.3
2023-11-27 08:43:38 +00:00
Karol Sójko fc53dab007 feat: allow github publish action permission to mint id-token 2023-11-27 09:22:22 +01:00
Karol Sójko e836abdef7 feat: add npm provenance to published packages 2023-11-27 09:20:10 +01:00
standardci 826482b1f0 chore(release): publish new version
- @standardnotes/analytics@2.33.4
 - @standardnotes/api-gateway@1.86.6
 - @standardnotes/auth-server@1.173.2
 - @standardnotes/domain-events-infra@1.21.4
 - @standardnotes/domain-events@2.136.0
 - @standardnotes/files-server@1.35.2
 - @standardnotes/home-server@1.21.11
 - @standardnotes/revisions-server@1.50.2
 - @standardnotes/scheduler-server@1.27.4
 - @standardnotes/syncing-server@1.125.2
 - @standardnotes/websockets-server@1.20.2
2023-11-23 09:34:26 +00:00
Karol Sójko 45bd00919c feat(domain-events): add email campaign send out requested event 2023-11-23 10:13:00 +01:00
standardci 4e1bae6daf chore(release): publish new version
- @standardnotes/api-gateway@1.86.5
 - @standardnotes/auth-server@1.173.1
 - @standardnotes/home-server@1.21.10
 - @standardnotes/syncing-server@1.125.1
2023-11-22 11:46:47 +00:00
Karol Sójko 8f23c8ab3f fix: error handling on gRPC (#937) 2023-11-22 12:25:42 +01:00
standardci 4d32f26631 chore(release): publish new version
- @standardnotes/analytics@2.33.3
 - @standardnotes/api-gateway@1.86.4
 - @standardnotes/auth-server@1.173.0
 - @standardnotes/domain-events-infra@1.21.3
 - @standardnotes/domain-events@2.135.0
 - @standardnotes/files-server@1.35.1
 - @standardnotes/home-server@1.21.9
 - @standardnotes/revisions-server@1.50.1
 - @standardnotes/scheduler-server@1.27.3
 - @standardnotes/syncing-server@1.125.0
 - @standardnotes/websockets-server@1.20.1
2023-11-22 10:22:09 +00:00
Karol Sójko c11abe1bd3 feat: add verifiying if user has no items before mass deleting spam accounts (#936) 2023-11-22 11:00:56 +01:00
standardci 4d12566b0d chore(release): publish new version
- @standardnotes/home-server@1.21.8
 - @standardnotes/syncing-server@1.124.3
2023-11-21 14:59:47 +00:00
Karol Sójko 2200dca69d fix(syncing-server): front load themes and user prefs as high priority load items (#935) 2023-11-21 15:38:28 +01:00
standardci d41dd3bdda chore(release): publish new version
- @standardnotes/api-gateway@1.86.3
 - @standardnotes/home-server@1.21.7
2023-11-21 08:42:31 +00:00
Karol Sójko c5c24b3ac9 fix(api-gateway): add meta field to grpc sync calls (#934) 2023-11-21 09:21:54 +01:00
standardci 462ade2145 chore(release): publish new version
- @standardnotes/api-gateway@1.86.2
 - @standardnotes/auth-server@1.172.2
 - @standardnotes/home-server@1.21.6
 - @standardnotes/syncing-server@1.124.2
2023-11-20 14:22:00 +00:00
Karol Sójko bfef16ce37 fix: define grpc max message size 2023-11-20 15:00:48 +01:00
standardci aa4351c8e9 chore(release): publish new version
- @standardnotes/api-gateway@1.86.1
 - @standardnotes/auth-server@1.172.1
 - @standardnotes/home-server@1.21.5
 - @standardnotes/syncing-server@1.124.1
2023-11-20 13:14:28 +00:00
Karol Sójko 2dff6a2ed3 fix: setting gzip as default compression on grpc calls (#933) 2023-11-20 13:52:54 +01:00
standardci 7808cc8ed2 chore(release): publish new version
- @standardnotes/api-gateway@1.86.0
 - @standardnotes/auth-server@1.172.0
 - @standardnotes/grpc@1.2.0
 - @standardnotes/home-server@1.21.4
 - @standardnotes/syncing-server@1.124.0
2023-11-20 10:29:07 +00:00
Karol Sójko 5b84f078c6 feat(grpc): add syncing protocol buffers (#930)
* feat(grpc): add syncing protocol buffers

* wip

* feat: syncing implementation

* fix: sendign metadata

* fix: grpc sync request mapping

* fix grpc response mapper
2023-11-20 11:08:16 +01:00
standardci cf5f44a4a5 chore(release): publish new version
- @standardnotes/api-gateway@1.85.1
 - @standardnotes/home-server@1.21.3
2023-11-16 12:17:57 +00:00
Karol Sójko ed05ea553f fix(api-gateway): remove overly verbose debug messages 2023-11-16 12:57:07 +01:00
standardci 4418c38878 chore(release): publish new version
- @standardnotes/api-gateway@1.85.0
 - @standardnotes/auth-server@1.171.0
 - @standardnotes/home-server@1.21.2
2023-11-16 11:31:52 +00:00
Karol Sójko 6391a01b57 feat: add debug logs for grpc communication 2023-11-16 12:10:51 +01:00
standardci 9dbcec198d chore(release): publish new version
- @standardnotes/api-gateway@1.84.1
 - @standardnotes/home-server@1.21.1
2023-11-16 10:25:54 +00:00
Karol Sójko 78fbeb595f fix(api-gateway): bindings 2023-11-16 11:04:44 +01:00
standardci d894a87e87 chore(release): publish new version
- @standardnotes/api-gateway@1.84.0
 - @standardnotes/auth-server@1.170.0
 - @standardnotes/files-server@1.35.0
 - @standardnotes/grpc@1.1.0
 - @standardnotes/home-server@1.21.0
 - @standardnotes/revisions-server@1.50.0
 - @standardnotes/syncing-server@1.123.0
 - @standardnotes/websockets-server@1.20.0
2023-11-16 09:31:37 +00:00
Karol Sójko 4f62cac213 feat: add grpc sessions validation server (#928)
* feat: add grpc sessions validation server

* feat: add client implementation on api gateway

* fix: response codes

* fix: errored response

* feat: add configuring grpc as optional service proxy

* fix env vars

* fix linter issue
2023-11-16 10:10:42 +01:00
standardci ce081274da chore(release): publish new version
- @standardnotes/api-gateway@1.83.5
 - @standardnotes/home-server@1.20.5
2023-11-14 14:18:46 +00:00
Karol Sójko fd997f4849 fix(api-gateway): remove unused imports 2023-11-14 14:57:41 +01:00
Karol Sójko 3ddd671c47 fix(api-gateway): remove the verify body function 2023-11-14 14:55:55 +01:00
standardci c19de13cac chore(release): publish new version
- @standardnotes/api-gateway@1.83.4
 - @standardnotes/home-server@1.20.4
2023-11-14 13:30:24 +00:00
Karol Sójko f65809ef30 fix(api-gateway): checking for buffer length 2023-11-14 14:09:10 +01:00
Karol Sójko 2823ed8612 fix(api-gateway): buffer encoding 2023-11-14 14:07:55 +01:00
Karol Sójko 420bf9ec54 fix(api-gateway): add verification if json is valid on request 2023-11-14 14:04:58 +01:00
standardci 5f67e5efda chore(release): publish new version
- @standardnotes/analytics@2.33.2
 - @standardnotes/api-gateway@1.83.3
 - @standardnotes/auth-server@1.169.2
 - @standardnotes/domain-events-infra@1.21.2
 - @standardnotes/domain-events@2.134.2
 - @standardnotes/files-server@1.34.2
 - @standardnotes/home-server@1.20.3
 - @standardnotes/revisions-server@1.49.2
 - @standardnotes/scheduler-server@1.27.2
 - @standardnotes/syncing-server@1.122.2
 - @standardnotes/websockets-server@1.19.2
2023-11-13 10:51:57 +00:00
Karol Sójko daed1a77a0 fix(api-gateway): add application version to the error logs 2023-11-13 11:23:18 +01:00
Karol Sójko b39eb09d91 fix(api-gateway): add request method to the debug logs 2023-11-13 11:15:02 +01:00
Karol Sójko f6ec8626e5 fix(domain-events): remove unused event 2023-11-13 11:02:11 +01:00
standardci 97b12f2131 chore(release): publish new version
- @standardnotes/analytics@2.33.1
 - @standardnotes/api-gateway@1.83.2
 - @standardnotes/auth-server@1.169.1
 - @standardnotes/domain-events-infra@1.21.1
 - @standardnotes/domain-events@2.134.1
 - @standardnotes/files-server@1.34.1
 - @standardnotes/home-server@1.20.2
 - @standardnotes/revisions-server@1.49.1
 - @standardnotes/scheduler-server@1.27.1
 - @standardnotes/syncing-server@1.122.1
 - @standardnotes/websockets-server@1.19.1
2023-11-13 10:00:24 +00:00
Karol Sójko 8e4e36513a fix: remove unused even-store from code base (#925)
* fix: remove unused even-store from code base

* fix lock
2023-11-13 10:37:42 +01:00
Karol Sójko c8bf4ab3a0 fix(api-gateway): debug log on error thrown body representation 2023-11-13 09:56:54 +01:00
standardci 3fa01a328b chore(release): publish new version
- @standardnotes/api-gateway@1.83.1
 - @standardnotes/home-server@1.20.1
2023-11-13 08:35:23 +00:00
Karol Sójko 60686dcdbd fix(api-gateway): add debug logs for errors on parsing 2023-11-13 07:39:34 +01:00
standardci fddd17e531 chore(release): publish new version
- @standardnotes/api-gateway@1.83.0
 - @standardnotes/auth-server@1.169.0
 - @standardnotes/files-server@1.34.0
 - @standardnotes/home-server@1.20.0
 - @standardnotes/revisions-server@1.49.0
 - @standardnotes/syncing-server@1.122.0
 - @standardnotes/websockets-server@1.19.0
2023-11-10 14:39:08 +00:00
Karol Sójko f99750169f fix(api-gateway): add more info on error logs 2023-11-10 15:13:54 +01:00
Karol Sójko daad76d0dd feat: add keep-alive connections to subservices (#924)
* feat: add keep-alive connections to subservices

* fix: defaults
2023-11-10 14:52:44 +01:00
standardci b3542e2fab chore(release): publish new version
- @standardnotes/api-gateway@1.82.1
 - @standardnotes/home-server@1.19.1
2023-11-10 12:21:41 +00:00
Karol Sójko a9b1543e20 fix(api-gateway): websockets calls logs severity 2023-11-10 12:59:42 +01:00
standardci e6d8e5c5f2 chore(release): publish new version
- @standardnotes/analytics@2.33.0
 - @standardnotes/api-gateway@1.82.0
 - @standardnotes/auth-server@1.168.0
 - @standardnotes/domain-events-infra@1.21.0
 - @standardnotes/domain-events@2.134.0
 - @standardnotes/event-store@1.14.0
 - @standardnotes/files-server@1.33.0
 - @standardnotes/home-server@1.19.0
 - @standardnotes/revisions-server@1.48.0
 - @standardnotes/scheduler-server@1.27.0
 - @standardnotes/syncing-server@1.121.0
 - @standardnotes/websockets-server@1.18.0
2023-11-10 11:46:24 +00:00
Karol Sójko c24353cc24 feat: add graceful shutdown procedures upon SIGTERM (#923) 2023-11-10 12:20:21 +01:00
standardci 4855e1d5f5 chore(release): publish new version
- @standardnotes/api-gateway@1.81.14
 - @standardnotes/home-server@1.18.32
2023-11-10 10:04:31 +00:00
Karol Sójko 5d3fb9a537 fix(api-gateway): add logs about calling web sockets with minimal format 2023-11-10 10:32:47 +01:00
standardci b55d80a7cd chore(release): publish new version
- @standardnotes/api-gateway@1.81.13
 - @standardnotes/home-server@1.18.31
2023-11-09 14:29:28 +00:00
Karol Sójko 16f92bdc99 fix(api-gateway): add possibility to configure keep-alive timeout (#920) 2023-11-09 15:02:32 +01:00
standardci 4c5738416a chore(release): publish new version
- @standardnotes/home-server@1.18.30
 - @standardnotes/syncing-server@1.120.5
 - @standardnotes/websockets-server@1.17.8
2023-11-09 13:26:30 +00:00
Karol Sójko 45d4920e0f fix: remove unused axios dep in subservices 2023-11-09 14:03:44 +01:00
269 changed files with 12519 additions and 4245 deletions
-7
View File
@@ -61,13 +61,6 @@ updates:
allow: allow:
- dependency-type: "direct" - dependency-type: "direct"
- package-ecosystem: "npm"
directory: "/packages/event-store"
schedule:
interval: "daily"
allow:
- dependency-type: "direct"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/packages/files" directory: "/packages/files"
schedule: schedule:
+10
View File
@@ -17,6 +17,8 @@ jobs:
name: (Self Hosting) E2E Test Suite name: (Self Hosting) E2E Test Suite
strategy: strategy:
fail-fast: false fail-fast: false
matrix:
service_proxy_type: [http, grpc]
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@@ -42,6 +44,14 @@ jobs:
env: env:
DB_TYPE: mysql DB_TYPE: mysql
CACHE_TYPE: redis CACHE_TYPE: redis
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
- name: Output Server Logs to File
run: docker compose -f docker-compose.ci.yml logs -f > logs/docker-compose.log 2>&1 &
env:
DB_TYPE: mysql
CACHE_TYPE: redis
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
- name: Wait for server to start - name: Wait for server to start
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
+3
View File
@@ -4,6 +4,9 @@ on:
push: push:
branches: [ main ] branches: [ main ]
permissions:
id-token: write
jobs: jobs:
build: build:
if: contains(github.event.head_commit.message, 'chore(release)') == false if: contains(github.event.head_commit.message, 'chore(release)') == false
Generated
+160 -83
View File
@@ -41,14 +41,14 @@ const RAW_RUNTIME_STATE =
"name": "@standardnotes/domain-events-infra",\ "name": "@standardnotes/domain-events-infra",\
"reference": "workspace:packages/domain-events-infra"\ "reference": "workspace:packages/domain-events-infra"\
},\ },\
{\
"name": "@standardnotes/event-store",\
"reference": "workspace:packages/event-store"\
},\
{\ {\
"name": "@standardnotes/files-server",\ "name": "@standardnotes/files-server",\
"reference": "workspace:packages/files"\ "reference": "workspace:packages/files"\
},\ },\
{\
"name": "@standardnotes/grpc",\
"reference": "workspace:packages/grpc"\
},\
{\ {\
"name": "@standardnotes/home-server",\ "name": "@standardnotes/home-server",\
"reference": "workspace:packages/home-server"\ "reference": "workspace:packages/home-server"\
@@ -100,8 +100,8 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-core", ["workspace:packages/domain-core"]],\ ["@standardnotes/domain-core", ["workspace:packages/domain-core"]],\
["@standardnotes/domain-events", ["workspace:packages/domain-events"]],\ ["@standardnotes/domain-events", ["workspace:packages/domain-events"]],\
["@standardnotes/domain-events-infra", ["workspace:packages/domain-events-infra"]],\ ["@standardnotes/domain-events-infra", ["workspace:packages/domain-events-infra"]],\
["@standardnotes/event-store", ["workspace:packages/event-store"]],\
["@standardnotes/files-server", ["workspace:packages/files"]],\ ["@standardnotes/files-server", ["workspace:packages/files"]],\
["@standardnotes/grpc", ["workspace:packages/grpc"]],\
["@standardnotes/home-server", ["workspace:packages/home-server"]],\ ["@standardnotes/home-server", ["workspace:packages/home-server"]],\
["@standardnotes/predicates", ["workspace:packages/predicates"]],\ ["@standardnotes/predicates", ["workspace:packages/predicates"]],\
["@standardnotes/revisions-server", ["workspace:packages/revisions"]],\ ["@standardnotes/revisions-server", ["workspace:packages/revisions"]],\
@@ -2674,6 +2674,15 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["@grpc/grpc-js", [\ ["@grpc/grpc-js", [\
["npm:1.9.11", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.11-5bb7febd65-71b8517b4f.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\
["@grpc/grpc-js", "npm:1.9.11"],\
["@grpc/proto-loader", "npm:0.7.10"],\
["@types/node", "npm:20.2.5"]\
],\
"linkType": "HARD"\
}],\
["npm:1.9.5", {\ ["npm:1.9.5", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.5-9b0cd6b5ed-5499d964d2.zip/node_modules/@grpc/grpc-js/",\ "packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.5-9b0cd6b5ed-5499d964d2.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\ "packageDependencies": [\
@@ -3575,6 +3584,22 @@ const RAW_RUNTIME_STATE =
["tar", "npm:6.1.15"]\ ["tar", "npm:6.1.15"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}],\
["npm:1.0.11", {\
"packageLocation": "./.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.11-5547f15a2b-59529a2444.zip/node_modules/@mapbox/node-pre-gyp/",\
"packageDependencies": [\
["@mapbox/node-pre-gyp", "npm:1.0.11"],\
["detect-libc", "npm:2.0.1"],\
["https-proxy-agent", "npm:5.0.1"],\
["make-dir", "npm:3.1.0"],\
["node-fetch", "virtual:0f92dfe7f9dc4fd492639d4a5b7805c2b27442bf599fd4f370b22a7966ba078f5d4525e2a8e8af29369f20e1833ed084bd52be59679efaa6c1c6c10cdbcd8baa#npm:2.6.11"],\
["nopt", "npm:5.0.0"],\
["npmlog", "npm:5.0.1"],\
["rimraf", "npm:3.0.2"],\
["semver", "npm:7.5.1"],\
["tar", "npm:6.1.15"]\
],\
"linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@nodelib/fs.scandir", [\ ["@nodelib/fs.scandir", [\
@@ -6376,9 +6401,11 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\ "packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\ "packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\ ["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@grpc/grpc-js", "npm:1.9.11"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\ ["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
@@ -6390,7 +6417,8 @@ const RAW_RUNTIME_STATE =
["@types/prettyjson", "npm:0.0.30"],\ ["@types/prettyjson", "npm:0.0.30"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\ ["agentkeepalive", "npm:4.5.0"],\
["axios", "npm:1.6.1"],\
["cors", "npm:2.8.5"],\ ["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\ ["eslint", "npm:8.41.0"],\
@@ -6423,6 +6451,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-sqs", "npm:3.427.0"],\ ["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\ ["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\ ["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@grpc/grpc-js", "npm:1.9.11"],\
["@simplewebauthn/server", "npm:8.1.1"],\ ["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\ ["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\ ["@standardnotes/api", "npm:1.26.26"],\
@@ -6431,6 +6460,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/features", "npm:1.59.7"],\ ["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/predicates", "workspace:packages/predicates"],\ ["@standardnotes/predicates", "workspace:packages/predicates"],\
["@standardnotes/responses", "npm:1.13.27"],\ ["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/security", "workspace:packages/security"],\
@@ -6578,38 +6608,6 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\ "linkType": "SOFT"\
}]\ }]\
]],\ ]],\
["@standardnotes/event-store", [\
["workspace:packages/event-store", {\
"packageLocation": "./packages/event-store/",\
"packageDependencies": [\
["@standardnotes/event-store", "workspace:packages/event-store"],\
["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\
["@types/nodemailer", "npm:6.4.8"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["inversify", "npm:6.0.1"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.9.0"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/features", [\ ["@standardnotes/features", [\
["npm:1.59.7", {\ ["npm:1.59.7", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.7-27c3e5296e-421af62d1e.zip/node_modules/@standardnotes/features/",\ "packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.7-27c3e5296e-421af62d1e.zip/node_modules/@standardnotes/features/",\
@@ -6675,6 +6673,21 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\ "linkType": "SOFT"\
}]\ }]\
]],\ ]],\
["@standardnotes/grpc", [\
["workspace:packages/grpc", {\
"packageLocation": "./packages/grpc/",\
"packageDependencies": [\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@grpc/grpc-js", "npm:1.9.11"],\
["@types/google-protobuf", "npm:3.15.10"],\
["google-protobuf", "npm:3.21.2"],\
["grpc-tools", "npm:1.12.4"],\
["grpc_tools_node_protoc_ts", "npm:5.3.3"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/home-server", [\ ["@standardnotes/home-server", [\
["workspace:packages/home-server", {\ ["workspace:packages/home-server", {\
"packageLocation": "./packages/home-server/",\ "packageLocation": "./packages/home-server/",\
@@ -6938,11 +6951,13 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-s3", "npm:3.427.0"],\ ["@aws-sdk/client-s3", "npm:3.427.0"],\
["@aws-sdk/client-sns", "npm:3.427.0"],\ ["@aws-sdk/client-sns", "npm:3.427.0"],\
["@aws-sdk/client-sqs", "npm:3.427.0"],\ ["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@grpc/grpc-js", "npm:1.9.11"],\
["@standardnotes/api", "npm:1.26.26"],\ ["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/responses", "npm:1.13.27"],\ ["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/settings", "workspace:packages/settings"],\ ["@standardnotes/settings", "workspace:packages/settings"],\
@@ -6961,7 +6976,6 @@ const RAW_RUNTIME_STATE =
["@types/uuid", "npm:9.0.3"],\ ["@types/uuid", "npm:9.0.3"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\
["cors", "npm:2.8.5"],\ ["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\ ["eslint", "npm:8.41.0"],\
@@ -7031,20 +7045,19 @@ const RAW_RUNTIME_STATE =
["@standardnotes/websockets-server", "workspace:packages/websockets"],\ ["@standardnotes/websockets-server", "workspace:packages/websockets"],\
["@aws-sdk/client-apigatewaymanagementapi", "npm:3.427.0"],\ ["@aws-sdk/client-apigatewaymanagementapi", "npm:3.427.0"],\
["@aws-sdk/client-sqs", "npm:3.427.0"],\ ["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/responses", "npm:1.13.27"],\ ["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\ ["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\ ["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\ ["@types/jest", "npm:29.5.2"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\ ["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\
["cors", "npm:2.8.5"],\ ["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\ ["eslint", "npm:8.41.0"],\
@@ -7322,6 +7335,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@types/google-protobuf", [\
["npm:3.15.10", {\
"packageLocation": "./.yarn/cache/@types-google-protobuf-npm-3.15.10-cbaa6c3e6c-29efde966f.zip/node_modules/@types/google-protobuf/",\
"packageDependencies": [\
["@types/google-protobuf", "npm:3.15.10"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/graceful-fs", [\ ["@types/graceful-fs", [\
["npm:4.1.6", {\ ["npm:4.1.6", {\
"packageLocation": "./.yarn/cache/@types-graceful-fs-npm-4.1.6-1eadcf742d-c3070ccdc9.zip/node_modules/@types/graceful-fs/",\ "packageLocation": "./.yarn/cache/@types-graceful-fs-npm-4.1.6-1eadcf742d-c3070ccdc9.zip/node_modules/@types/graceful-fs/",\
@@ -7477,16 +7499,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@types/nodemailer", [\
["npm:6.4.8", {\
"packageLocation": "./.yarn/cache/@types-nodemailer-npm-6.4.8-04975b93f9-d5afdd77ef.zip/node_modules/@types/nodemailer/",\
"packageDependencies": [\
["@types/nodemailer", "npm:6.4.8"],\
["@types/node", "npm:20.2.5"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/normalize-package-data", [\ ["@types/normalize-package-data", [\
["npm:2.4.1", {\ ["npm:2.4.1", {\
"packageLocation": "./.yarn/cache/@types-normalize-package-data-npm-2.4.1-c31c56ae6a-e87bccbf11.zip/node_modules/@types/normalize-package-data/",\ "packageLocation": "./.yarn/cache/@types-normalize-package-data-npm-2.4.1-c31c56ae6a-e87bccbf11.zip/node_modules/@types/normalize-package-data/",\
@@ -8196,6 +8208,14 @@ const RAW_RUNTIME_STATE =
["humanize-ms", "npm:1.2.1"]\ ["humanize-ms", "npm:1.2.1"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}],\
["npm:4.5.0", {\
"packageLocation": "./.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip/node_modules/agentkeepalive/",\
"packageDependencies": [\
["agentkeepalive", "npm:4.5.0"],\
["humanize-ms", "npm:1.2.1"]\
],\
"linkType": "HARD"\
}]\ }]\
]],\ ]],\
["aggregate-error", [\ ["aggregate-error", [\
@@ -8498,11 +8518,11 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["axios", [\ ["axios", [\
["npm:1.4.0", {\ ["npm:1.6.1", {\
"packageLocation": "./.yarn/cache/axios-npm-1.4.0-4d7ce8ca3e-b987e4259e.zip/node_modules/axios/",\ "packageLocation": "./.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip/node_modules/axios/",\
"packageDependencies": [\ "packageDependencies": [\
["axios", "npm:1.4.0"],\ ["axios", "npm:1.6.1"],\
["follow-redirects", "virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2"],\ ["follow-redirects", "virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2"],\
["form-data", "npm:4.0.0"],\ ["form-data", "npm:4.0.0"],\
["proxy-from-env", "npm:1.1.0"]\ ["proxy-from-env", "npm:1.1.0"]\
],\ ],\
@@ -10906,10 +10926,10 @@ const RAW_RUNTIME_STATE =
],\ ],\
"linkType": "SOFT"\ "linkType": "SOFT"\
}],\ }],\
["virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2", {\ ["virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2", {\
"packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-359bc4c55c/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\ "packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-c2d5794c26/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\
"packageDependencies": [\ "packageDependencies": [\
["follow-redirects", "virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2"],\ ["follow-redirects", "virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2"],\
["@types/debug", null],\ ["@types/debug", null],\
["debug", null]\ ["debug", null]\
],\ ],\
@@ -11336,6 +11356,22 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["google-protobuf", [\
["npm:3.15.8", {\
"packageLocation": "./.yarn/cache/google-protobuf-npm-3.15.8-75df975b6c-0b1ea24a55.zip/node_modules/google-protobuf/",\
"packageDependencies": [\
["google-protobuf", "npm:3.15.8"]\
],\
"linkType": "HARD"\
}],\
["npm:3.21.2", {\
"packageLocation": "./.yarn/cache/google-protobuf-npm-3.21.2-7c82de39ab-b376c2e47f.zip/node_modules/google-protobuf/",\
"packageDependencies": [\
["google-protobuf", "npm:3.21.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["graceful-fs", [\ ["graceful-fs", [\
["npm:4.2.11", {\ ["npm:4.2.11", {\
"packageLocation": "./.yarn/cache/graceful-fs-npm-4.2.11-24bb648a68-bf152d0ed1.zip/node_modules/graceful-fs/",\ "packageLocation": "./.yarn/cache/graceful-fs-npm-4.2.11-24bb648a68-bf152d0ed1.zip/node_modules/graceful-fs/",\
@@ -11354,6 +11390,27 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["grpc-tools", [\
["npm:1.12.4", {\
"packageLocation": "./.yarn/unplugged/grpc-tools-npm-1.12.4-956df6794d/node_modules/grpc-tools/",\
"packageDependencies": [\
["grpc-tools", "npm:1.12.4"],\
["@mapbox/node-pre-gyp", "npm:1.0.11"]\
],\
"linkType": "HARD"\
}]\
]],\
["grpc_tools_node_protoc_ts", [\
["npm:5.3.3", {\
"packageLocation": "./.yarn/unplugged/grpc_tools_node_protoc_ts-npm-5.3.3-297a345c26/node_modules/grpc_tools_node_protoc_ts/",\
"packageDependencies": [\
["grpc_tools_node_protoc_ts", "npm:5.3.3"],\
["google-protobuf", "npm:3.15.8"],\
["handlebars", "npm:4.7.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["handlebars", [\ ["handlebars", [\
["npm:4.7.7", {\ ["npm:4.7.7", {\
"packageLocation": "./.yarn/cache/handlebars-npm-4.7.7-a9ccfabf80-617b1e689b.zip/node_modules/handlebars/",\ "packageLocation": "./.yarn/cache/handlebars-npm-4.7.7-a9ccfabf80-617b1e689b.zip/node_modules/handlebars/",\
@@ -17991,14 +18048,16 @@ const Filename = {
const npath = Object.create(path__default.default); const npath = Object.create(path__default.default);
const ppath = Object.create(path__default.default.posix); const ppath = Object.create(path__default.default.posix);
npath.cwd = () => process.cwd(); npath.cwd = () => process.cwd();
ppath.cwd = () => toPortablePath(process.cwd()); ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
ppath.resolve = (...segments) => { if (process.platform === `win32`) {
if (segments.length > 0 && ppath.isAbsolute(segments[0])) { ppath.resolve = (...segments) => {
return path__default.default.posix.resolve(...segments); if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
} else { return path__default.default.posix.resolve(...segments);
return path__default.default.posix.resolve(ppath.cwd(), ...segments); } else {
} return path__default.default.posix.resolve(ppath.cwd(), ...segments);
}; }
};
}
const contains = function(pathUtils, from, to) { const contains = function(pathUtils, from, to) {
from = pathUtils.normalize(from); from = pathUtils.normalize(from);
to = pathUtils.normalize(to); to = pathUtils.normalize(to);
@@ -18012,17 +18071,13 @@ const contains = function(pathUtils, from, to) {
return null; return null;
} }
}; };
npath.fromPortablePath = fromPortablePath;
npath.toPortablePath = toPortablePath;
npath.contains = (from, to) => contains(npath, from, to); npath.contains = (from, to) => contains(npath, from, to);
ppath.contains = (from, to) => contains(ppath, from, to); ppath.contains = (from, to) => contains(ppath, from, to);
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/; const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/; const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/; const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/; const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
function fromPortablePath(p) { function fromPortablePathWin32(p) {
if (process.platform !== `win32`)
return p;
let portablePathMatch, uncPortablePathMatch; let portablePathMatch, uncPortablePathMatch;
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP)) if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
p = portablePathMatch[1]; p = portablePathMatch[1];
@@ -18032,9 +18087,7 @@ function fromPortablePath(p) {
return p; return p;
return p.replace(/\//g, `\\`); return p.replace(/\//g, `\\`);
} }
function toPortablePath(p) { function toPortablePathWin32(p) {
if (process.platform !== `win32`)
return p;
p = p.replace(/\\/g, `/`); p = p.replace(/\\/g, `/`);
let windowsPathMatch, uncWindowsPathMatch; let windowsPathMatch, uncWindowsPathMatch;
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP)) if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
@@ -18043,6 +18096,10 @@ function toPortablePath(p) {
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`; p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
return p; return p;
} }
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
npath.fromPortablePath = fromPortablePath;
npath.toPortablePath = toPortablePath;
function convertPath(targetPathUtils, sourcePath) { function convertPath(targetPathUtils, sourcePath) {
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath); return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
} }
@@ -19087,6 +19144,12 @@ class ProxiedFS extends FakeFS {
} }
} }
function direntToPortable(dirent) {
const portableDirent = dirent;
if (typeof dirent.path === `string`)
portableDirent.path = npath.toPortablePath(dirent.path);
return portableDirent;
}
class NodeFS extends BasePortableFakeFS { class NodeFS extends BasePortableFakeFS {
constructor(realFs = fs__default.default) { constructor(realFs = fs__default.default) {
super(); super();
@@ -19413,15 +19476,31 @@ class NodeFS extends BasePortableFakeFS {
async readdirPromise(p, opts) { async readdirPromise(p, opts) {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
if (opts) { if (opts) {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); if (opts.recursive && process.platform === `win32`) {
if (opts.withFileTypes) {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
} else {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
}
} else {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
}
} else { } else {
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject)); this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
} }
}); });
} }
readdirSync(p, opts) { readdirSync(p, opts) {
if (opts) { if (opts) {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts); if (opts.recursive && process.platform === `win32`) {
if (opts.withFileTypes) {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
} else {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
}
} else {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
}
} else { } else {
return this.realFs.readdirSync(npath.fromPortablePath(p)); return this.realFs.readdirSync(npath.fromPortablePath(p));
} }
@@ -22975,8 +23054,6 @@ function getPathForDisplay(p) {
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10)); const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13; const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
const builtinModules = new Set(require$$0.Module.builtinModules || Object.keys(process.binding(`natives`)));
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
function readPackageScope(checkPath) { function readPackageScope(checkPath) {
const rootSeparatorIndex = checkPath.indexOf(npath.sep); const rootSeparatorIndex = checkPath.indexOf(npath.sep);
let separatorIndex; let separatorIndex;
@@ -23085,7 +23162,7 @@ function applyPatch(pnpapi, opts) {
const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/; const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/;
const originalModuleResolveFilename = require$$0.Module._resolveFilename; const originalModuleResolveFilename = require$$0.Module._resolveFilename;
require$$0.Module._resolveFilename = function(request, parent, isMain, options) { require$$0.Module._resolveFilename = function(request, parent, isMain, options) {
if (isBuiltinModule(request)) if (require$$0.isBuiltin(request))
return request; return request;
if (!enableNativeHooks) if (!enableNativeHooks)
return originalModuleResolveFilename.call(require$$0.Module, request, parent, isMain, options); return originalModuleResolveFilename.call(require$$0.Module, request, parent, isMain, options);
@@ -24447,7 +24524,7 @@ function makeApi(runtimeState, opts) {
throw new Error(`resolveToUnqualified can not handle private import mappings`); throw new Error(`resolveToUnqualified can not handle private import mappings`);
if (request === `pnpapi`) if (request === `pnpapi`)
return npath.toPortablePath(opts.pnpapiResolution); return npath.toPortablePath(opts.pnpapiResolution);
if (considerBuiltins && isBuiltinModule(request)) if (considerBuiltins && require$$0.isBuiltin(request))
return null; return null;
const requestForDisplay = getPathForDisplay(request); const requestForDisplay = getPathForDisplay(request);
const issuerForDisplay = issuer && getPathForDisplay(issuer); const issuerForDisplay = issuer && getPathForDisplay(issuer);
@@ -24585,7 +24662,7 @@ ${brokenAncestors.map((ancestorLocator) => `Ancestor breaking the chain: ${ances
} }
} }
} else if (dependencyReference === void 0) { } else if (dependencyReference === void 0) {
if (!considerBuiltins && isBuiltinModule(request)) { if (!considerBuiltins && require$$0.isBuiltin(request)) {
if (isDependencyTreeRoot(issuerLocator)) { if (isDependencyTreeRoot(issuerLocator)) {
error = makeError( error = makeError(
ErrorCode.UNDECLARED_DEPENDENCY, ErrorCode.UNDECLARED_DEPENDENCY,
@@ -24752,7 +24829,7 @@ ${candidates.map((candidate) => `Not found: ${getPathForDisplay(candidate)}
if (unqualifiedPath === null) if (unqualifiedPath === null)
return null; return null;
const isIssuerIgnored = () => issuer !== null ? isPathIgnored(issuer) : false; const isIssuerIgnored = () => issuer !== null ? isPathIgnored(issuer) : false;
const remappedPath = (!considerBuiltins || !isBuiltinModule(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath; const remappedPath = (!considerBuiltins || !require$$0.isBuiltin(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath;
return resolveUnqualified(remappedPath, { extensions }); return resolveUnqualified(remappedPath, { extensions });
} catch (error) { } catch (error) {
if (Object.hasOwn(error, `pnpCode`)) if (Object.hasOwn(error, `pnpCode`))
+81 -46
View File
@@ -1,9 +1,9 @@
import fs from 'fs'; import fs from 'fs';
import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url'; import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url';
import path from 'path'; import path from 'path';
import moduleExports, { Module } from 'module';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { EOL } from 'os'; import { EOL } from 'os';
import moduleExports, { isBuiltin } from 'module';
import assert from 'assert'; import assert from 'assert';
const SAFE_TIME = 456789e3; const SAFE_TIME = 456789e3;
@@ -16,14 +16,16 @@ const PortablePath = {
const npath = Object.create(path); const npath = Object.create(path);
const ppath = Object.create(path.posix); const ppath = Object.create(path.posix);
npath.cwd = () => process.cwd(); npath.cwd = () => process.cwd();
ppath.cwd = () => toPortablePath(process.cwd()); ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
ppath.resolve = (...segments) => { if (process.platform === `win32`) {
if (segments.length > 0 && ppath.isAbsolute(segments[0])) { ppath.resolve = (...segments) => {
return path.posix.resolve(...segments); if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
} else { return path.posix.resolve(...segments);
return path.posix.resolve(ppath.cwd(), ...segments); } else {
} return path.posix.resolve(ppath.cwd(), ...segments);
}; }
};
}
const contains = function(pathUtils, from, to) { const contains = function(pathUtils, from, to) {
from = pathUtils.normalize(from); from = pathUtils.normalize(from);
to = pathUtils.normalize(to); to = pathUtils.normalize(to);
@@ -37,17 +39,13 @@ const contains = function(pathUtils, from, to) {
return null; return null;
} }
}; };
npath.fromPortablePath = fromPortablePath;
npath.toPortablePath = toPortablePath;
npath.contains = (from, to) => contains(npath, from, to); npath.contains = (from, to) => contains(npath, from, to);
ppath.contains = (from, to) => contains(ppath, from, to); ppath.contains = (from, to) => contains(ppath, from, to);
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/; const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/; const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/; const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/; const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
function fromPortablePath(p) { function fromPortablePathWin32(p) {
if (process.platform !== `win32`)
return p;
let portablePathMatch, uncPortablePathMatch; let portablePathMatch, uncPortablePathMatch;
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP)) if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
p = portablePathMatch[1]; p = portablePathMatch[1];
@@ -57,9 +55,7 @@ function fromPortablePath(p) {
return p; return p;
return p.replace(/\//g, `\\`); return p.replace(/\//g, `\\`);
} }
function toPortablePath(p) { function toPortablePathWin32(p) {
if (process.platform !== `win32`)
return p;
p = p.replace(/\\/g, `/`); p = p.replace(/\\/g, `/`);
let windowsPathMatch, uncWindowsPathMatch; let windowsPathMatch, uncWindowsPathMatch;
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP)) if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
@@ -68,6 +64,10 @@ function toPortablePath(p) {
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`; p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
return p; return p;
} }
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
npath.fromPortablePath = fromPortablePath;
npath.toPortablePath = toPortablePath;
function convertPath(targetPathUtils, sourcePath) { function convertPath(targetPathUtils, sourcePath) {
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath); return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
} }
@@ -902,6 +902,12 @@ class ProxiedFS extends FakeFS {
} }
} }
function direntToPortable(dirent) {
const portableDirent = dirent;
if (typeof dirent.path === `string`)
portableDirent.path = npath.toPortablePath(dirent.path);
return portableDirent;
}
class NodeFS extends BasePortableFakeFS { class NodeFS extends BasePortableFakeFS {
constructor(realFs = fs) { constructor(realFs = fs) {
super(); super();
@@ -1228,15 +1234,31 @@ class NodeFS extends BasePortableFakeFS {
async readdirPromise(p, opts) { async readdirPromise(p, opts) {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
if (opts) { if (opts) {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject)); if (opts.recursive && process.platform === `win32`) {
if (opts.withFileTypes) {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
} else {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
}
} else {
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
}
} else { } else {
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject)); this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
} }
}); });
} }
readdirSync(p, opts) { readdirSync(p, opts) {
if (opts) { if (opts) {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts); if (opts.recursive && process.platform === `win32`) {
if (opts.withFileTypes) {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
} else {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
}
} else {
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
}
} else { } else {
return this.realFs.readdirSync(npath.fromPortablePath(p)); return this.realFs.readdirSync(npath.fromPortablePath(p));
} }
@@ -1372,10 +1394,8 @@ class VirtualFS extends ProxiedFS {
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10)); const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13; const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
const HAS_LAZY_LOADED_TRANSLATORS = major > 19 || major === 19 && minor >= 3; const HAS_LAZY_LOADED_TRANSLATORS = major === 20 && minor < 6 || major === 19 && minor >= 3;
const builtinModules = new Set(Module.builtinModules || Object.keys(process.binding(`natives`)));
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
function readPackageScope(checkPath) { function readPackageScope(checkPath) {
const rootSeparatorIndex = checkPath.indexOf(npath.sep); const rootSeparatorIndex = checkPath.indexOf(npath.sep);
let separatorIndex; let separatorIndex;
@@ -1963,7 +1983,7 @@ async function resolvePrivateRequest(specifier, issuer, context, nextResolve) {
} }
async function resolve$1(originalSpecifier, context, nextResolve) { async function resolve$1(originalSpecifier, context, nextResolve) {
const { findPnpApi } = moduleExports; const { findPnpApi } = moduleExports;
if (!findPnpApi || isBuiltinModule(originalSpecifier)) if (!findPnpApi || isBuiltin(originalSpecifier))
return nextResolve(originalSpecifier, context, nextResolve); return nextResolve(originalSpecifier, context, nextResolve);
let specifier = originalSpecifier; let specifier = originalSpecifier;
const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0); const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0);
@@ -2022,31 +2042,46 @@ async function resolve$1(originalSpecifier, context, nextResolve) {
if (!HAS_LAZY_LOADED_TRANSLATORS) { if (!HAS_LAZY_LOADED_TRANSLATORS) {
const binding = process.binding(`fs`); const binding = process.binding(`fs`);
const originalfstat = binding.fstat; const originalReadFile = binding.readFileUtf8 || binding.readFileSync;
const ZIP_MASK = 4278190080; if (originalReadFile) {
const ZIP_MAGIC = 704643072; binding[originalReadFile.name] = function(...args) {
binding.fstat = function(...args) {
const [fd, useBigint, req] = args;
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
try { try {
const stats = fs.fstatSync(fd); return fs.readFileSync(args[0], {
return new Float64Array([ encoding: `utf8`,
stats.dev, flag: args[1]
stats.mode, });
stats.nlink,
stats.uid,
stats.gid,
stats.rdev,
stats.blksize,
stats.ino,
stats.size,
stats.blocks
]);
} catch { } catch {
} }
} return originalReadFile.apply(this, args);
return originalfstat.apply(this, args); };
}; } else {
const binding2 = process.binding(`fs`);
const originalfstat = binding2.fstat;
const ZIP_MASK = 4278190080;
const ZIP_MAGIC = 704643072;
binding2.fstat = function(...args) {
const [fd, useBigint, req] = args;
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
try {
const stats = fs.fstatSync(fd);
return new Float64Array([
stats.dev,
stats.mode,
stats.nlink,
stats.uid,
stats.gid,
stats.rdev,
stats.blksize,
stats.ino,
stats.size,
stats.blocks
]);
} catch {
}
}
return originalfstat.apply(this, args);
};
}
} }
const resolve = resolve$1; const resolve = resolve$1;
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+893
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -2,4 +2,4 @@ compressionLevel: mixed
enableGlobalCache: false enableGlobalCache: false
yarnPath: .yarn/releases/yarn-4.0.0-rc.51.cjs yarnPath: .yarn/releases/yarn-4.0.2.cjs
+2 -1
View File
@@ -22,6 +22,7 @@ services:
environment: environment:
DB_TYPE: "${DB_TYPE}" DB_TYPE: "${DB_TYPE}"
CACHE_TYPE: "${CACHE_TYPE}" CACHE_TYPE: "${CACHE_TYPE}"
SERVICE_PROXY_TYPE: "${SERVICE_PROXY_TYPE}"
container_name: server-ci container_name: server-ci
ports: ports:
- 3123:3000 - 3123:3000
@@ -32,7 +33,7 @@ services:
- standardnotes_self_hosted - standardnotes_self_hosted
localstack: localstack:
image: localstack/localstack:1.4 image: localstack/localstack:3.0
container_name: localstack-ci container_name: localstack-ci
expose: expose:
- 4566 - 4566
+1 -1
View File
@@ -14,7 +14,7 @@ services:
- standardnotes_self_hosted - standardnotes_self_hosted
localstack: localstack:
image: localstack/localstack:1.3 image: localstack/localstack:3.0
container_name: localstack_self_hosted container_name: localstack_self_hosted
expose: expose:
- 4566 - 4566
+10
View File
@@ -14,10 +14,18 @@ if [ -z "$SYNCING_SERVER_PORT" ]; then
export SYNCING_SERVER_PORT=3101 export SYNCING_SERVER_PORT=3101
fi fi
if [ -z "$SYNCING_SERVER_GRPC_PORT" ]; then
export SYNCING_SERVER_GRPC_PORT=50052
fi
if [ -z "$AUTH_SERVER_PORT" ]; then if [ -z "$AUTH_SERVER_PORT" ]; then
export AUTH_SERVER_PORT=3103 export AUTH_SERVER_PORT=3103
fi fi
if [ -z "$AUTH_SERVER_GRPC_PORT" ]; then
export AUTH_SERVER_GRPC_PORT=50051
fi
export FILES_SERVER_PORT=3104 export FILES_SERVER_PORT=3104
if [ -z "$REVISIONS_SERVER_PORT" ]; then if [ -z "$REVISIONS_SERVER_PORT" ]; then
@@ -352,7 +360,9 @@ export API_GATEWAY_NODE_ENV=production
export API_GATEWAY_VERSION=local export API_GATEWAY_VERSION=local
export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT
export API_GATEWAY_SYNCING_SERVER_GRPC_URL=0.0.0.0:$SYNCING_SERVER_GRPC_PORT
export API_GATEWAY_AUTH_SERVER_URL=http://localhost:$AUTH_SERVER_PORT export API_GATEWAY_AUTH_SERVER_URL=http://localhost:$AUTH_SERVER_PORT
export API_GATEWAY_AUTH_SERVER_GRPC_URL=0.0.0.0:$AUTH_SERVER_GRPC_PORT
export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:$REVISIONS_SERVER_PORT export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:$REVISIONS_SERVER_PORT
if [ -z "$PUBLIC_FILES_SERVER_URL" ]; then if [ -z "$PUBLIC_FILES_SERVER_URL" ]; then
export PUBLIC_FILES_SERVER_URL=http://localhost:3125 export PUBLIC_FILES_SERVER_URL=http://localhost:3125
+9 -1
View File
@@ -39,5 +39,13 @@
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.4" "typescript": "^5.0.4"
}, },
"packageManager": "yarn@4.0.0-rc.51" "packageManager": "yarn@4.0.2",
"dependenciesMeta": {
"grpc-tools@1.12.4": {
"unplugged": true
},
"grpc_tools_node_protoc_ts@5.3.3": {
"unplugged": true
}
}
} }
+42
View File
@@ -3,6 +3,48 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.34.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.2...@standardnotes/analytics@2.34.3) (2023-11-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.1...@standardnotes/analytics@2.34.2) (2023-11-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.34.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.0...@standardnotes/analytics@2.34.1) (2023-11-27)
### Bug Fixes
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
# [2.34.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.4...@standardnotes/analytics@2.34.0) (2023-11-27)
### Features
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
## [2.33.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.3...@standardnotes/analytics@2.33.4) (2023-11-23)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.2...@standardnotes/analytics@2.33.3) (2023-11-22)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.1...@standardnotes/analytics@2.33.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.0...@standardnotes/analytics@2.33.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
# [2.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.6...@standardnotes/analytics@2.33.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07) ## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/analytics **Note:** Version bump only for package @standardnotes/analytics
+6
View File
@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber) const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start() subscriber.start()
}) })
+2 -2
View File
@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in case "$COMMAND" in
'start-worker' ) 'start-worker' )
echo "[Docker] Starting Worker..." echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js exec node docker/entrypoint-worker.js
;; ;;
'report' ) 'report' )
echo "[Docker] Starting Usage Report Generation..." echo "[Docker] Starting Usage Report Generation..."
node docker/entrypoint-report.js exec node docker/entrypoint-report.js
;; ;;
* ) * )
+8 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/analytics", "name": "@standardnotes/analytics",
"version": "2.32.6", "version": "2.34.3",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -10,7 +10,13 @@
"author": "Standard Notes", "author": "Standard Notes",
"types": "dist/src/index.d.ts", "types": "dist/src/index.d.ts",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public",
"provenance": true
},
"repository": {
"type": "git",
"url": "git@github.com:standardnotes/server.git",
"directory": "packages/analytics"
}, },
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
+2
View File
@@ -6,7 +6,9 @@ VERSION=development
PORT=3000 PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000 SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
SYNCING_SERVER_GRPC_URL=http://syncing_server_js:50052
AUTH_SERVER_URL=http://auth:3000 AUTH_SERVER_URL=http://auth:3000
AUTH_SERVER_GRPC_URL=http://auth:50051
WEB_SOCKET_SERVER_URL=http://websockets:3000 WEB_SOCKET_SERVER_URL=http://websockets:3000
PAYMENTS_SERVER_URL=http://payments:3000 PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000 FILES_SERVER_URL=http://files:3000
+158
View File
@@ -3,6 +3,164 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.87.4](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.3...@standardnotes/api-gateway@1.87.4) (2023-12-01)
### Bug Fixes
* **api-gateway:** home server home page ([#949](https://github.com/standardnotes/server/issues/949)) ([a82192d](https://github.com/standardnotes/server/commit/a82192db42dfbb3eea4ac6af40ef2b3d6126e5a3))
## [1.87.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.2...@standardnotes/api-gateway@1.87.3) (2023-11-28)
### Bug Fixes
* **api-gateway:** add session to response locals from web socket middleware ([4cc647a](https://github.com/standardnotes/server/commit/4cc647ac07b2471d6616a913bcdca431c506fd0e))
## [1.87.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.1...@standardnotes/api-gateway@1.87.2) (2023-11-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.87.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.0...@standardnotes/api-gateway@1.87.1) (2023-11-27)
### Bug Fixes
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
# [1.87.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.6...@standardnotes/api-gateway@1.87.0) (2023-11-27)
### Features
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/api-gateway/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
## [1.86.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.5...@standardnotes/api-gateway@1.86.6) (2023-11-23)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.86.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.4...@standardnotes/api-gateway@1.86.5) (2023-11-22)
### Bug Fixes
* error handling on gRPC ([#937](https://github.com/standardnotes/api-gateway/issues/937)) ([8f23c8a](https://github.com/standardnotes/api-gateway/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
## [1.86.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.3...@standardnotes/api-gateway@1.86.4) (2023-11-22)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.86.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.2...@standardnotes/api-gateway@1.86.3) (2023-11-21)
### Bug Fixes
* **api-gateway:** add meta field to grpc sync calls ([#934](https://github.com/standardnotes/api-gateway/issues/934)) ([c5c24b3](https://github.com/standardnotes/api-gateway/commit/c5c24b3ac9dbd559d96adc56270d724a3156ebd4))
## [1.86.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.1...@standardnotes/api-gateway@1.86.2) (2023-11-20)
### Bug Fixes
* define grpc max message size ([bfef16c](https://github.com/standardnotes/api-gateway/commit/bfef16ce3757b57ea1cb0cb7417d6bc935a52321))
## [1.86.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.0...@standardnotes/api-gateway@1.86.1) (2023-11-20)
### Bug Fixes
* setting gzip as default compression on grpc calls ([#933](https://github.com/standardnotes/api-gateway/issues/933)) ([2dff6a2](https://github.com/standardnotes/api-gateway/commit/2dff6a2ed3d105ca65996d47321a811e22e25099))
# [1.86.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.85.1...@standardnotes/api-gateway@1.86.0) (2023-11-20)
### Features
* **grpc:** add syncing protocol buffers ([#930](https://github.com/standardnotes/api-gateway/issues/930)) ([5b84f07](https://github.com/standardnotes/api-gateway/commit/5b84f078c6ae6330706895f7c57b67ff8c8d18ae))
## [1.85.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.85.0...@standardnotes/api-gateway@1.85.1) (2023-11-16)
### Bug Fixes
* **api-gateway:** remove overly verbose debug messages ([ed05ea5](https://github.com/standardnotes/api-gateway/commit/ed05ea553f605234cd8803e633f3c07429877dbb))
# [1.85.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.84.1...@standardnotes/api-gateway@1.85.0) (2023-11-16)
### Features
* add debug logs for grpc communication ([6391a01](https://github.com/standardnotes/api-gateway/commit/6391a01b5703db23b566710d0520c1197c46144b))
## [1.84.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.84.0...@standardnotes/api-gateway@1.84.1) (2023-11-16)
### Bug Fixes
* **api-gateway:** bindings ([78fbeb5](https://github.com/standardnotes/api-gateway/commit/78fbeb595f9e213688bcb2a031fba2aa3974cc6a))
# [1.84.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.5...@standardnotes/api-gateway@1.84.0) (2023-11-16)
### Features
* add grpc sessions validation server ([#928](https://github.com/standardnotes/api-gateway/issues/928)) ([4f62cac](https://github.com/standardnotes/api-gateway/commit/4f62cac213a6b5f503040ef6319e5198967974ce))
## [1.83.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.4...@standardnotes/api-gateway@1.83.5) (2023-11-14)
### Bug Fixes
* **api-gateway:** remove the verify body function ([3ddd671](https://github.com/standardnotes/api-gateway/commit/3ddd671c4797482a396844e804b4b45b82dbff2d))
* **api-gateway:** remove unused imports ([fd997f4](https://github.com/standardnotes/api-gateway/commit/fd997f4849ed01ef3ae4baf52b5895012fa711d4))
## [1.83.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.3...@standardnotes/api-gateway@1.83.4) (2023-11-14)
### Bug Fixes
* **api-gateway:** add verification if json is valid on request ([420bf9e](https://github.com/standardnotes/api-gateway/commit/420bf9ec5460a9693cc382e9164b4bdbb9b769a1))
* **api-gateway:** buffer encoding ([2823ed8](https://github.com/standardnotes/api-gateway/commit/2823ed8612cb9797d43e847edac5e2bdc0fe7426))
* **api-gateway:** checking for buffer length ([f65809e](https://github.com/standardnotes/api-gateway/commit/f65809ef3052d05df2e3f012a9b6340d18a6deae))
## [1.83.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.2...@standardnotes/api-gateway@1.83.3) (2023-11-13)
### Bug Fixes
* **api-gateway:** add application version to the error logs ([daed1a7](https://github.com/standardnotes/api-gateway/commit/daed1a77a02559a8487896b6fb8299befe8a2d96))
* **api-gateway:** add request method to the debug logs ([b39eb09](https://github.com/standardnotes/api-gateway/commit/b39eb09d91f0ea9482d27578faecdf57ed2ea48e))
## [1.83.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.1...@standardnotes/api-gateway@1.83.2) (2023-11-13)
### Bug Fixes
* **api-gateway:** debug log on error thrown body representation ([c8bf4ab](https://github.com/standardnotes/api-gateway/commit/c8bf4ab3a0ab757092077fc594e3ca7e090116b4))
## [1.83.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.0...@standardnotes/api-gateway@1.83.1) (2023-11-13)
### Bug Fixes
* **api-gateway:** add debug logs for errors on parsing ([60686dc](https://github.com/standardnotes/api-gateway/commit/60686dcdbd59c0d99cd1857a82ad62baed088b25))
# [1.83.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.1...@standardnotes/api-gateway@1.83.0) (2023-11-10)
### Bug Fixes
* **api-gateway:** add more info on error logs ([f997501](https://github.com/standardnotes/api-gateway/commit/f99750169f4d24cdc7530184af2230c687f3e166))
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/api-gateway/issues/924)) ([daad76d](https://github.com/standardnotes/api-gateway/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
## [1.82.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.0...@standardnotes/api-gateway@1.82.1) (2023-11-10)
### Bug Fixes
* **api-gateway:** websockets calls logs severity ([a9b1543](https://github.com/standardnotes/api-gateway/commit/a9b1543e204afeab1fa2e008327c39cf306a247c))
# [1.82.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.14...@standardnotes/api-gateway@1.82.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/api-gateway/issues/923)) ([c24353c](https://github.com/standardnotes/api-gateway/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.81.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.13...@standardnotes/api-gateway@1.81.14) (2023-11-10)
### Bug Fixes
* **api-gateway:** add logs about calling web sockets with minimal format ([5d3fb9a](https://github.com/standardnotes/api-gateway/commit/5d3fb9a537f6971cfe8ae3c5ea449806cc4de8a0))
## [1.81.13](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.12...@standardnotes/api-gateway@1.81.13) (2023-11-09)
### Bug Fixes
* **api-gateway:** add possibility to configure keep-alive timeout ([#920](https://github.com/standardnotes/api-gateway/issues/920)) ([16f92bd](https://github.com/standardnotes/api-gateway/commit/16f92bdc990ded5c3f1fe5af1e6e4a113a9954de))
## [1.81.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.11...@standardnotes/api-gateway@1.81.12) (2023-11-09) ## [1.81.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.11...@standardnotes/api-gateway@1.81.12) (2023-11-09)
### Bug Fixes ### Bug Fixes
+20 -4
View File
@@ -90,8 +90,15 @@ void container.load().then((container) => {
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger) const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
server.setErrorConfig((app) => { server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => { app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack) logger.error(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${request.headers['x-application-version']}] Error thrown: ${error.stack}`,
)
logger.debug(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
request.headers['x-application-version']
}] Request body: ${JSON.stringify(request.body)}`,
)
response.status(500).send({ response.status(500).send({
error: { error: {
@@ -102,9 +109,18 @@ void container.load().then((container) => {
}) })
}) })
const serverInstance = server.build() const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT')) const keepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true) ? +env.get('HTTP_KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`) logger.info(`Server started on port ${process.env.PORT}`)
}) })
+1 -1
View File
@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
case "$COMMAND" in case "$COMMAND" in
'start-web' ) 'start-web' )
echo "Starting Web..." echo "Starting Web..."
node docker/entrypoint-server.js exec node docker/entrypoint-server.js
;; ;;
* ) * )
+12 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/api-gateway", "name": "@standardnotes/api-gateway",
"version": "1.81.12", "version": "1.87.4",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -11,11 +11,16 @@
"dist/src/**/*.js", "dist/src/**/*.js",
"dist/src/**/*.d.ts" "dist/src/**/*.d.ts"
], ],
"repository": "git@github.com:standardnotes/api-gateway.git", "repository": {
"type": "git",
"url": "git@github.com:standardnotes/server.git",
"directory": "packages/api-gateway"
},
"author": "Karol Sójko <karol@standardnotes.com>", "author": "Karol Sójko <karol@standardnotes.com>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public",
"provenance": true
}, },
"scripts": { "scripts": {
"clean": "rm -fr dist", "clean": "rm -fr dist",
@@ -26,12 +31,15 @@
"start": "yarn node dist/bin/server.js" "start": "yarn node dist/bin/server.js"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.11",
"@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/grpc": "workspace:^",
"@standardnotes/security": "workspace:*", "@standardnotes/security": "workspace:*",
"@standardnotes/time": "workspace:*", "@standardnotes/time": "workspace:*",
"axios": "^1.1.3", "agentkeepalive": "^4.5.0",
"axios": "^1.6.1",
"cors": "2.8.5", "cors": "2.8.5",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.2", "express": "^4.18.2",
+126 -20
View File
@@ -1,27 +1,40 @@
import * as winston from 'winston' import * as winston from 'winston'
// eslint-disable-next-line @typescript-eslint/no-var-requires import * as AgentKeepAlive from 'agentkeepalive'
const axios = require('axios') import * as grpc from '@grpc/grpc-js'
import { AxiosInstance } from 'axios' import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis' import Redis from 'ioredis'
import { Container } from 'inversify' import { Container } from 'inversify'
import { Timer, TimerInterface } from '@standardnotes/time' import { Timer, TimerInterface } from '@standardnotes/time'
import { Env } from './Env' import { Env } from './Env'
import { TYPES } from './Types' import { TYPES } from './Types'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { HttpServiceProxy } from '../Service/Http/HttpServiceProxy' import { HttpServiceProxy } from '../Service/Http/HttpServiceProxy'
import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware' import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache' import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware' import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware'
import { InMemoryCrossServiceTokenCache } from '../Infra/InMemory/InMemoryCrossServiceTokenCache' import { InMemoryCrossServiceTokenCache } from '../Infra/InMemory/InMemoryCrossServiceTokenCache'
import { DirectCallServiceProxy } from '../Service/Proxy/DirectCallServiceProxy' import { DirectCallServiceProxy } from '../Service/DirectCall/DirectCallServiceProxy'
import { ServiceContainerInterface } from '@standardnotes/domain-core' import { MapperInterface, ServiceContainerInterface } from '@standardnotes/domain-core'
import { EndpointResolverInterface } from '../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../Service/Resolver/EndpointResolverInterface'
import { EndpointResolver } from '../Service/Resolver/EndpointResolver' import { EndpointResolver } from '../Service/Resolver/EndpointResolver'
import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware' import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware'
import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware' import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware'
import { Transform } from 'stream' import { Transform } from 'stream'
import {
ISessionsClient,
ISyncingClient,
SessionsClient,
SyncRequest,
SyncResponse,
SyncingClient,
} from '@standardnotes/grpc'
import { GRPCServiceProxy } from '../Service/gRPC/GRPCServiceProxy'
import { GRPCSyncingServerServiceProxy } from '../Service/gRPC/GRPCSyncingServerServiceProxy'
import { SyncResponseHttpRepresentation } from '../Mapping/Sync/Http/SyncResponseHttpRepresentation'
import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper'
import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper'
export class ContainerConfigLoader { export class ContainerConfigLoader {
async load(configuration?: { async load(configuration?: {
@@ -70,7 +83,19 @@ export class ContainerConfigLoader {
container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis) container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis)
} }
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(axios.create()) const httpAgentKeepAliveTimeout = env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 4_000
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(
axios.create({
httpAgent: new AgentKeepAlive({
keepAlive: true,
timeout: 2 * httpAgentKeepAliveTimeout,
freeSocketTimeout: httpAgentKeepAliveTimeout,
}),
}),
)
// env vars // env vars
container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true)) container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))
@@ -105,19 +130,6 @@ export class ContainerConfigLoader {
// Services // Services
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer()) container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) {
if (!configuration?.serviceContainer) {
throw new Error('Service container is required when configured for home server')
}
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
}
if (isConfiguredForHomeServer) { if (isConfiguredForHomeServer) {
container container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache) .bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
@@ -131,6 +143,100 @@ export class ContainerConfigLoader {
.bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver) .bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
.toConstantValue(new EndpointResolver(isConfiguredForHomeServer)) .toConstantValue(new EndpointResolver(isConfiguredForHomeServer))
if (isConfiguredForHomeServer) {
if (!configuration?.serviceContainer) {
throw new Error('Service container is required when configured for home server')
}
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
const isConfiguredForGRPCProxy = env.get('SERVICE_PROXY_TYPE', true) === 'grpc'
if (isConfiguredForGRPCProxy) {
container.bind(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL).toConstantValue(env.get('AUTH_SERVER_GRPC_URL'))
container.bind(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL).toConstantValue(env.get('SYNCING_SERVER_GRPC_URL'))
const grpcAgentKeepAliveTimeout = env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 8_000
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
: 1024 * 1024 * 50
container.bind<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient).toConstantValue(
new SessionsClient(
container.get<string>(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL),
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.default_compression_level': 2,
'grpc.max_receive_message_length': grpcMaxMessageSize,
'grpc.max_send_message_length': grpcMaxMessageSize,
},
),
)
container.bind<ISyncingClient>(TYPES.ApiGateway_GRPCSyncingClient).toConstantValue(
new SyncingClient(
container.get<string>(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL),
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.default_compression_level': 2,
'grpc.max_receive_message_length': grpcMaxMessageSize,
'grpc.max_send_message_length': grpcMaxMessageSize,
},
),
)
container
.bind<MapperInterface<Record<string, unknown>, SyncRequest>>(TYPES.Mapper_SyncRequestGRPCMapper)
.toConstantValue(new SyncRequestGRPCMapper())
container
.bind<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(TYPES.Mapper_SyncResponseGRPCMapper)
.toConstantValue(new SyncResponseGRPCMapper())
container
.bind<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy)
.toConstantValue(
new GRPCSyncingServerServiceProxy(
container.get<ISyncingClient>(TYPES.ApiGateway_GRPCSyncingClient),
container.get<MapperInterface<Record<string, unknown>, SyncRequest>>(TYPES.Mapper_SyncRequestGRPCMapper),
container.get<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(
TYPES.Mapper_SyncResponseGRPCMapper,
),
),
)
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new GRPCServiceProxy(
container.get<AxiosInstance>(TYPES.ApiGateway_HTTPClient),
container.get<string>(TYPES.ApiGateway_AUTH_SERVER_URL),
container.get<string>(TYPES.ApiGateway_SYNCING_SERVER_JS_URL),
container.get<string>(TYPES.ApiGateway_PAYMENTS_SERVER_URL),
container.get<string>(TYPES.ApiGateway_FILES_SERVER_URL),
container.get<string>(TYPES.ApiGateway_WEB_SOCKET_SERVER_URL),
container.get<string>(TYPES.ApiGateway_REVISIONS_SERVER_URL),
container.get<string>(TYPES.ApiGateway_EMAIL_SERVER_URL),
container.get<number>(TYPES.ApiGateway_HTTP_CALL_TIMEOUT),
container.get<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache),
container.get<winston.Logger>(TYPES.ApiGateway_Logger),
container.get<TimerInterface>(TYPES.ApiGateway_Timer),
container.get<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient),
container.get<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy),
),
)
} else {
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
}
}
logger.debug('Configuration complete') logger.debug('Configuration complete')
return container return container
@@ -5,6 +5,8 @@ export const TYPES = {
// env vars // env vars
ApiGateway_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'), ApiGateway_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'),
ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'), ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'),
ApiGateway_AUTH_SERVER_GRPC_URL: Symbol.for('ApiGateway_AUTH_SERVER_GRPC_URL'),
ApiGateway_SYNCING_SERVER_GRPC_URL: Symbol.for('ApiGateway_SYNCING_SERVER_GRPC_URL'),
ApiGateway_PAYMENTS_SERVER_URL: Symbol.for('ApiGateway_PAYMENTS_SERVER_URL'), ApiGateway_PAYMENTS_SERVER_URL: Symbol.for('ApiGateway_PAYMENTS_SERVER_URL'),
ApiGateway_FILES_SERVER_URL: Symbol.for('ApiGateway_FILES_SERVER_URL'), ApiGateway_FILES_SERVER_URL: Symbol.for('ApiGateway_FILES_SERVER_URL'),
ApiGateway_REVISIONS_SERVER_URL: Symbol.for('ApiGateway_REVISIONS_SERVER_URL'), ApiGateway_REVISIONS_SERVER_URL: Symbol.for('ApiGateway_REVISIONS_SERVER_URL'),
@@ -23,9 +25,15 @@ export const TYPES = {
ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'), ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'),
ApiGateway_WebSocketAuthMiddleware: Symbol.for('ApiGateway_WebSocketAuthMiddleware'), ApiGateway_WebSocketAuthMiddleware: Symbol.for('ApiGateway_WebSocketAuthMiddleware'),
ApiGateway_SubscriptionTokenAuthMiddleware: Symbol.for('ApiGateway_SubscriptionTokenAuthMiddleware'), ApiGateway_SubscriptionTokenAuthMiddleware: Symbol.for('ApiGateway_SubscriptionTokenAuthMiddleware'),
// Mapping
Mapper_SyncRequestGRPCMapper: Symbol.for('Mapper_SyncRequestGRPCMapper'),
Mapper_SyncResponseGRPCMapper: Symbol.for('Mapper_SyncResponseGRPCMapper'),
// Services // Services
ApiGateway_GRPCSyncingServerServiceProxy: Symbol.for('ApiGateway_GRPCSyncingServerServiceProxy'),
ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'), ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'),
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'), ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),
ApiGateway_Timer: Symbol.for('ApiGateway_Timer'), ApiGateway_Timer: Symbol.for('ApiGateway_Timer'),
ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'), ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'),
ApiGateway_GRPCSessionsClient: Symbol.for('ApiGateway_GRPCSessionsClient'),
ApiGateway_GRPCSyncingClient: Symbol.for('ApiGateway_GRPCSyncingClient'),
} }
@@ -7,7 +7,7 @@ import { AxiosError } from 'axios'
import { Logger } from 'winston' import { Logger } from 'winston'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
export abstract class AuthMiddleware extends BaseMiddleware { export abstract class AuthMiddleware extends BaseMiddleware {
constructor( constructor(
@@ -49,6 +49,8 @@ export abstract class AuthMiddleware extends BaseMiddleware {
return return
} }
this.logger.debug('[AuthMiddleware] Fetched cross-service token from underlying service')
crossServiceToken = (authResponse.data as { authToken: string }).authToken crossServiceToken = (authResponse.data as { authToken: string }).authToken
crossServiceTokenFetchedFromCache = false crossServiceTokenFetchedFromCache = false
} }
@@ -1,9 +1,18 @@
import { Request, Response } from 'express'
import { BaseHttpController, all, controller, results } from 'inversify-express-utils' import { BaseHttpController, all, controller, results } from 'inversify-express-utils'
@controller('') @controller('')
export class FallbackController extends BaseHttpController { export class FallbackController extends BaseHttpController {
@all('*') @all('*')
public async fallback(): Promise<results.NotFoundResult> { public async fallback(request: Request, response: Response): Promise<void | results.NotFoundResult> {
if (request.path === '/' && request.method === 'GET') {
response.send(
'<!DOCTYPE html><html lang="en"><head><meta name="robots" content="noindex"></head><body>Your home server is up and running! Enter the URL of this page into Standard Notes when registering or signing in to begin using your home server.</body></html>',
)
return
}
return this.notFound() return this.notFound()
} }
} }
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { controller, all, BaseHttpController, httpPost, httpGet, results, httpDelete } from 'inversify-express-utils' import { controller, all, BaseHttpController, httpPost, httpGet, results, httpDelete } from 'inversify-express-utils'
import { TYPES } from '../Bootstrap/Types' import { TYPES } from '../Bootstrap/Types'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
@controller('') @controller('')
export class LegacyController extends BaseHttpController { export class LegacyController extends BaseHttpController {
@@ -5,7 +5,7 @@ import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types' import { TYPES } from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { AuthMiddleware } from './AuthMiddleware' import { AuthMiddleware } from './AuthMiddleware'
@injectable() @injectable()
@@ -5,7 +5,7 @@ import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types' import { TYPES } from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { AuthMiddleware } from './AuthMiddleware' import { AuthMiddleware } from './AuthMiddleware'
@injectable() @injectable()
@@ -60,6 +60,7 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] }) const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
response.locals.user = decodedToken.user response.locals.user = decodedToken.user
response.locals.session = decodedToken.session
response.locals.roles = decodedToken.roles response.locals.roles = decodedToken.roles
} catch (error) { } catch (error) {
const errorMessage = (error as AxiosError).isAxiosError const errorMessage = (error as AxiosError).isAxiosError
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1') @controller('/v1')
@@ -3,7 +3,7 @@ import { Request, Response } from 'express'
import { controller, BaseHttpController, httpPost, httpGet, httpDelete } from 'inversify-express-utils' import { controller, BaseHttpController, httpPost, httpGet, httpDelete } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/authenticators') @controller('/v1/authenticators')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/files') @controller('/v1/files')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify' import { inject } from 'inversify'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v1') @controller('/v1')
export class InvoicesController extends BaseHttpController { export class InvoicesController extends BaseHttpController {
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/items', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v1/items', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/messages', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v1/messages', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/offline') @controller('/v1/offline')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { all, BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { all, BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v1') @controller('/v1')
export class PaymentsController extends BaseHttpController { export class PaymentsController extends BaseHttpController {
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/sessions') @controller('/v1/sessions')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify' import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/subscription-invites') @controller('/v1/subscription-invites')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/subscription-tokens') @controller('/v1/subscription-tokens')
@@ -13,7 +13,7 @@ import {
} from 'inversify-express-utils' } from 'inversify-express-utils'
import { Logger } from 'winston' import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod' import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@@ -4,7 +4,7 @@ import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-
import { Logger } from 'winston' import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/sockets') @controller('/v1/sockets')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v2') @controller('/v2')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify' import { inject } from 'inversify'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v2') @controller('/v2')
export class PaymentsControllerV2 extends BaseHttpController { export class PaymentsControllerV2 extends BaseHttpController {
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils' import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface' import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v2', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware) @controller('/v2', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -0,0 +1,94 @@
import { MapperInterface, Validator } from '@standardnotes/domain-core'
import { ItemHash, SyncRequest } from '@standardnotes/grpc'
export class SyncRequestGRPCMapper implements MapperInterface<Record<string, unknown>, SyncRequest> {
toDomain(_projection: SyncRequest): Record<string, unknown> {
throw new Error('Method not implemented.')
}
toProjection(domain: Record<string, unknown>): SyncRequest {
const syncRequest = new SyncRequest()
if ('items' in domain) {
syncRequest.setItemsList((domain.items as Record<string, unknown>[]).map((item) => this.createItemHash(item)))
}
if ('shared_vault_uuids' in domain) {
const sharedVaultUuidsValidation = Validator.isNotEmpty(domain.shared_vault_uuids)
if (!sharedVaultUuidsValidation.isFailed()) {
syncRequest.setSharedVaultUuidsList(domain.shared_vault_uuids as string[])
}
}
if ('compute_integrity' in domain) {
syncRequest.setComputeIntegrity(!!domain.compute_integrity)
}
if ('sync_token' in domain) {
syncRequest.setSyncToken(domain.sync_token as string)
}
if ('cursor_token' in domain) {
syncRequest.setCursorToken(domain.cursor_token as string)
}
if ('limit' in domain) {
syncRequest.setLimit(domain.limit as number)
}
if ('content_type' in domain) {
syncRequest.setContentType(domain.content_type as string)
}
if ('api' in domain) {
syncRequest.setApiVersion(domain.api as string)
}
return syncRequest
}
private createItemHash(record: Record<string, unknown>): ItemHash {
const itemHash = new ItemHash()
itemHash.setUuid(record.uuid as string)
if (record.content) {
itemHash.setContent(record.content as string)
}
if (record.content_type) {
itemHash.setContentType(record.content_type as string)
}
if (record.deleted !== undefined) {
itemHash.setDeleted(!!record.deleted)
}
if (record.duplicate_of) {
itemHash.setDuplicateOf(record.duplicate_of as string)
}
if (record.auth_hash) {
itemHash.setAuthHash(record.auth_hash as string)
}
if (record.enc_item_key) {
itemHash.setEncItemKey(record.enc_item_key as string)
}
if (record.items_key_id) {
itemHash.setItemsKeyId(record.items_key_id as string)
}
if (record.key_system_identifier) {
itemHash.setKeySystemIdentifier(record.key_system_identifier as string)
}
if (record.shared_vault_uuid) {
itemHash.setSharedVaultUuid(record.shared_vault_uuid as string)
}
if (record.created_at) {
itemHash.setCreatedAt(record.created_at as string)
}
if (record.created_at_timestamp) {
itemHash.setCreatedAtTimestamp(record.created_at_timestamp as number)
}
if (record.updated_at) {
itemHash.setUpdatedAt(record.updated_at as string)
}
if (record.updated_at_timestamp) {
itemHash.setUpdatedAtTimestamp(record.updated_at_timestamp as number)
}
return itemHash
}
}
@@ -0,0 +1,164 @@
import { MapperInterface } from '@standardnotes/domain-core'
import {
ItemConflictRepresentation,
ItemHashRepresentation,
ItemRepresentation,
MessageRepresentation,
NotificationRepresentation,
SavedItemRepresentation,
SharedVaultInviteRepresentation,
SharedVaultRepresentation,
SyncResponse,
} from '@standardnotes/grpc'
import { ItemHttpRepresentation } from '../Http/ItemHttpRepresentation'
import { SavedItemHttpRepresentation } from '../Http/SavedItemHttpRepresentation'
import { ItemConflictHttpRepresentation } from '../Http/ItemConflictHttpRepresentation'
import { ItemHashHttpRepresentation } from '../Http/ItemHashHttpRepresentation'
import { MessageHttpRepresentation } from '../Http/MessageHttpRepresentation'
import { SharedVaultHttpRepresentation } from '../Http/SharedVaultHttpRepresentation'
import { SharedVaultInviteHttpRepresentation } from '../Http/SharedVaultInviteHttpRepresentation'
import { NotificationHttpRepresentation } from '../Http/NotificationHttpRepresentation'
import { SyncResponseHttpRepresentation } from '../Http/SyncResponseHttpRepresentation'
export class SyncResponseGRPCMapper implements MapperInterface<SyncResponse, SyncResponseHttpRepresentation> {
toDomain(_projection: SyncResponseHttpRepresentation): SyncResponse {
throw new Error('Method not implemented.')
}
toProjection(domain: SyncResponse): SyncResponseHttpRepresentation {
return {
retrieved_items: domain.getRetrievedItemsList().map((item) => this.createItem(item)),
saved_items: domain.getSavedItemsList().map((item) => this.createSavedItem(item)),
conflicts: domain.getConflictsList().map((conflict) => this.createConflict(conflict)),
sync_token: domain.getSyncToken(),
cursor_token: domain.getCursorToken(),
messages: domain.getMessagesList().map((message) => this.createMessage(message)),
shared_vaults: domain.getSharedVaultsList().map((sharedVault) => this.createSharedVault(sharedVault)),
shared_vault_invites: domain
.getSharedVaultInvitesList()
.map((sharedVaultInvite) => this.createSharedVaultInvite(sharedVaultInvite)),
notifications: domain.getNotificationsList().map((notification) => this.createNotification(notification)),
}
}
private createNotification(notification: NotificationRepresentation): NotificationHttpRepresentation {
return {
uuid: notification.getUuid(),
user_uuid: notification.getUserUuid(),
type: notification.getType(),
payload: notification.getPayload(),
created_at_timestamp: notification.getCreatedAtTimestamp(),
updated_at_timestamp: notification.getUpdatedAtTimestamp(),
}
}
private createSharedVaultInvite(
sharedVaultInvite: SharedVaultInviteRepresentation,
): SharedVaultInviteHttpRepresentation {
return {
uuid: sharedVaultInvite.getUuid(),
shared_vault_uuid: sharedVaultInvite.getSharedVaultUuid(),
user_uuid: sharedVaultInvite.getUserUuid(),
sender_uuid: sharedVaultInvite.getSenderUuid(),
encrypted_message: sharedVaultInvite.getEncryptedMessage(),
permission: sharedVaultInvite.getPermission(),
created_at_timestamp: sharedVaultInvite.getCreatedAtTimestamp(),
updated_at_timestamp: sharedVaultInvite.getUpdatedAtTimestamp(),
}
}
private createSharedVault(sharedVault: SharedVaultRepresentation): SharedVaultHttpRepresentation {
return {
uuid: sharedVault.getUuid(),
user_uuid: sharedVault.getUserUuid(),
file_upload_bytes_used: sharedVault.getFileUploadBytesUsed(),
created_at_timestamp: sharedVault.getCreatedAtTimestamp(),
updated_at_timestamp: sharedVault.getUpdatedAtTimestamp(),
}
}
private createMessage(message: MessageRepresentation): MessageHttpRepresentation {
return {
uuid: message.getUuid(),
recipient_uuid: message.getRecipientUuid(),
sender_uuid: message.getSenderUuid(),
encrypted_message: message.getEncryptedMessage(),
replaceability_identifier: message.getReplaceabilityIdentifier() ?? null,
created_at_timestamp: message.getCreatedAtTimestamp(),
updated_at_timestamp: message.getUpdatedAtTimestamp(),
}
}
private createConflict(conflict: ItemConflictRepresentation): ItemConflictHttpRepresentation {
return {
server_item: conflict.hasServerItem()
? this.createItem(conflict.getServerItem() as ItemRepresentation)
: undefined,
unsaved_item: conflict.hasUnsavedItem()
? this.createItemHash(conflict.getUnsavedItem() as ItemHashRepresentation)
: undefined,
type: conflict.getType(),
}
}
private createItemHash(itemHash: ItemHashRepresentation): ItemHashHttpRepresentation {
return {
uuid: itemHash.getUuid(),
user_uuid: itemHash.getUserUuid(),
content: itemHash.hasContent() ? (itemHash.getContent() as string) : undefined,
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
deleted: itemHash.hasDeleted() ? (itemHash.getDeleted() as boolean) : false,
duplicate_of: itemHash.hasDuplicateOf() ? (itemHash.getDuplicateOf() as string) : null,
auth_hash: itemHash.hasAuthHash() ? (itemHash.getAuthHash() as string) : undefined,
enc_item_key: itemHash.hasEncItemKey() ? (itemHash.getEncItemKey() as string) : undefined,
items_key_id: itemHash.hasItemsKeyId() ? (itemHash.getItemsKeyId() as string) : undefined,
key_system_identifier: itemHash.hasKeySystemIdentifier() ? (itemHash.getKeySystemIdentifier() as string) : null,
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
created_at: itemHash.hasCreatedAt() ? (itemHash.getCreatedAt() as string) : undefined,
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? (itemHash.getCreatedAtTimestamp() as number) : undefined,
updated_at: itemHash.hasUpdatedAt() ? (itemHash.getUpdatedAt() as string) : undefined,
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? (itemHash.getUpdatedAtTimestamp() as number) : undefined,
}
}
private createSavedItem(item: SavedItemRepresentation): SavedItemHttpRepresentation {
return {
uuid: item.getUuid(),
duplicate_of: item.hasDuplicateOf() ? (item.getDuplicateOf() as string) : null,
content_type: item.getContentType(),
auth_hash: item.hasAuthHash() ? (item.getAuthHash() as string) : null,
deleted: item.getDeleted(),
created_at: item.getCreatedAt(),
created_at_timestamp: item.getCreatedAtTimestamp(),
updated_at: item.getUpdatedAt(),
updated_at_timestamp: item.getUpdatedAtTimestamp(),
key_system_identifier: item.hasKeySystemIdentifier() ? (item.getKeySystemIdentifier() as string) : null,
shared_vault_uuid: item.hasSharedVaultUuid() ? (item.getSharedVaultUuid() as string) : null,
user_uuid: item.hasUserUuid() ? (item.getUserUuid() as string) : null,
last_edited_by_uuid: item.hasLastEditedByUuid() ? (item.getLastEditedByUuid() as string) : null,
}
}
private createItem(item: ItemRepresentation): ItemHttpRepresentation {
return {
uuid: item.getUuid(),
items_key_id: item.hasItemsKeyId() ? (item.getItemsKeyId() as string) : null,
duplicate_of: item.hasDuplicateOf() ? (item.getDuplicateOf() as string) : null,
enc_item_key: item.hasEncItemKey() ? (item.getEncItemKey() as string) : null,
content: item.hasContent() ? (item.getContent() as string) : null,
content_type: item.getContentType(),
auth_hash: item.hasAuthHash() ? (item.getAuthHash() as string) : null,
deleted: item.getDeleted(),
created_at: item.getCreatedAt(),
created_at_timestamp: item.getCreatedAtTimestamp(),
updated_at: item.getUpdatedAt(),
updated_at_timestamp: item.getUpdatedAtTimestamp(),
updated_with_session: item.hasUpdatedWithSession() ? (item.getUpdatedWithSession() as string) : null,
key_system_identifier: item.hasKeySystemIdentifier() ? (item.getKeySystemIdentifier() as string) : null,
shared_vault_uuid: item.hasSharedVaultUuid() ? (item.getSharedVaultUuid() as string) : null,
user_uuid: item.hasUserUuid() ? (item.getUserUuid() as string) : null,
last_edited_by_uuid: item.hasLastEditedByUuid() ? (item.getLastEditedByUuid() as string) : null,
}
}
}
@@ -0,0 +1,8 @@
import { ItemHttpRepresentation } from './ItemHttpRepresentation'
import { ItemHashHttpRepresentation } from './ItemHashHttpRepresentation'
export interface ItemConflictHttpRepresentation {
server_item?: ItemHttpRepresentation
unsaved_item?: ItemHashHttpRepresentation
type: string
}
@@ -0,0 +1,17 @@
export interface ItemHashHttpRepresentation {
uuid: string
user_uuid: string
content?: string
content_type: string | null
deleted?: boolean
duplicate_of?: string | null
auth_hash?: string
enc_item_key?: string
items_key_id?: string
key_system_identifier: string | null
shared_vault_uuid: string | null
created_at?: string
created_at_timestamp?: number
updated_at?: string
updated_at_timestamp?: number
}
@@ -0,0 +1,19 @@
export interface ItemHttpRepresentation {
uuid: string
items_key_id: string | null
duplicate_of: string | null
enc_item_key: string | null
content: string | null
content_type: string
auth_hash: string | null
deleted: boolean
created_at: string
created_at_timestamp: number
updated_at: string
updated_at_timestamp: number
updated_with_session: string | null
key_system_identifier: string | null
shared_vault_uuid: string | null
user_uuid: string | null
last_edited_by_uuid: string | null
}
@@ -0,0 +1,9 @@
export interface MessageHttpRepresentation {
uuid: string
recipient_uuid: string
sender_uuid: string
encrypted_message: string
replaceability_identifier: string | null
created_at_timestamp: number
updated_at_timestamp: number
}
@@ -0,0 +1,8 @@
export interface NotificationHttpRepresentation {
uuid: string
user_uuid: string
type: string
payload: string
created_at_timestamp: number
updated_at_timestamp: number
}
@@ -0,0 +1,15 @@
export interface SavedItemHttpRepresentation {
uuid: string
duplicate_of: string | null
content_type: string
auth_hash: string | null
deleted: boolean
created_at: string
created_at_timestamp: number
updated_at: string
updated_at_timestamp: number
key_system_identifier: string | null
shared_vault_uuid: string | null
user_uuid: string | null
last_edited_by_uuid: string | null
}
@@ -0,0 +1,7 @@
export interface SharedVaultHttpRepresentation {
uuid: string
user_uuid: string
file_upload_bytes_used: number
created_at_timestamp: number
updated_at_timestamp: number
}
@@ -0,0 +1,10 @@
export interface SharedVaultInviteHttpRepresentation {
uuid: string
shared_vault_uuid: string
user_uuid: string
sender_uuid: string
encrypted_message: string
permission: string
created_at_timestamp: number
updated_at_timestamp: number
}
@@ -0,0 +1,19 @@
import { ItemConflictHttpRepresentation } from './ItemConflictHttpRepresentation'
import { ItemHttpRepresentation } from './ItemHttpRepresentation'
import { MessageHttpRepresentation } from './MessageHttpRepresentation'
import { NotificationHttpRepresentation } from './NotificationHttpRepresentation'
import { SavedItemHttpRepresentation } from './SavedItemHttpRepresentation'
import { SharedVaultHttpRepresentation } from './SharedVaultHttpRepresentation'
import { SharedVaultInviteHttpRepresentation } from './SharedVaultInviteHttpRepresentation'
export type SyncResponseHttpRepresentation = {
retrieved_items: ItemHttpRepresentation[]
saved_items: SavedItemHttpRepresentation[]
conflicts: ItemConflictHttpRepresentation[]
sync_token: string
cursor_token?: string
messages: MessageHttpRepresentation[]
shared_vaults: SharedVaultHttpRepresentation[]
shared_vault_invites: SharedVaultInviteHttpRepresentation[]
notifications: NotificationHttpRepresentation[]
}
@@ -1,7 +1,7 @@
import { Request, Response } from 'express' import { Request, Response } from 'express'
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core' import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
import { ServiceProxyInterface } from '../Http/ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
export class DirectCallServiceProxy implements ServiceProxyInterface { export class DirectCallServiceProxy implements ServiceProxyInterface {
constructor( constructor(
@@ -42,7 +42,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
} }
} }
async callEmailServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> { async callEmailServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
response.status(400).send({ response.status(400).send({
error: { error: {
message: 'Email server is not available.', message: 'Email server is not available.',
@@ -50,13 +50,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
}) })
} }
async callAuthServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> { async callAuthServer(request: never, response: never, methodIdentifier: string): Promise<void> {
const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue()) const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
if (!authService) { if (!authService) {
throw new Error('Auth service not found') throw new Error('Auth service not found')
} }
const serviceResponse = (await authService.handleRequest(request, response, endpointOrMethodIdentifier)) as { const serviceResponse = (await authService.handleRequest(request, response, methodIdentifier)) as {
statusCode: number statusCode: number
json: Record<string, unknown> json: Record<string, unknown>
} }
@@ -67,7 +67,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
async callAuthServerWithLegacyFormat( async callAuthServerWithLegacyFormat(
_request: Request, _request: Request,
response: Response, response: Response,
_endpointOrMethodIdentifier: string, _methodIdentifier: string,
): Promise<void> { ): Promise<void> {
response.status(400).send({ response.status(400).send({
error: { error: {
@@ -76,13 +76,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
}) })
} }
async callRevisionsServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> { async callRevisionsServer(request: never, response: never, methodIdentifier: string): Promise<void> {
const service = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Revisions).getValue()) const service = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Revisions).getValue())
if (!service) { if (!service) {
throw new Error('Revisions service not found') throw new Error('Revisions service not found')
} }
const serviceResponse = (await service.handleRequest(request, response, endpointOrMethodIdentifier)) as { const serviceResponse = (await service.handleRequest(request, response, methodIdentifier)) as {
statusCode: number statusCode: number
json: Record<string, unknown> json: Record<string, unknown>
} }
@@ -90,7 +90,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
this.sendDecoratedResponse(response, serviceResponse) this.sendDecoratedResponse(response, serviceResponse)
} }
async callSyncingServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> { async callSyncingServer(request: never, response: never, methodIdentifier: string): Promise<void> {
const service = this.serviceContainer.get( const service = this.serviceContainer.get(
ServiceIdentifier.create(ServiceIdentifier.NAMES.SyncingServer).getValue(), ServiceIdentifier.create(ServiceIdentifier.NAMES.SyncingServer).getValue(),
) )
@@ -98,7 +98,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
throw new Error('Syncing service not found') throw new Error('Syncing service not found')
} }
const serviceResponse = (await service.handleRequest(request, response, endpointOrMethodIdentifier)) as { const serviceResponse = (await service.handleRequest(request, response, methodIdentifier)) as {
statusCode: number statusCode: number
json: Record<string, unknown> json: Record<string, unknown>
} }
@@ -106,11 +106,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
this.sendDecoratedResponse(response, serviceResponse) this.sendDecoratedResponse(response, serviceResponse)
} }
async callLegacySyncingServer( async callLegacySyncingServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
_request: Request,
response: Response,
_endpointOrMethodIdentifier: string,
): Promise<void> {
response.status(400).send({ response.status(400).send({
error: { error: {
message: 'Legacy syncing server endpoints are no longer available.', message: 'Legacy syncing server endpoints are no longer available.',
@@ -118,7 +114,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
}) })
} }
async callPaymentsServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> { async callPaymentsServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
response.status(400).send({ response.status(400).send({
error: { error: {
message: 'Payments server is not available.', message: 'Payments server is not available.',
@@ -126,7 +122,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
}) })
} }
async callWebSocketServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> { async callWebSocketServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
response.status(400).send({ response.status(400).send({
error: { error: {
message: 'Websockets server is not available.', message: 'Websockets server is not available.',
@@ -6,7 +6,7 @@ import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types' import { TYPES } from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from './ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
@injectable() @injectable()
@@ -72,16 +72,16 @@ export class HttpServiceProxy implements ServiceProxyInterface {
async callSyncingServer( async callSyncingServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
await this.callServer(this.syncingServerJsUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
} }
async callRevisionsServer( async callRevisionsServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
if (!this.revisionsServerUrl) { if (!this.revisionsServerUrl) {
@@ -89,37 +89,31 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return return
} }
await this.callServer(this.revisionsServerUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
} }
async callLegacySyncingServer( async callLegacySyncingServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
await this.callServerWithLegacyFormat( await this.callServerWithLegacyFormat(this.syncingServerJsUrl, request, response, endpoint, payload)
this.syncingServerJsUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
} }
async callAuthServer( async callAuthServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
await this.callServer(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServer(this.authServerUrl, request, response, endpoint, payload)
} }
async callEmailServer( async callEmailServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
if (!this.emailServerUrl) { if (!this.emailServerUrl) {
@@ -128,13 +122,13 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return return
} }
await this.callServer(this.emailServerUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
} }
async callWebSocketServer( async callWebSocketServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
if (!this.webSocketServerUrl) { if (!this.webSocketServerUrl) {
@@ -145,22 +139,16 @@ export class HttpServiceProxy implements ServiceProxyInterface {
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) { if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
await this.callServerWithLegacyFormat( await this.callServerWithLegacyFormat(this.webSocketServerUrl, request, response, endpoint, payload)
this.webSocketServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
} else { } else {
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
} }
} }
async callPaymentsServer( async callPaymentsServer(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> { ): Promise<void | Response<unknown, Record<string, unknown>>> {
if (!this.paymentsServerUrl) { if (!this.paymentsServerUrl) {
@@ -169,29 +157,23 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return return
} }
await this.callServerWithLegacyFormat( await this.callServerWithLegacyFormat(this.paymentsServerUrl, request, response, endpoint, payload)
this.paymentsServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
} }
async callAuthServerWithLegacyFormat( async callAuthServerWithLegacyFormat(
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload) await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpoint, payload)
} }
private async getServerResponse( private async getServerResponse(
serverUrl: string, serverUrl: string,
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
retryAttempt?: number, retryAttempt?: number,
): Promise<AxiosResponse | undefined> { ): Promise<AxiosResponse | undefined> {
@@ -212,15 +194,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
} }
this.logger.debug(`Calling [${request.method}] ${serverUrl}/${endpointOrMethodIdentifier},
headers: ${JSON.stringify(headers)},
query: ${JSON.stringify(request.query)},
payload: ${JSON.stringify(payload)}`)
const serviceResponse = await this.httpClient.request({ const serviceResponse = await this.httpClient.request({
method: request.method as Method, method: request.method as Method,
headers, headers,
url: `${serverUrl}/${endpointOrMethodIdentifier}`, url: `${serverUrl}/${endpoint}`,
data: this.getRequestData(payload), data: this.getRequestData(payload),
maxContentLength: Infinity, maxContentLength: Infinity,
maxBodyLength: Infinity, maxBodyLength: Infinity,
@@ -237,9 +214,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
} }
if (retryAttempt) { if (retryAttempt) {
this.logger.debug( this.logger.debug(`Request to ${serverUrl}/${endpoint} succeeded after ${retryAttempt} retries`)
`Request to ${serverUrl}/${endpointOrMethodIdentifier} succeeded after ${retryAttempt} retries`,
)
} }
return serviceResponse return serviceResponse
@@ -251,18 +226,9 @@ export class HttpServiceProxy implements ServiceProxyInterface {
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1 const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.debug( this.logger.debug(`Retrying request to ${serverUrl}/${endpoint} for the ${nextRetryAttempt} time`)
`Retrying request to ${serverUrl}/${endpointOrMethodIdentifier} for the ${nextRetryAttempt} time`,
)
return this.getServerResponse( return this.getServerResponse(serverUrl, request, response, endpoint, payload, nextRetryAttempt)
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
nextRetryAttempt,
)
} }
let detailedErrorMessage = (error as Error).message let detailedErrorMessage = (error as Error).message
@@ -272,8 +238,8 @@ export class HttpServiceProxy implements ServiceProxyInterface {
this.logger.error( this.logger.error(
tooManyRetryAttempts tooManyRetryAttempts
? `Request to ${serverUrl}/${endpointOrMethodIdentifier} timed out after ${retryAttempt} retries` ? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpointOrMethodIdentifier} on underlying service: ${detailedErrorMessage}`, : `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
) )
this.logger.debug(`Response error: ${JSON.stringify(error)}`) this.logger.debug(`Response error: ${JSON.stringify(error)}`)
@@ -304,19 +270,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
serverUrl: string, serverUrl: string,
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void> { ): Promise<void> {
const serviceResponse = await this.getServerResponse( const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
this.logger.debug(`Response from underlying server: ${JSON.stringify(serviceResponse?.data)},
headers: ${JSON.stringify(serviceResponse?.headers)}`)
if (!serviceResponse) { if (!serviceResponse) {
return return
@@ -348,16 +305,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
serverUrl: string, serverUrl: string,
request: Request, request: Request,
response: Response, response: Response,
endpointOrMethodIdentifier: string, endpoint: string,
payload?: Record<string, unknown> | string, payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> { ): Promise<void | Response<unknown, Record<string, unknown>>> {
const serviceResponse = await this.getServerResponse( const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
if (!serviceResponse) { if (!serviceResponse) {
return return
@@ -0,0 +1,414 @@
import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time'
import { ISessionsClient, AuthorizationHeader, SessionValidationResponse } from '@standardnotes/grpc'
import * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
export class GRPCServiceProxy implements ServiceProxyInterface {
constructor(
private httpClient: AxiosInstance,
private authServerUrl: string,
private syncingServerJsUrl: string,
private paymentsServerUrl: string,
private filesServerUrl: string,
private webSocketServerUrl: string,
private revisionsServerUrl: string,
private emailServerUrl: string,
private httpCallTimeout: number,
private crossServiceTokenCache: CrossServiceTokenCacheInterface,
private logger: Logger,
private timer: TimerInterface,
private sessionsClient: ISessionsClient,
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
) {}
async validateSession(headers: {
authorization: string
sharedVaultOwnerContext?: string
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
return new Promise((resolve, reject) => {
try {
const request = new AuthorizationHeader()
request.setBearerToken(headers.authorization)
const metadata = new grpc.Metadata()
metadata.set('x-shared-vault-owner-context', headers.sharedVaultOwnerContext ?? '')
this.logger.debug('[GRPCServiceProxy] Validating session via gRPC')
this.sessionsClient.validate(
request,
metadata,
(error: grpc.ServiceError | null, response: SessionValidationResponse) => {
if (error) {
const responseCode = error.metadata.get('x-auth-error-response-code').pop()
if (responseCode) {
return resolve({
status: +responseCode,
data: {
error: {
message: error.metadata.get('x-auth-error-message').pop(),
tag: error.metadata.get('x-auth-error-tag').pop(),
},
},
headers: {
contentType: 'application/json',
},
})
}
return reject(error)
}
return resolve({
status: 200,
data: {
authToken: response.getCrossServiceToken(),
},
headers: {
contentType: 'application/json',
},
})
},
)
} catch (error) {
return reject(error)
}
})
}
async callSyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
const requestIsUsingLatestApiVersions =
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
response.status(result.status).send({
meta: {
auth: {
userUuid: response.locals.user?.uuid,
roles: response.locals.roles,
},
server: {
filesServerUrl: this.filesServerUrl,
},
},
data: result.data,
})
return
}
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
}
async callRevisionsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.revisionsServerUrl) {
response.status(400).send({ message: 'Revisions Server not configured' })
return
}
await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
}
async callLegacySyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(this.syncingServerJsUrl, request, response, endpoint, payload)
}
async callAuthServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
}
async callEmailServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.emailServerUrl) {
response.status(400).send({ message: 'Email Server not configured' })
return
}
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
}
async callWebSocketServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.webSocketServerUrl) {
this.logger.debug('Websockets Server URL not defined. Skipped request to WebSockets API.')
return
}
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
await this.callServerWithLegacyFormat(this.webSocketServerUrl, request, response, endpoint, payload)
} else {
await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
}
}
async callPaymentsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> {
if (!this.paymentsServerUrl) {
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
return
}
await this.callServerWithLegacyFormat(this.paymentsServerUrl, request, response, endpoint, payload)
}
async callAuthServerWithLegacyFormat(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpoint, payload)
}
private async getServerResponse(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
retryAttempt?: number,
): Promise<AxiosResponse | undefined> {
try {
const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) {
headers[headerName] = request.headers[headerName] as string
}
delete headers.host
delete headers['content-length']
if (response.locals.authToken) {
headers['X-Auth-Token'] = response.locals.authToken
}
if (response.locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
}
const serviceResponse = await this.httpClient.request({
method: request.method as Method,
headers,
url: `${serverUrl}/${endpoint}`,
data: this.getRequestData(payload),
maxContentLength: Infinity,
maxBodyLength: Infinity,
params: request.query,
timeout: this.httpCallTimeout,
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
})
if (serviceResponse.headers['x-invalidate-cache']) {
const userUuid = serviceResponse.headers['x-invalidate-cache']
await this.crossServiceTokenCache.invalidate(userUuid)
}
if (retryAttempt) {
this.logger.debug(`Request to ${serverUrl}/${endpoint} succeeded after ${retryAttempt} retries`)
}
return serviceResponse
} catch (error) {
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.debug(`Retrying request to ${serverUrl}/${endpoint} for the ${nextRetryAttempt} time`)
return this.getServerResponse(serverUrl, request, response, endpoint, payload, nextRetryAttempt)
}
let detailedErrorMessage = (error as Error).message
if (error instanceof AxiosError) {
detailedErrorMessage = `Status: ${error.status}, code: ${error.code}, message: ${error.message}`
}
this.logger.error(
tooManyRetryAttempts
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
)
this.logger.debug(`Response error: ${JSON.stringify(error)}`)
if ((error as AxiosError).response?.headers['content-type']) {
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
}
const errorCode =
(error as AxiosError).isAxiosError && !isNaN(+((error as AxiosError).code as string))
? +((error as AxiosError).code as string)
: 500
const responseErrorMessage = (error as AxiosError).response?.data
response
.status(errorCode)
.send(
responseErrorMessage ??
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
)
}
return
}
private async callServer(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
if (this.responseShouldNotBeDecorated(serviceResponse)) {
response.status(serviceResponse.status).send(serviceResponse.data)
return
}
response.status(serviceResponse.status).send({
meta: {
auth: {
userUuid: response.locals.user?.uuid,
roles: response.locals.roles,
},
server: {
filesServerUrl: this.filesServerUrl,
},
},
data: serviceResponse.data,
})
}
private async callServerWithLegacyFormat(
serverUrl: string,
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> {
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
if (serviceResponse.request._redirectable._redirectCount > 0) {
response.status(302)
response.redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status)
response.send(serviceResponse.data)
}
}
private getRequestData(
payload: Record<string, unknown> | string | undefined,
): Record<string, unknown> | string | undefined {
if (
payload === '' ||
payload === null ||
payload === undefined ||
(typeof payload === 'object' && Object.keys(payload).length === 0)
) {
return undefined
}
return payload
}
private responseShouldNotBeDecorated(serviceResponse: AxiosResponse): boolean {
return (
serviceResponse.headers['content-type'] !== undefined &&
serviceResponse.headers['content-type'].toLowerCase().includes('text/html')
)
}
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type',
'x-ssjs-version',
'x-auth-version',
]
returnedHeadersFromUnderlyingService.map((headerName) => {
const headerValue = serviceResponse.headers[headerName]
if (headerValue) {
response.setHeader(headerName, headerValue)
}
})
}
private requestTimedOutOrDidNotReachDestination(error: Record<string, unknown>): boolean {
return (
('code' in error && error.code === 'ETIMEDOUT') ||
('response' in error &&
'status' in (error.response as Record<string, unknown>) &&
[503, 504].includes((error.response as Record<string, unknown>).status as number))
)
}
}
@@ -0,0 +1,52 @@
import { Request, Response } from 'express'
import { ISyncingClient, SyncRequest, SyncResponse } from '@standardnotes/grpc'
import { MapperInterface } from '@standardnotes/domain-core'
import { Metadata } from '@grpc/grpc-js'
import { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
export class GRPCSyncingServerServiceProxy {
constructor(
private syncingClient: ISyncingClient,
private syncRequestGRPCMapper: MapperInterface<Record<string, unknown>, SyncRequest>,
private syncResponseGRPCMapper: MapperInterface<SyncResponse, SyncResponseHttpRepresentation>,
) {}
async sync(
request: Request,
response: Response,
payload?: Record<string, unknown> | string,
): Promise<{ status: number; data: unknown }> {
return new Promise((resolve, reject) => {
try {
const syncRequest = this.syncRequestGRPCMapper.toProjection(payload as Record<string, unknown>)
const metadata = new Metadata()
metadata.set('x-user-uuid', response.locals.user.uuid)
metadata.set('x-snjs-version', request.headers['x-snjs-version'] as string)
metadata.set('x-read-only-access', response.locals.readonlyAccess ? 'true' : 'false')
if (response.locals.session) {
metadata.set('x-session-uuid', response.locals.session.uuid)
}
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
if (error) {
const responseCode = error.metadata.get('x-sync-error-response-code').pop()
if (responseCode) {
return resolve({
status: +responseCode,
data: { error: { message: error.metadata.get('x-sync-error-message').pop() } },
})
}
return reject(error)
}
return resolve({ status: 200, data: this.syncResponseGRPCMapper.toProjection(syncResponse) })
})
} catch (error) {
reject(error)
}
})
}
}
@@ -6,4 +6,4 @@ sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT
sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js exec node docker/entrypoint-server.js
+1
View File
@@ -13,6 +13,7 @@ AUTH_JWT_TTL=60000
ENCRYPTION_SERVER_KEY=change-me-! ENCRYPTION_SERVER_KEY=change-me-!
PORT=3000 PORT=3000
GRPC_PORT=50051
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1 DB_REPLICA_HOST=127.0.0.1
+89
View File
@@ -3,6 +3,95 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.174.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.2...@standardnotes/auth-server@1.174.3) (2023-11-28)
### Bug Fixes
* **auth:** pass session uuid to web sockets controller ([edb0a76](https://github.com/standardnotes/server/commit/edb0a768d0b8c31298b31372e6cec16d003fd28d))
* pass session uuid to websockets token ([bcd1d83](https://github.com/standardnotes/server/commit/bcd1d830e6125fc5f8cc1312e581284221aaac8f))
## [1.174.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.1...@standardnotes/auth-server@1.174.2) (2023-11-28)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.174.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.0...@standardnotes/auth-server@1.174.1) (2023-11-27)
### Bug Fixes
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
# [1.174.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.2...@standardnotes/auth-server@1.174.0) (2023-11-27)
### Features
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
## [1.173.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.1...@standardnotes/auth-server@1.173.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.173.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.0...@standardnotes/auth-server@1.173.1) (2023-11-22)
### Bug Fixes
* error handling on gRPC ([#937](https://github.com/standardnotes/server/issues/937)) ([8f23c8a](https://github.com/standardnotes/server/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
# [1.173.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.2...@standardnotes/auth-server@1.173.0) (2023-11-22)
### Features
* add verifiying if user has no items before mass deleting spam accounts ([#936](https://github.com/standardnotes/server/issues/936)) ([c11abe1](https://github.com/standardnotes/server/commit/c11abe1bd36de7c0fb9850c20a8157c066fa9379))
## [1.172.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.1...@standardnotes/auth-server@1.172.2) (2023-11-20)
### Bug Fixes
* define grpc max message size ([bfef16c](https://github.com/standardnotes/server/commit/bfef16ce3757b57ea1cb0cb7417d6bc935a52321))
## [1.172.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.0...@standardnotes/auth-server@1.172.1) (2023-11-20)
### Bug Fixes
* setting gzip as default compression on grpc calls ([#933](https://github.com/standardnotes/server/issues/933)) ([2dff6a2](https://github.com/standardnotes/server/commit/2dff6a2ed3d105ca65996d47321a811e22e25099))
# [1.172.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.171.0...@standardnotes/auth-server@1.172.0) (2023-11-20)
### Features
* **grpc:** add syncing protocol buffers ([#930](https://github.com/standardnotes/server/issues/930)) ([5b84f07](https://github.com/standardnotes/server/commit/5b84f078c6ae6330706895f7c57b67ff8c8d18ae))
# [1.171.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.170.0...@standardnotes/auth-server@1.171.0) (2023-11-16)
### Features
* add debug logs for grpc communication ([6391a01](https://github.com/standardnotes/server/commit/6391a01b5703db23b566710d0520c1197c46144b))
# [1.170.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.2...@standardnotes/auth-server@1.170.0) (2023-11-16)
### Features
* add grpc sessions validation server ([#928](https://github.com/standardnotes/server/issues/928)) ([4f62cac](https://github.com/standardnotes/server/commit/4f62cac213a6b5f503040ef6319e5198967974ce))
## [1.169.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.1...@standardnotes/auth-server@1.169.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.169.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.0...@standardnotes/auth-server@1.169.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.169.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.168.0...@standardnotes/auth-server@1.169.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.168.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.2...@standardnotes/auth-server@1.168.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08) ## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08)
### Bug Fixes ### Bug Fixes
+66 -2
View File
@@ -20,6 +20,7 @@ import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
import '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController' import '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController'
import * as cors from 'cors' import * as cors from 'cors'
import * as grpc from '@grpc/grpc-js'
import { urlencoded, json, Request, Response, NextFunction } from 'express' import { urlencoded, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston' import * as winston from 'winston'
import * as dayjs from 'dayjs' import * as dayjs from 'dayjs'
@@ -29,6 +30,10 @@ import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container' import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types' import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env' import { Env } from '../src/Bootstrap/Env'
import { SessionsServer } from '../src/Infra/gRPC/SessionsServer'
import { SessionsService } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest'
import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
const container = new ContainerConfigLoader() const container = new ContainerConfigLoader()
void container.load().then((container) => { void container.load().then((container) => {
@@ -64,9 +69,68 @@ void container.load().then((container) => {
}) })
}) })
const serverInstance = server.build() const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT')) const httpKeepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
? +env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
: 10_000
serverInstance.keepAliveTimeout = httpKeepAliveTimeout
const grpcKeepAliveTimeout = env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
: 10_000
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
: 1024 * 1024 * 50
const grpcServer = new grpc.Server({
'grpc.keepalive_time_ms': grpcKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcKeepAliveTimeout,
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
'grpc.max_receive_message_length': grpcMaxMessageSize,
'grpc.max_send_message_length': grpcMaxMessageSize,
})
const gRPCPort = env.get('GRPC_PORT', true) ? +env.get('GRPC_PORT', true) : 50051
const sessionsServer = new SessionsServer(
container.get<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest),
container.get<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken),
container.get<winston.Logger>(TYPES.Auth_Logger),
)
grpcServer.addService(SessionsService, {
validate: sessionsServer.validate.bind(sessionsServer),
})
grpcServer.bindAsync(`0.0.0.0:${gRPCPort}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
if (error) {
logger.error(`Failed to bind gRPC server: ${error.message}`)
return
}
logger.info(`gRPC server bound on port ${port}`)
grpcServer.start()
logger.info('gRPC server started')
})
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
grpcServer.tryShutdown((error?: Error) => {
if (error) {
logger.error(`Failed to shutdown gRPC server: ${error.message}`)
} else {
logger.info('gRPC server closed')
}
})
})
logger.info(`Server started on port ${process.env.PORT}`) logger.info(`Server started on port ${process.env.PORT}`)
}) })
+6
View File
@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber) const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start() subscriber.start()
}) })
+8 -8
View File
@@ -6,45 +6,45 @@ COMMAND=$1 && shift 1
case "$COMMAND" in case "$COMMAND" in
'start-web' ) 'start-web' )
echo "[Docker] Starting Web..." echo "[Docker] Starting Web..."
node docker/entrypoint-server.js exec node docker/entrypoint-server.js
;; ;;
'start-worker' ) 'start-worker' )
echo "[Docker] Starting Worker..." echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js exec node docker/entrypoint-worker.js
;; ;;
'cleanup' ) 'cleanup' )
echo "[Docker] Starting Cleanup..." echo "[Docker] Starting Cleanup..."
node docker/entrypoint-cleanup.js exec node docker/entrypoint-cleanup.js
;; ;;
'stats' ) 'stats' )
echo "[Docker] Starting Persisting Stats..." echo "[Docker] Starting Persisting Stats..."
node docker/entrypoint-stats.js exec node docker/entrypoint-stats.js
;; ;;
'email-daily-backup' ) 'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..." echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js daily exec node docker/entrypoint-backup.js daily
;; ;;
'email-weekly-backup' ) 'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..." echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js weekly exec node docker/entrypoint-backup.js weekly
;; ;;
'email-backup' ) 'email-backup' )
echo "[Docker] Starting Email Backup For Single User..." echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1 EMAIL=$1 && shift 1
node docker/entrypoint-user-email-backup.js $EMAIL exec node docker/entrypoint-user-email-backup.js $EMAIL
;; ;;
'delete-accounts' ) 'delete-accounts' )
echo "[Docker] Starting Accounts Deleting from CSV..." echo "[Docker] Starting Accounts Deleting from CSV..."
FILE_NAME=$1 && shift 1 FILE_NAME=$1 && shift 1
MODE=$1 && shift 1 MODE=$1 && shift 1
node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
;; ;;
* ) * )
+10 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.167.2", "version": "1.174.3",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -10,7 +10,13 @@
"author": "Karol Sójko <karol@standardnotes.com>", "author": "Karol Sójko <karol@standardnotes.com>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public",
"provenance": true
},
"repository": {
"type": "git",
"url": "git@github.com:standardnotes/server.git",
"directory": "packages/auth"
}, },
"scripts": { "scripts": {
"clean": "rm -fr dist", "clean": "rm -fr dist",
@@ -37,6 +43,7 @@
"@aws-sdk/client-sqs": "^3.427.0", "@aws-sdk/client-sqs": "^3.427.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1", "@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1", "@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@grpc/grpc-js": "^1.9.11",
"@simplewebauthn/server": "^8.1.1", "@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0", "@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26", "@standardnotes/api": "^1.26.26",
@@ -45,6 +52,7 @@
"@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/features": "^1.59.7", "@standardnotes/features": "^1.59.7",
"@standardnotes/grpc": "workspace:^",
"@standardnotes/predicates": "workspace:*", "@standardnotes/predicates": "workspace:*",
"@standardnotes/responses": "^1.13.27", "@standardnotes/responses": "^1.13.27",
"@standardnotes/security": "workspace:*", "@standardnotes/security": "workspace:*",
+14 -1
View File
@@ -280,6 +280,7 @@ import { TriggerEmailBackupForAllUsers } from '../Domain/UseCase/TriggerEmailBac
import { CSVFileReaderInterface } from '../Domain/CSV/CSVFileReaderInterface' import { CSVFileReaderInterface } from '../Domain/CSV/CSVFileReaderInterface'
import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader' import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader'
import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile' import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
import { AccountDeletionVerificationPassedEventHandler } from '../Domain/Handler/AccountDeletionVerificationPassedEventHandler'
export class ContainerConfigLoader { export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {} constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -1194,6 +1195,7 @@ export class ContainerConfigLoader {
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser), container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting), container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository), container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
container.get<GetActiveSessionsForUser>(TYPES.Auth_GetActiveSessionsForUser),
), ),
) )
container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest) container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest)
@@ -1274,7 +1276,9 @@ export class ContainerConfigLoader {
.toConstantValue( .toConstantValue(
new DeleteAccountsFromCSVFile( new DeleteAccountsFromCSVFile(
container.get<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader), container.get<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader),
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount), container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<winston.Logger>(TYPES.Auth_Logger), container.get<winston.Logger>(TYPES.Auth_Logger),
), ),
) )
@@ -1328,6 +1332,14 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger), container.get<winston.Logger>(TYPES.Auth_Logger),
), ),
) )
container
.bind<AccountDeletionVerificationPassedEventHandler>(TYPES.Auth_AccountDeletionVerificationPassedEventHandler)
.toConstantValue(
new AccountDeletionVerificationPassedEventHandler(
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container container
.bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler) .bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler)
.toConstantValue( .toConstantValue(
@@ -1516,6 +1528,7 @@ export class ContainerConfigLoader {
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([ const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)], ['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
['ACCOUNT_DELETION_VERIFICATION_PASSED', container.get(TYPES.Auth_AccountDeletionVerificationPassedEventHandler)],
['SUBSCRIPTION_PURCHASED', container.get(TYPES.Auth_SubscriptionPurchasedEventHandler)], ['SUBSCRIPTION_PURCHASED', container.get(TYPES.Auth_SubscriptionPurchasedEventHandler)],
['SUBSCRIPTION_CANCELLED', container.get(TYPES.Auth_SubscriptionCancelledEventHandler)], ['SUBSCRIPTION_CANCELLED', container.get(TYPES.Auth_SubscriptionCancelledEventHandler)],
['SUBSCRIPTION_RENEWED', container.get(TYPES.Auth_SubscriptionRenewedEventHandler)], ['SUBSCRIPTION_RENEWED', container.get(TYPES.Auth_SubscriptionRenewedEventHandler)],
+1
View File
@@ -171,6 +171,7 @@ const TYPES = {
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'), Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
// Handlers // Handlers
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'), Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'),
Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'), Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
Auth_SubscriptionCancelledEventHandler: Symbol.for('Auth_SubscriptionCancelledEventHandler'), Auth_SubscriptionCancelledEventHandler: Symbol.for('Auth_SubscriptionCancelledEventHandler'),
Auth_SubscriptionReassignedEventHandler: Symbol.for('Auth_SubscriptionReassignedEventHandler'), Auth_SubscriptionReassignedEventHandler: Symbol.for('Auth_SubscriptionReassignedEventHandler'),
@@ -20,6 +20,7 @@ import {
StatisticPersistenceRequestedEvent, StatisticPersistenceRequestedEvent,
SessionCreatedEvent, SessionCreatedEvent,
SessionRefreshedEvent, SessionRefreshedEvent,
AccountDeletionVerificationRequestedEvent,
} from '@standardnotes/domain-events' } from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates' import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
@@ -33,6 +34,24 @@ import { KeyParamsData } from '@standardnotes/responses'
export class DomainEventFactory implements DomainEventFactoryInterface { export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {} constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
createAccountDeletionVerificationRequestedEvent(dto: {
userUuid: string
email: string
}): AccountDeletionVerificationRequestedEvent {
return {
type: 'ACCOUNT_DELETION_VERIFICATION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent { createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent {
return { return {
type: 'SESSION_CREATED', type: 'SESSION_CREATED',
@@ -18,6 +18,7 @@ import {
StatisticPersistenceRequestedEvent, StatisticPersistenceRequestedEvent,
SessionCreatedEvent, SessionCreatedEvent,
SessionRefreshedEvent, SessionRefreshedEvent,
AccountDeletionVerificationRequestedEvent,
} from '@standardnotes/domain-events' } from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType' import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
import { KeyParamsData } from '@standardnotes/responses' import { KeyParamsData } from '@standardnotes/responses'
@@ -56,6 +57,10 @@ export interface DomainEventFactoryInterface {
ownerUuid: string ownerUuid: string
} }
}): AccountDeletionRequestedEvent }): AccountDeletionRequestedEvent
createAccountDeletionVerificationRequestedEvent(dto: {
userUuid: string
email: string
}): AccountDeletionVerificationRequestedEvent
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
createUserDisabledSessionUserAgentLoggingEvent(dto: { createUserDisabledSessionUserAgentLoggingEvent(dto: {
@@ -0,0 +1,21 @@
import { AccountDeletionVerificationPassedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { DeleteAccount } from '../UseCase/DeleteAccount/DeleteAccount'
export class AccountDeletionVerificationPassedEventHandler implements DomainEventHandlerInterface {
constructor(
private deleteAccount: DeleteAccount,
private logger: Logger,
) {}
async handle(event: AccountDeletionVerificationPassedEvent): Promise<void> {
const result = await this.deleteAccount.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error(`AccountDeletionVerificationPassedEventHandler failed: ${result.getError()}`)
}
}
}
@@ -22,6 +22,7 @@ import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/
import { UserSubscription } from '../../Subscription/UserSubscription' import { UserSubscription } from '../../Subscription/UserSubscription'
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting' import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
import { EncryptionVersion } from '../../Encryption/EncryptionVersion' import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
describe('CreateCrossServiceToken', () => { describe('CreateCrossServiceToken', () => {
let userProjector: ProjectorInterface<User> let userProjector: ProjectorInterface<User>
@@ -32,6 +33,7 @@ describe('CreateCrossServiceToken', () => {
let getRegularSubscription: GetRegularSubscriptionForUser let getRegularSubscription: GetRegularSubscriptionForUser
let getSubscriptionSetting: GetSubscriptionSetting let getSubscriptionSetting: GetSubscriptionSetting
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let getActiveSessionsForUser: GetActiveSessionsForUser
const jwtTTL = 60 const jwtTTL = 60
let session: Session let session: Session
@@ -49,11 +51,15 @@ describe('CreateCrossServiceToken', () => {
getRegularSubscription, getRegularSubscription,
getSubscriptionSetting, getSubscriptionSetting,
sharedVaultUserRepository, sharedVaultUserRepository,
getActiveSessionsForUser,
) )
beforeEach(() => { beforeEach(() => {
session = {} as jest.Mocked<Session> session = {} as jest.Mocked<Session>
getActiveSessionsForUser = {} as jest.Mocked<GetActiveSessionsForUser>
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [session] })
user = { user = {
uuid: '00000000-0000-0000-0000-000000000000', uuid: '00000000-0000-0000-0000-000000000000',
email: 'test@test.te', email: 'test@test.te',
@@ -195,6 +201,69 @@ describe('CreateCrossServiceToken', () => {
) )
}) })
it('should create a cross service token for a user and a specific session', async () => {
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000000',
})
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
{
roles: [
{
name: 'role1',
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
session: {
test: 'test',
},
user: {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
},
60,
)
})
it('should create a cross service token for a user and specific session if the session is missing', async () => {
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [] })
await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000000',
})
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
{
roles: [
{
name: 'role1',
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
user: {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
},
60,
)
})
it('should throw an error if user does not exist', async () => { it('should throw an error if user does not exist', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null) userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
@@ -11,6 +11,7 @@ import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface' import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
import { GetSubscriptionSetting } from '../GetSubscriptionSetting/GetSubscriptionSetting' import { GetSubscriptionSetting } from '../GetSubscriptionSetting/GetSubscriptionSetting'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser' import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
export class CreateCrossServiceToken implements UseCaseInterface<string> { export class CreateCrossServiceToken implements UseCaseInterface<string> {
constructor( constructor(
@@ -23,6 +24,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
private getRegularSubscription: GetRegularSubscriptionForUser, private getRegularSubscription: GetRegularSubscriptionForUser,
private getSubscriptionSettingUseCase: GetSubscriptionSetting, private getSubscriptionSettingUseCase: GetSubscriptionSetting,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface, private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private getActiveSessions: GetActiveSessionsForUser,
) {} ) {}
async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> { async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> {
@@ -84,6 +86,14 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
if (dto.session !== undefined) { if (dto.session !== undefined) {
authTokenData.session = this.projectSession(dto.session) authTokenData.session = this.projectSession(dto.session)
} else if (dto.sessionUuid !== undefined) {
const activeSessionsResponse = await this.getActiveSessions.execute({
userUuid: user.uuid,
sessionUuid: dto.sessionUuid,
})
if (activeSessionsResponse.sessions.length) {
authTokenData.session = this.projectSession(activeSessionsResponse.sessions[0])
}
} }
return Result.ok(this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL)) return Result.ok(this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL))
@@ -10,5 +10,6 @@ export type CreateCrossServiceTokenDTO = Either<
}, },
{ {
userUuid: string userUuid: string
sessionUuid?: string
} }
> >
@@ -1,33 +1,51 @@
import { Logger } from 'winston' import { Logger } from 'winston'
import { Result } from '@standardnotes/domain-core' import { Result } from '@standardnotes/domain-core'
import { AccountDeletionVerificationRequestedEvent, DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface' import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
import { DeleteAccountsFromCSVFile } from './DeleteAccountsFromCSVFile' import { DeleteAccountsFromCSVFile } from './DeleteAccountsFromCSVFile'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { User } from '../../User/User'
describe('DeleteAccountsFromCSVFile', () => { describe('DeleteAccountsFromCSVFile', () => {
let csvFileReader: CSVFileReaderInterface let csvFileReader: CSVFileReaderInterface
let deleteAccount: DeleteAccount let userRepository: UserRepositoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let logger: Logger let logger: Logger
const createUseCase = () => new DeleteAccountsFromCSVFile(csvFileReader, deleteAccount, logger) const createUseCase = () =>
new DeleteAccountsFromCSVFile(csvFileReader, domainEventPublisher, domainEventFactory, userRepository, logger)
beforeEach(() => { beforeEach(() => {
const user = {} as jest.Mocked<User>
csvFileReader = {} as jest.Mocked<CSVFileReaderInterface> csvFileReader = {} as jest.Mocked<CSVFileReaderInterface>
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['email1'])) csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['email1']))
deleteAccount = {} as jest.Mocked<DeleteAccount> userRepository = {} as jest.Mocked<UserRepositoryInterface>
deleteAccount.execute = jest.fn().mockResolvedValue(Result.ok('')) userRepository.findAllByUsernameOrEmail = jest.fn().mockResolvedValue([user])
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createAccountDeletionVerificationRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<AccountDeletionVerificationRequestedEvent>)
logger = {} as jest.Mocked<Logger> logger = {} as jest.Mocked<Logger>
logger.info = jest.fn() logger.info = jest.fn()
}) })
it('should delete accounts', async () => { it('should request account deletion verification', async () => {
const useCase = createUseCase() const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false }) const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(result.isFailed()).toBeFalsy() expect(result.isFailed()).toBeFalsy()
}) })
@@ -56,12 +74,12 @@ describe('DeleteAccountsFromCSVFile', () => {
const result = await useCase.execute({ fileName: 'test.csv', dryRun: true }) const result = await useCase.execute({ fileName: 'test.csv', dryRun: true })
expect(deleteAccount.execute).not.toHaveBeenCalled() expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(result.isFailed()).toBeFalsy() expect(result.isFailed()).toBeFalsy()
}) })
it('should return error if delete account fails', async () => { it('should return error username is invalid', async () => {
deleteAccount.execute = jest.fn().mockResolvedValue(Result.fail('Oops')) csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['']))
const useCase = createUseCase() const useCase = createUseCase()
@@ -69,4 +87,15 @@ describe('DeleteAccountsFromCSVFile', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
}) })
it('should do nothing if users could not be found', async () => {
userRepository.findAllByUsernameOrEmail = jest.fn().mockResolvedValue([])
const useCase = createUseCase()
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(result.isFailed()).toBeFalsy()
})
}) })
@@ -1,14 +1,18 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core' import { Result, UseCaseInterface, Username } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston' import { Logger } from 'winston'
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface' import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
import { DeleteAccountsFromCSVFileDTO } from './DeleteAccountsFromCSVFileDTO' import { DeleteAccountsFromCSVFileDTO } from './DeleteAccountsFromCSVFileDTO'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> { export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
constructor( constructor(
private csvFileReader: CSVFileReaderInterface, private csvFileReader: CSVFileReaderInterface,
private deleteAccount: DeleteAccount, private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private userRepository: UserRepositoryInterface,
private logger: Logger, private logger: Logger,
) {} ) {}
@@ -33,12 +37,20 @@ export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
} }
for (const email of emails) { for (const email of emails) {
const deleteAccountOrError = await this.deleteAccount.execute({ const usernameOrError = Username.create(email)
username: email, if (usernameOrError.isFailed()) {
}) return Result.fail(usernameOrError.getError())
}
const username = usernameOrError.getValue()
if (deleteAccountOrError.isFailed()) { const users = await this.userRepository.findAllByUsernameOrEmail(username)
return Result.fail(deleteAccountOrError.getError()) for (const user of users) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createAccountDeletionVerificationRequestedEvent({
userUuid: user.uuid,
email: user.email,
}),
)
} }
} }
@@ -65,4 +65,10 @@ describe('GetActiveSessionsForUser', () => {
expect(sessionRepository.findAllByRefreshExpirationAndUserUuid).toHaveBeenCalledWith('1-2-3') expect(sessionRepository.findAllByRefreshExpirationAndUserUuid).toHaveBeenCalledWith('1-2-3')
}) })
it('should get a single session for a user', async () => {
expect(await createUseCase().execute({ userUuid: '1-2-3', sessionUuid: '2-3-4' })).toEqual({
sessions: [session2],
})
})
}) })
@@ -5,6 +5,7 @@ import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterfac
import { GetActiveSessionsForUserDTO } from './GetActiveSessionsForUserDTO' import { GetActiveSessionsForUserDTO } from './GetActiveSessionsForUserDTO'
import { GetActiveSessionsForUserResponse } from './GetActiveSessionsForUserResponse' import { GetActiveSessionsForUserResponse } from './GetActiveSessionsForUserResponse'
import { UseCaseInterface } from './UseCaseInterface' import { UseCaseInterface } from './UseCaseInterface'
import { Session } from '../Session/Session'
@injectable() @injectable()
export class GetActiveSessionsForUser implements UseCaseInterface { export class GetActiveSessionsForUser implements UseCaseInterface {
@@ -18,13 +19,26 @@ export class GetActiveSessionsForUser implements UseCaseInterface {
const ephemeralSessions = await this.ephemeralSessionRepository.findAllByUserUuid(dto.userUuid) const ephemeralSessions = await this.ephemeralSessionRepository.findAllByUserUuid(dto.userUuid)
const sessions = await this.sessionRepository.findAllByRefreshExpirationAndUserUuid(dto.userUuid) const sessions = await this.sessionRepository.findAllByRefreshExpirationAndUserUuid(dto.userUuid)
return { const activeSessions = sessions.concat(ephemeralSessions).sort((a, b) => {
sessions: sessions.concat(ephemeralSessions).sort((a, b) => { const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration)
const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration) const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
return dateB.getTime() - dateA.getTime() return dateB.getTime() - dateA.getTime()
}), })
if (dto.sessionUuid) {
let sessions: Session[] = []
const session = activeSessions.find((session) => session.uuid === dto.sessionUuid)
if (session) {
sessions = [session]
}
return {
sessions,
}
}
return {
sessions: activeSessions,
} }
} }
} }
@@ -1,3 +1,4 @@
export type GetActiveSessionsForUserDTO = { export type GetActiveSessionsForUserDTO = {
userUuid: string userUuid: string
sessionUuid?: string
} }
@@ -8,6 +8,7 @@ export interface UserRepositoryInterface {
streamTeam(memberEmail?: Email): Promise<ReadStream> streamTeam(memberEmail?: Email): Promise<ReadStream>
findOneByUuid(uuid: Uuid): Promise<User | null> findOneByUuid(uuid: Uuid): Promise<User | null>
findOneByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User | null> findOneByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User | null>
findAllByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User[]>
findAllCreatedBetween(dto: { start: Date; end: Date; offset: number; limit: number }): Promise<User[]> findAllCreatedBetween(dto: { start: Date; end: Date; offset: number; limit: number }): Promise<User[]>
countAllCreatedBetween(start: Date, end: Date): Promise<number> countAllCreatedBetween(start: Date, end: Date): Promise<number>
save(user: User): Promise<User> save(user: User): Promise<User>
@@ -48,6 +48,7 @@ export class BaseWebSocketsController extends BaseHttpController {
const resultOrError = await this.createCrossServiceToken.execute({ const resultOrError = await this.createCrossServiceToken.execute({
userUuid: token.userUuid, userUuid: token.userUuid,
sessionUuid: token.sessionUuid,
}) })
if (resultOrError.isFailed()) { if (resultOrError.isFailed()) {
return this.json( return this.json(
@@ -69,7 +69,13 @@ export class TypeORMUserRepository implements UserRepositoryInterface {
return this.ormRepository return this.ormRepository
.createQueryBuilder('user') .createQueryBuilder('user')
.where('user.email = :email', { email: usernameOrEmail.value }) .where('user.email = :email', { email: usernameOrEmail.value })
.cache(`user_email_${usernameOrEmail.value}`, 60000)
.getOne() .getOne()
} }
async findAllByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User[]> {
return this.ormRepository
.createQueryBuilder('user')
.where('user.email = :email', { email: usernameOrEmail.value })
.getMany()
}
} }
@@ -0,0 +1,93 @@
import * as grpc from '@grpc/grpc-js'
import { Status } from '@grpc/grpc-js/build/src/constants'
import { AuthorizationHeader, ISessionsServer, SessionValidationResponse } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../../Domain/UseCase/AuthenticateRequest'
import { User } from '../../Domain/User/User'
import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { Logger } from 'winston'
export class SessionsServer implements ISessionsServer {
constructor(
private authenticateRequest: AuthenticateRequest,
private createCrossServiceToken: CreateCrossServiceToken,
private logger: Logger,
) {}
async validate(
call: grpc.ServerUnaryCall<AuthorizationHeader, SessionValidationResponse>,
callback: grpc.sendUnaryData<SessionValidationResponse>,
): Promise<void> {
try {
this.logger.debug('[SessionsServer] Validating session via gRPC')
const authenticateRequestResponse = await this.authenticateRequest.execute({
authorizationHeader: call.request.getBearerToken(),
})
if (!authenticateRequestResponse.success) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
return callback(
{
code: Status.PERMISSION_DENIED,
message: authenticateRequestResponse.errorMessage,
name: authenticateRequestResponse.errorTag,
metadata,
},
null,
)
}
const user = authenticateRequestResponse.user as User
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
let sharedVaultOwnerContext = undefined
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
}
const resultOrError = await this.createCrossServiceToken.execute({
user,
session: authenticateRequestResponse.session,
sharedVaultOwnerContext,
})
if (resultOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', resultOrError.getError())
metadata.set('x-auth-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: resultOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
const response = new SessionValidationResponse()
response.setCrossServiceToken(resultOrError.getValue())
this.logger.debug('[SessionsServer] Session validated via gRPC')
callback(null, response)
} catch (error) {
this.logger.error(`[SessionsServer] Error validating session via gRPC: ${(error as Error).message}`)
callback(
{
code: Status.UNKNOWN,
message: 'An error occurred while validating session',
name: 'UNKNOWN',
},
null,
)
}
}
}
@@ -3,4 +3,4 @@
set -euo pipefail set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js exec node docker/entrypoint-server.js

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