153 Commits

Author SHA1 Message Date
Abdullah Atta
33a189fe91 api: minor refactor 2025-08-18 23:44:22 +05:00
Abdullah Atta
2f361db9df monographs: fix monographs not appearing on clients 2025-08-18 23:44:22 +05:00
Minecon724
bf6cd6cd46 docker: fix the mongo healthcheck (#42) 2025-08-18 23:43:01 +05:00
Abdullah Atta
54266d1ba3 sync: disable SendMonographs on sync since its not supported by clients yet 2025-08-18 18:23:42 +05:00
Abdullah Atta
d3894d2a9f sync: fix StorageLimit does not match any field or property 2025-08-18 17:54:14 +05:00
Abdullah Atta
00c089e677 docker: get rid of initiate-rs0 2025-08-18 14:22:31 +05:00
dyw770
fa8f69157a docker: Fix MongoDB container volume error (#12) 2025-08-18 13:34:31 +05:00
dyw770
30d5394425 api: fix s3 multipart upload (#22)
* api: fix s3 multipart upload use external s3 url

* api: fix s3 multipart upload use external s3 url

* api: fix CompleteMultipartUploadRequest can not deserialize

* api: start multipart upload use s3 internal url

* Update Notesnook.API/Models/PartETagWrapper.cs

remove default constructor

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>

* api: remove default constructor

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>

* api: merge method call

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>

* api: revocation due to conflict

* api: revocation due to conflict

---------

Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2025-08-18 13:33:49 +05:00
Vsevolod Volkov
037bf4c3ea api: update S3 client mode for presigned URL generation to EXTERNAL (#33)
Signed-off-by: Vsevolod Volkov <73965070+StLyn4@users.noreply.github.com>
2025-08-18 11:48:48 +05:00
01zulfi
6d6342dbff api: add user's monograph passwords key (#41) 2025-08-18 11:45:42 +05:00
01zulfi
8df70c81fc monograph: fix existing item condition in update endpoint (#40) 2025-08-04 13:00:55 +05:00
01zulfi
bf2e6efeff monograph: add sync support (#39)
* monograph: add sync support

* monograph: fix password field && improve syncing logic && fix delete endpoint

* sync: get rid of unnecessary .ToList & ToListAsync

* sync: AddIdsToAllDevices is no longer asynchronous

* monograph: simplify and fix several bugs

- we were sending the triggerSync event to all users instead of all devices
- asynchronous methods did not have the `Async` suffix
- we weren't properly replacing the deleted monograph

* monograph: fix minor issues
* fix publishing
* don't return deleted monograph in monographs/:id endpoint
* persist UserId when soft deleting monograph

* monograph: check soft delete status in several endpoints

---------

Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2025-08-04 11:51:15 +05:00
Abdullah Atta
1e2ef0685d sync: don't break the loop if device id is same 2025-08-02 09:17:36 +05:00
Abdullah Atta
c1f0e24d21 sync: update LastAccessTime on push, pull & register 2025-08-01 11:39:18 +05:00
Abdullah Atta
a96b0e1e42 sync: use synchronous locks instead of semaphores 2025-08-01 10:52:48 +05:00
Abdullah Atta
76af2cbfc8 identity: fix 2fa recovery codes not working 2025-07-15 13:35:07 +05:00
Abdullah Atta
34fa43f302 global: add some basic rate limiting 2025-07-15 13:34:31 +05:00
Abdullah Atta
8c267e51f4 common: add gift card to SubscriptionProvider 2025-07-15 13:33:54 +05:00
Abdullah Atta
182558136a identify: validate mfa methods against user's enabled methods 2025-04-30 11:48:50 +05:00
Abdullah Atta
8d3b0d6dbf identity: validate mfa method against user's mfa methods 2025-04-30 11:45:18 +05:00
Abdullah Atta
0841ca1aa8 common: add gift card model 2025-04-10 12:20:03 +05:00
Abdullah Atta
11dff4f0cc identity: move email sender to common 2025-04-10 12:19:49 +05:00
Abdullah Atta
bbabf51073 global: newline changes 2025-04-10 11:09:28 +05:00
Abdullah Atta
a135bd50d7 db: increase heartbeat interval to 60 seconds 2024-12-02 09:38:21 +05:00
Abdullah Atta
e5bf3367cc global: add support for -DSTAGING 2024-11-28 14:38:28 +05:00
Abdullah Atta
c6bcd4a84d monograph: add new ItemId property that is a simple string
This fixes the issue where an invalid objectid causes error when publishing a monograph
2024-11-28 14:21:57 +05:00
Abdullah Atta
07675632e0 identity: enable user's 2fa method after verifying 2fa code
This must be done after the MFA code is verified otherwise the security stamp will be updated making the 2FA code invalid
2024-11-28 14:20:35 +05:00
Abdullah Atta
8d15e176ff docker: DISABLE_ACCOUNT_CREATION -> DISABLE_SIGNUPS 2024-10-25 12:40:53 +05:00
Abdullah Atta
e3f97bc47e docker: use named volumes instead of creating folders in $HOME 2024-10-12 13:39:57 +05:00
Abdullah Atta
9482e1ddc1 docker: add health checks for mongodb and minio 2024-10-12 13:18:12 +05:00
Abdullah Atta
0447ab6e55 docker: fix variable descriptions 2024-10-12 12:07:31 +05:00
Abdullah Atta
682d904dc9 docker: make monograph self hostable 2024-10-12 12:00:40 +05:00
Abdullah Atta
d8ee28389a docker: make .env more consistent 2024-10-12 12:00:29 +05:00
Abdullah Atta
309dcafa02 monograph: add new id/view endpoint for self destruction 2024-10-12 11:59:10 +05:00
Abdullah Atta
1b97ba77da identity: use new server PublicURL 2024-10-12 11:56:57 +05:00
Abdullah Atta
6d19112fb6 common: replace server Domain with PublicURL 2024-10-12 11:56:34 +05:00
Abdullah Atta
1c68942a6d identity: replace Sodium.Core with Geralt 2024-10-12 11:55:52 +05:00
Maniues
3cc84d7603 Update self-hosting TO-DO (#10)
* Update self-hosting TO-DO

* Add starting version and update note
2024-09-26 18:53:17 +05:00
Abdullah Atta
7f94a647c7 Merge pull request #8 from dyw770/master
docker: fix db persistent configuration
2024-09-06 21:00:28 +05:00
dyw770
ba006974a0 docker: fix db persistent configuration 2024-09-05 23:18:12 +08:00
Abdullah Atta
c3772c86ee Merge pull request #7 from dyw770/master
docker: add db persistence configuration and modify the value of the S3_INTERNAL_SERVICE_URL environment variable
2024-09-05 20:09:21 +05:00
dyw770
53695174b5 docker: Add db persistence configuration and modify the value of the S3_INTERNAL_SERVICE_URL environment variable 2024-09-05 12:06:04 +08:00
Abdullah Atta
356488beab s3: use protocol from service url (fixes #6) 2024-09-04 10:22:07 +05:00
Abdullah Atta
b12eb39797 docker: use wget for healthcheck 2024-08-29 12:23:14 +05:00
Abdullah Atta
962b805054 api: remove s3 objects bigger than the maximum size 2024-08-29 12:20:32 +05:00
Abdullah Atta
f3216330a1 docker: remove even more unnecessary env vars 2024-08-29 12:19:37 +05:00
Abdullah Atta
63069ae573 sync: more stable deserializer for syncitem model 2024-08-11 10:18:55 +05:00
Abdullah Atta
cd06a31d1b docker: expose non-80 port inside containers 2024-08-09 11:20:07 +05:00
Abdullah Atta
21a9b4c203 docker: add service to validate enviroment variables before starting containers 2024-08-08 09:14:58 +05:00
Abdullah Atta
a1003ffdd5 docker: minor cleanup 2024-08-07 15:25:43 +05:00
Abdullah Atta
c66a084ed6 docker: explain why smtp configuration is required 2024-08-07 15:16:30 +05:00
Abdullah Atta
dfabfcbc23 common: simplify compatibility version 2024-08-07 15:06:58 +05:00
Abdullah Atta
e324b588a1 docker: fill out as many vars in .env as possible 2024-08-07 09:02:18 +05:00
Abdullah Atta
15b6947ff0 docker: remove support for 3rd-party S3 providers 2024-08-07 08:50:58 +05:00
Abdullah Atta
c441a1750c docker: add documentation for all variables in .env 2024-08-06 15:52:18 +05:00
Abdullah Atta
9f1f3e14d7 global: remove unncessary environment variables 2024-08-06 15:52:00 +05:00
Abdullah Atta
90118488cb docker: update .env 2024-08-02 11:37:47 +05:00
Abdullah Atta
e99f0f33d2 docker: fix attachments not uploading 2024-08-02 11:36:29 +05:00
Abdullah Atta
881354ab83 global: fix failing build 2024-08-01 12:20:37 +05:00
Abdullah Atta
5c1944d29f common: send more info in /version endpoint 2024-08-01 12:05:50 +05:00
Abdullah Atta
cbd0c01d28 identity: add support for disabling new signups 2024-08-01 10:32:51 +05:00
Abdullah Atta
ad590f6011 identity: auto enable 2fa by email on self hosted instance 2024-08-01 10:05:46 +05:00
Abdullah Atta
2f5bd75d4e identity: confirm email automatically on self hosted instances 2024-08-01 09:56:06 +05:00
Abdullah Atta
3c8c8ebc81 chore: update docker set up instructions 2024-07-31 13:28:59 +05:00
Abdullah Atta
2bbb50e9f6 docker: use container images from dockerhub in docker compose 2024-07-31 13:16:19 +05:00
Abdullah Atta
d0a1a2ea9f docker: enable reflection for json serializing 2024-07-31 12:58:38 +05:00
Abdullah Atta
005dc4284d docker: publish with TrimMode set to partial 2024-07-31 12:17:22 +05:00
Abdullah Atta
c730a77b41 docker: optimize dockerfiles for multi platform builds 2024-07-31 11:33:42 +05:00
Abdullah Atta
483be74fa1 docker: enable multi-platform builds 2024-07-31 10:07:30 +05:00
Abdullah Atta
d4b0f7cdf5 docker: revert all chanegs to dockerfile 2024-07-31 09:51:46 +05:00
Abdullah Atta
3ebfc8de7c docker: remove --use-current-runtime 2024-07-31 09:12:28 +05:00
Abdullah Atta
2201984689 docker: fix You may also need to include 'linux-x64' in your project's RuntimeIdentifiers 2024-07-31 09:07:25 +05:00
Abdullah Atta
46675033c8 fix docker build (again) 2024-07-30 16:11:12 +05:00
Abdullah Atta
805ee02b4b fix docker build 2024-07-30 16:06:18 +05:00
Abdullah Atta
e808d28c45 remove listmonk.sdk empty directory 2024-07-30 15:39:09 +05:00
Abdullah Atta
ec1b454d42 ci: add publish workflow 2024-07-30 15:38:25 +05:00
Abdullah Atta
edd860e3ae global: simplify dockerfiles 2024-07-30 15:38:17 +05:00
Abdullah Atta
dad489f41d global: update docker-compose config 2024-07-30 11:30:56 +05:00
Abdullah Atta
e380797004 s3: allow setting separate bucket name for internal s3 service 2024-07-23 10:46:21 +05:00
Abdullah Atta
e9fb43b7ba api: minor refactoring 2024-06-07 15:42:26 +05:00
Abdullah Atta
a3b875a3c5 identity: change IPersistedGrantDbContext to a singleton 2024-06-07 15:42:08 +05:00
Abdullah Atta
59cf7ffcde notesnook: add opentelemetry based metric logging 2024-06-07 15:41:44 +05:00
Abdullah Atta
b304d314a0 identity: minor refactor 2024-06-07 15:40:14 +05:00
Abdullah Atta
f41b38c964 common: expose clients to public 2024-06-07 15:40:05 +05:00
Abdullah Atta
f5bb5d0716 api: remove unnecessary auth policies 2024-06-07 15:39:49 +05:00
Abdullah Atta
99f095babe db: refactor to only init mongo client & collections once 2024-06-07 15:39:12 +05:00
Abdullah Atta
c5b41be2fd identity: register wamp user account service 2024-06-07 15:38:25 +05:00
Abdullah Atta
44536cb9f5 common: add helper for registering repositories 2024-06-07 15:37:39 +05:00
Abdullah Atta
64ae13b589 identity: handle crash on introspection when user is null 2024-06-07 15:36:01 +05:00
Abdullah Atta
99da765a1c api: use wamp services instead of forwarding http requests for internal apis 2024-06-07 15:35:31 +05:00
Abdullah Atta
353e866cda common: add support for wamp based services 2024-06-07 15:31:53 +05:00
Abdullah Atta
336976dd1e monographs: Id -> _id 2024-06-07 15:30:47 +05:00
Abdullah Atta
fe4b71ef7e api: optimize announcements fetching 2024-06-07 15:30:34 +05:00
Abdullah Atta
292f2d4ece sync: add upsertmany for faster bulk upserts 2024-06-07 11:16:06 +05:00
Abdullah Atta
98c5f0c96f sync: use builders instead of linq for mongodb queries 2024-06-07 11:12:31 +05:00
Abdullah Atta
ad4e43e879 sync: remove item type specific model classes & simplify sync repository usage 2024-06-07 11:10:43 +05:00
Abdullah Atta
90b9012c32 sync: use custom bson serializer for SyncItem for perf 2024-06-07 11:00:48 +05:00
Abdullah Atta
9d2c54ad33 sync: remove legacy sync api 2024-06-07 10:56:17 +05:00
Abdullah Atta
0c0ade0c64 sync: refactor sync device service to be more memory efficient 2024-06-07 10:55:15 +05:00
Abdullah Atta
7ce02d0193 api: only fetch monographs' ids 2024-06-07 10:50:47 +05:00
Abdullah Atta
cb0ad7ac9a api: improve pro authorization handling 2024-06-07 10:49:57 +05:00
Abdullah Atta
690414cb51 s3: only allow pro users to upload attachments 2024-05-29 22:58:34 +05:00
Abdullah Atta
0ce5b69f91 identity: send email even if gpg signing fails 2024-05-16 13:20:26 +05:00
Abdullah Atta
abac61e03d ignore sync/ dir 2024-05-16 13:17:07 +05:00
Abdullah Atta
aed05f1eb9 common: add PREMIUM_PAUSED subscription type 2024-05-16 13:16:50 +05:00
Abdullah Atta
95119f8df2 global: change default log level in prod to Warning 2024-05-16 13:16:11 +05:00
Abdullah Atta
dac2d7a577 identity: many fixes to auth grant validation 2024-05-16 13:15:41 +05:00
Abdullah Atta
abe7e67933 identity: include scope when validating account recovery token 2024-05-16 13:15:04 +05:00
Abdullah Atta
90dd4e548d db: decrease maxPoolSize to 500 2024-05-16 13:14:37 +05:00
Abdullah Atta
6e192e1765 s3: return 0 on failure instead of null when getting attachment size 2024-05-16 13:14:16 +05:00
Abdullah Atta
45a8f056b9 api: handle sync v2 in SyncRequirement 2024-05-16 13:13:38 +05:00
Abdullah Atta
1c901aad84 api: remove profile from user settings 2024-05-16 13:13:06 +05:00
Abdullah Atta
98b5143bfe sync: v3 compatible sync 2024-05-16 13:12:37 +05:00
Abdullah Atta
7ad546a863 s3: require pro subscription to upload files 2024-05-16 13:10:18 +05:00
Abdullah Atta
1e3b308210 api: minor refactors 2024-03-05 10:26:44 +05:00
Abdullah Atta
9a98c1afb8 notesnook: add support for user profile 2024-03-05 10:25:54 +05:00
Abdullah Atta
1dcf6557a7 announcements: add support for variable substitution 2024-03-05 10:24:52 +05:00
Abdullah Atta
ce7fb81df3 monographs: self destruct monographs on api call 2024-03-05 10:24:13 +05:00
Abdullah Atta
61adea6a06 monographs: check monograph size on update 2024-03-05 10:23:36 +05:00
Abdullah Atta
8781531042 sync: add new repositories for vault & settings 2024-03-05 10:22:51 +05:00
Abdullah Atta
dbc726aea8 sync: remove colors & tags syncing from v1 sync 2024-03-05 10:20:51 +05:00
Abdullah Atta
36690c5472 sync: rename Settings repository to LegacySettings 2024-03-05 10:20:23 +05:00
Abdullah Atta
e7350e2c49 sync: fix vault key getting reset on sync 2024-03-05 10:16:04 +05:00
Abdullah Atta
b8835923c5 sync: validate cipher base64 before adding to database 2024-03-05 10:13:36 +05:00
Abdullah Atta
e21e2f1510 identity: fix no error being showed if user is locked out 2024-03-05 10:10:47 +05:00
Abdullah Atta
b7e423a3d4 common: support getting local ip for wifi & ethernet adapters during debug 2024-03-05 10:08:58 +05:00
Abdullah Atta
cece6ad4e2 identity: catch and log errors during signup 2024-03-05 10:08:14 +05:00
Abdullah Atta
1e43f7bfdd identity: fix expired tokens not being removed 2024-03-05 10:07:54 +05:00
Abdullah Atta
29eedd57e8 global: minor refactoring 2024-03-05 10:07:01 +05:00
Abdullah Atta
4da9614851 global: upgrade to net8.0 2024-03-05 10:03:27 +05:00
Abdullah Atta
9f4293560f identity: only clean reference_tokens 2023-10-28 11:38:18 +05:00
Abdullah Atta
1f72e2c3a8 identity: fix session revokation 2023-10-28 11:08:17 +05:00
Abdullah Atta
3746c4b42b identity: extend token expiration time 2023-10-24 10:11:02 +05:00
Abdullah Atta
aa77c543dd identity: change disposable domains blocklist 2023-10-24 10:10:22 +05:00
Abdullah Atta
aa62803c73 identity: fix build 2023-09-09 20:37:45 +05:00
Abdullah Atta
3208fdd532 identity: allow twilio errors to propagate 2023-09-09 20:36:05 +05:00
Abdullah Atta
2c1dc6f95e identity: minor refactors 2023-09-09 20:31:21 +05:00
Abdullah Atta
d91df60c57 identity: reset user 2fa on password reset 2023-09-09 20:31:02 +05:00
Abdullah Atta
1a5fe8230e identity: move to twilio verify for SMS 2FA 2023-09-09 20:30:35 +05:00
Abdullah Atta
ab7ea72fd4 sync: introduce sync v2 2023-09-09 20:29:05 +05:00
Abdullah Atta
55a7e9fd1c sync: make collection & db name usage more obvious 2023-09-09 20:28:46 +05:00
Abdullah Atta
8bbb4d0b9e sync: make tags & colors syncable 2023-09-09 20:26:51 +05:00
Abdullah Atta
fc757674a9 sync: improve announcements & monograph query performance 2023-09-09 20:23:16 +05:00
Abdullah Atta
87fd5b8196 identity: delete user completely on unregister 2023-06-28 17:16:29 +05:00
Abdullah Atta
5e95cd5ec9 identity: do not enable mfa on sign up 2023-06-28 17:13:01 +05:00
Abdullah Atta
eb45e8c3ce identity: enable mfa after user confirms email 2023-06-28 17:12:49 +05:00
Abdullah Atta
6e7a85763c sync: pause all fetches if another device is pushing 2023-06-28 17:12:02 +05:00
Abdullah Atta
0ad00c9747 identity: make 2fa truly mandatory 2023-06-08 12:55:27 +05:00
Abdullah Atta
26703bfd8e identity: add support for toggling marketing consent 2023-06-08 12:54:57 +05:00
Abdullah Atta
5ca66f5819 identity: save which platform a user signed up from
this is normalized to web, android or iOS.
Specific device information is not saved.
2023-05-22 18:23:22 +05:00
Abdullah Atta
4b67b7eedb sync: prevent multiple syncs from a single connection 2023-05-22 18:22:32 +05:00
Abdullah Atta
19056a9302 sync: detect multiple conflicting syncs
When 2 or more syncs conflict, it is necessary to adjust last synced
date to avoid data from entering a Sync Blindspot.
2023-05-22 18:22:16 +05:00
Abdullah Atta
99a7ffa6ae identity: keep all grants for 12 hours before cleaning up 2023-04-27 12:26:54 +05:00
193 changed files with 13856 additions and 12018 deletions

View File

@@ -1,5 +1,5 @@
**/Dockerfile
**/bin
**/obj
**/.env
**/Dockerfile
**/bin
**/obj
**/.env
**/.env.local

85
.env
View File

@@ -1,31 +1,72 @@
# Required variables
NOTESNOOK_API_SECRET= # This should be a randomly generated secret
# Description: Name of your self hosted instance. Used in the client apps for identification purposes
# Required: yes
# Example: notesnook-instance-sg
INSTANCE_NAME=self-hosted-notesnook-instance
# SMTP settings required for delivering emails
# Description: This secret is used for generating, validating, and introspecting auth tokens. It must be a randomly generated token (preferably >32 characters).
# Required: yes
NOTESNOOK_API_SECRET=
# Description: Use this flag to disable creation of new accounts on your instance (i.e. in case it is exposed to the Internet).
# Required: yes
# Possible values: true/false
DISABLE_SIGNUPS=false
### SMTP Configuration ###
# SMTP Configuration is required for sending emails for password reset, 2FA emails etc. You can get SMTP settings from your email provider.
# Description: Username for the SMTP connection (most time it is the email address of your account). Check your email provider's documentation to get the appropriate value.
# Required: yes
SMTP_USERNAME=
# Description: Password for the SMTP connection. Check your email provider's documentation to get the appropriate value.
# Required: yes
SMTP_PASSWORD=
# Description: Host on which the the SMTP connection is running. Check your email provider's documentation to get the appropriate value.
# Required: yes
# Example: smtp.gmail.com
SMTP_HOST=
# Description: Port on which the the SMTP connection is running. Check your email provider's documentation to get the appropriate value.
# Required: yes
# Example: 465
SMTP_PORT=
NOTESNOOK_SENDER_EMAIL=
NOTESNOOK_SENDER_NAME=
SMTP_REPLYTO_NAME= # optional
SMTP_REPLYTO_EMAIL= # optional
# MessageBird is used for 2FA via SMS
MESSAGEBIRD_ACCESS_KEY=
# Description: Twilio account SID is required for sending SMS with 2FA codes. Learn more here: https://help.twilio.com/articles/14726256820123-What-is-a-Twilio-Account-SID-and-where-can-I-find-it-
# Required: no
TWILIO_ACCOUNT_SID=
# Description: Twilio account auth is required for sending SMS with 2FA codes. Learn more here: https://help.twilio.com/articles/223136027-Auth-Tokens-and-How-to-Change-Them
# Required: no
TWILIO_AUTH_TOKEN=
# Description: The unique string that we created to identify the Service resource.
# Required: no
# Example: VAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
TWILIO_SERVICE_SID=
# Server discovery settings
# The domain must be without protocol
# e.g. example.org NOT http://example.org
NOTESNOOK_SERVER_DOMAIN=
IDENTITY_SERVER_DOMAIN=
SSE_SERVER_DOMAIN=
# Description: Add the origins for which you want to allow CORS. Leave it empty to allow all origins to access your server. If you want to allow multiple origins, seperate each origin with a comma.
# Required: no
# Example: https://app.notesnook.com,http://localhost:3000
NOTESNOOK_CORS_ORIGINS=
# url of the web app instance you want to use
# e.g. http://localhost:3000
# Note: no slashes at the end
NOTESNOOK_APP_HOST=
# Description: This is the public URL for the web app, and is used by the backend for creating redirect URLs (e.g. after email confirmation etc).
# Note: the URL has no slashes at the end
# Required: yes
# Example: https://app.notesnook.com
NOTESNOOK_APP_PUBLIC_URL=https://app.notesnook.com
# Description: This is the public URL for the monograph frontend.
# Required: yes
# Example: https://monogr.ph
MONOGRAPH_PUBLIC_URL=http://localhost:6264
# Description: This is the public URL for the Authentication server. Used for generating email confirmation & password reset URLs.
# Required: yes
# Example: https://auth.streetwriters.co
AUTH_SERVER_PUBLIC_URL=http://localhost:8264
# Description: This is the public URL for the S3 attachments server (minio). It'll be used by the Notesnook clients for uploading/downloading attachments.
# Required: yes
# Example: https://attachments.notesnook.com
ATTACHMENTS_SERVER_PUBLIC_URL=http://localhost:9000
# Minio is used for S3 storage
MINIO_ROOT_USER= # aka. AccessKeyId (must be > 3 characters)
MINIO_ROOT_PASSWORD= # aka. AccessKey (must be > 8 characters)
# Description: Custom username for the root Minio account. Minio is used for storing your attachments. This must be greater than 3 characters in length.
# Required: no
MINIO_ROOT_USER=
# Description: Custom password for the root Minio account. Minio is used for storing your attachments. This must be greater than 8 characters in length.
# Required: no
MINIO_ROOT_PASSWORD=

85
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.
name: Publish Docker images
on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
strategy:
matrix:
repos:
- image: streetwriters/notesnook-sync
file: ./Notesnook.API/Dockerfile
- image: streetwriters/identity
file: ./Streetwriters.Identity/Dockerfile
- image: streetwriters/sse
file: ./Streetwriters.Messenger/Dockerfile
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- name: Check out the repo
uses: actions/checkout@v4
# Setup Buildx
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
ecr: auto
logout: true
# Pull previous image from docker hub to use it as cache to improve the image build time.
- name: docker pull cache image
continue-on-error: true
run: docker pull ${{ matrix.repos.image }}:latest
# Setup QEMU
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ matrix.repos.image }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.repos.file }}
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
tags: ${{ steps.meta.outputs.tags }}
cache-from: ${{ matrix.repos.image }}:latest
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: index.docker.io/${{ matrix.repos.image }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true

534
.gitignore vendored
View File

@@ -1,267 +1,267 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
keys/
dist/
appsettings.json
keystore/
.env.local
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
keys/
dist/
keystore/
.env.local
Notesnook.API/sync/

9
.vscode/launch.json vendored
View File

@@ -9,8 +9,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-notesnook",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Notesnook.API/bin/Debug/net7.0/linux-x64/Notesnook.API.dll",
"program": "bin/Debug/net8.0/Notesnook.API.dll",
"args": [],
"cwd": "${workspaceFolder}/Notesnook.API",
"stopAtEntry": false,
@@ -25,8 +24,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-identity",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Streetwriters.Identity/bin/Debug/net7.0/linux-x64/Streetwriters.Identity.dll",
"program": "bin/Debug/net8.0/Streetwriters.Identity.dll",
"args": [],
"cwd": "${workspaceFolder}/Streetwriters.Identity",
"stopAtEntry": false,
@@ -41,8 +39,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-messenger",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Streetwriters.Messenger/bin/Debug/net7.0/linux-x64/Streetwriters.Messenger.dll",
"program": "bin/Debug/net8.0/Streetwriters.Messenger.dll",
"args": [],
"cwd": "${workspaceFolder}/Streetwriters.Messenger",
"stopAtEntry": false,

82
.vscode/tasks.json vendored
View File

@@ -1,41 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-notesnook",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Notesnook.API/Notesnook.API.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-identity",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Streetwriters.Identity/Streetwriters.Identity.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-messenger",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Streetwriters.Messenger/Streetwriters.Messenger.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
]
}
{
"version": "2.0.0",
"tasks": [
{
"label": "build-notesnook",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Notesnook.API/Notesnook.API.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-identity",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Streetwriters.Identity/Streetwriters.Identity.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-messenger",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Streetwriters.Messenger/Streetwriters.Messenger.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
]
}

View File

@@ -1,3 +1,3 @@
Notesnook Sync Server is written & maintained by:
- Abdullah Atta <abdullahatta@streetwriters.co>
- Ammar Ahmed <ammarahmed6506@gmail.com>
Notesnook Sync Server is written & maintained by:
- Abdullah Atta <abdullahatta@streetwriters.co>
- Ammar Ahmed <ammarahmed6506@gmail.com>

View File

@@ -1,128 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support@streetwriters.co.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support@streetwriters.co.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

1322
LICENSE

File diff suppressed because it is too large Load Diff

View File

@@ -17,47 +17,76 @@ You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Repositories;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;
namespace Notesnook.API.Accessors
{
public class SyncItemsRepositoryAccessor : ISyncItemsRepositoryAccessor
{
public SyncItemsRepository<Note> Notes { get; }
public SyncItemsRepository<Notebook> Notebooks { get; }
public SyncItemsRepository<Shortcut> Shortcuts { get; }
public SyncItemsRepository<Relation> Relations { get; }
public SyncItemsRepository<Reminder> Reminders { get; }
public SyncItemsRepository<Content> Contents { get; }
public SyncItemsRepository<Setting> Settings { get; }
public SyncItemsRepository<Attachment> Attachments { get; }
public SyncItemsRepository Notes { get; }
public SyncItemsRepository Notebooks { get; }
public SyncItemsRepository Shortcuts { get; }
public SyncItemsRepository Relations { get; }
public SyncItemsRepository Reminders { get; }
public SyncItemsRepository Contents { get; }
public SyncItemsRepository LegacySettings { get; }
public SyncItemsRepository Settings { get; }
public SyncItemsRepository Attachments { get; }
public SyncItemsRepository Colors { get; }
public SyncItemsRepository Vaults { get; }
public SyncItemsRepository Tags { get; }
public Repository<UserSettings> UsersSettings { get; }
public Repository<Monograph> Monographs { get; }
public SyncItemsRepositoryAccessor(SyncItemsRepository<Note> _notes,
SyncItemsRepository<Notebook> _notebooks,
SyncItemsRepository<Content> _content,
SyncItemsRepository<Setting> _settings,
SyncItemsRepository<Attachment> _attachments,
SyncItemsRepository<Shortcut> _shortcuts,
SyncItemsRepository<Relation> _relations,
SyncItemsRepository<Reminder> _reminders,
Repository<UserSettings> _usersSettings,
Repository<Monograph> _monographs)
public SyncItemsRepositoryAccessor(IDbContext dbContext,
[FromKeyedServices(Collections.NotebooksKey)]
IMongoCollection<SyncItem> notebooks,
[FromKeyedServices(Collections.NotesKey)]
IMongoCollection<SyncItem> notes,
[FromKeyedServices(Collections.ContentKey)]
IMongoCollection<SyncItem> content,
[FromKeyedServices(Collections.SettingsKey)]
IMongoCollection<SyncItem> settings,
[FromKeyedServices(Collections.LegacySettingsKey)]
IMongoCollection<SyncItem> legacySettings,
[FromKeyedServices(Collections.AttachmentsKey)]
IMongoCollection<SyncItem> attachments,
[FromKeyedServices(Collections.ShortcutsKey)]
IMongoCollection<SyncItem> shortcuts,
[FromKeyedServices(Collections.RemindersKey)]
IMongoCollection<SyncItem> reminders,
[FromKeyedServices(Collections.RelationsKey)]
IMongoCollection<SyncItem> relations,
[FromKeyedServices(Collections.ColorsKey)]
IMongoCollection<SyncItem> colors,
[FromKeyedServices(Collections.VaultsKey)]
IMongoCollection<SyncItem> vaults,
[FromKeyedServices(Collections.TagsKey)]
IMongoCollection<SyncItem> tags,
Repository<UserSettings> usersSettings, Repository<Monograph> monographs)
{
Notebooks = _notebooks;
Notes = _notes;
Contents = _content;
Settings = _settings;
Attachments = _attachments;
UsersSettings = _usersSettings;
Monographs = _monographs;
Shortcuts = _shortcuts;
Reminders = _reminders;
Relations = _relations;
UsersSettings = usersSettings;
Monographs = monographs;
Notebooks = new SyncItemsRepository(dbContext, notebooks);
Notes = new SyncItemsRepository(dbContext, notes);
Contents = new SyncItemsRepository(dbContext, content);
Settings = new SyncItemsRepository(dbContext, settings);
LegacySettings = new SyncItemsRepository(dbContext, legacySettings);
Attachments = new SyncItemsRepository(dbContext, attachments);
Shortcuts = new SyncItemsRepository(dbContext, shortcuts);
Reminders = new SyncItemsRepository(dbContext, reminders);
Relations = new SyncItemsRepository(dbContext, relations);
Colors = new SyncItemsRepository(dbContext, colors);
Vaults = new SyncItemsRepository(dbContext, vaults);
Tags = new SyncItemsRepository(dbContext, tags);
}
}
}

View File

@@ -1,36 +0,0 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace Notesnook.API.Authorization
{
public class EmailVerifiedRequirement : AuthorizationHandler<EmailVerifiedRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EmailVerifiedRequirement requirement)
{
var isEmailVerified = context.User.HasClaim("verified", "true");
var isUserBasic = context.User.HasClaim("notesnook:status", "basic") || context.User.HasClaim("notesnook:status", "premium_expired");
if (!isUserBasic || isEmailVerified)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}

View File

@@ -1,36 +1,36 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace Notesnook.API.Authorization
{
public class NotesnookUserRequirement : AuthorizationHandler<NotesnookUserRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NotesnookUserRequirement requirement)
{
var isInAudience = context.User.HasClaim("aud", "notesnook");
var hasRole = context.User.HasClaim("role", "notesnook");
if (isInAudience && hasRole)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace Notesnook.API.Authorization
{
public class NotesnookUserRequirement : AuthorizationHandler<NotesnookUserRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NotesnookUserRequirement requirement)
{
var isInAudience = context.User.HasClaim("aud", "notesnook");
var hasRole = context.User.HasClaim("role", "notesnook");
if (isInAudience && hasRole)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
}

View File

@@ -1,37 +1,63 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace Notesnook.API.Authorization
{
public class ProUserRequirement : AuthorizationHandler<ProUserRequirement>, IAuthorizationRequirement
{
private string[] allowedClaims = { "trial", "premium", "premium_canceled" };
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProUserRequirement requirement)
{
var isProOrTrial = context.User.HasClaim((c) => c.Type == "notesnook:status" && allowedClaims.Contains(c.Value));
if (isProOrTrial)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Notesnook.API.Authorization
{
public class ProUserRequirement : AuthorizationHandler<ProUserRequirement>, IAuthorizationRequirement
{
private readonly Dictionary<string, string> pathErrorPhraseMap = new()
{
["/s3"] = "upload attachments",
["/s3/multipart"] = "upload attachments",
};
private readonly string[] allowedClaims = ["trial", "premium", "premium_canceled"];
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProUserRequirement requirement)
{
PathString path = context.Resource is DefaultHttpContext httpContext ? httpContext.Request.Path : null;
var isProOrTrial = context.User.Claims.Any((c) => c.Type == "notesnook:status" && allowedClaims.Contains(c.Value));
if (isProOrTrial) context.Succeed(requirement);
else
{
var phrase = "continue";
foreach (var item in pathErrorPhraseMap)
{
if (path != null && path.StartsWithSegments(item.Key))
phrase = item.Value;
}
var error = $"Please upgrade to Pro to {phrase}.";
context.Fail(new AuthorizationFailureReason(this, error));
}
return Task.CompletedTask;
}
public override Task HandleAsync(AuthorizationHandlerContext context)
{
return this.HandleRequirementAsync(context, this);
}
}
}

View File

@@ -1,107 +1,102 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
namespace Notesnook.API.Authorization
{
public class SyncRequirement : AuthorizationHandler<SyncRequirement>, IAuthorizationRequirement
{
private Dictionary<string, string> pathErrorPhraseMap = new Dictionary<string, string>
{
["/sync/attachments"] = "use attachments",
["/sync"] = "sync your notes",
["/hubs/sync"] = "sync your notes",
["/monographs"] = "publish monographs"
};
private string[] allowedClaims = { "trial", "premium", "premium_canceled" };
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncRequirement requirement)
{
PathString path = context.Resource is DefaultHttpContext httpContext ? httpContext.Request.Path : null;
var result = this.IsAuthorized(context.User, path);
if (result.Succeeded) context.Succeed(requirement);
else
{
var hasReason = result.AuthorizationFailure.FailureReasons.Count() > 0;
if (hasReason)
context.Fail(result.AuthorizationFailure.FailureReasons.First());
else context.Fail();
}
return Task.CompletedTask;
}
public PolicyAuthorizationResult IsAuthorized(ClaimsPrincipal User, PathString requestPath)
{
var id = User.FindFirstValue("sub");
if (string.IsNullOrEmpty(id))
{
var reason = new AuthorizationFailureReason[]
{
new AuthorizationFailureReason(this, "Invalid token.")
};
return PolicyAuthorizationResult.Forbid(AuthorizationFailure.Failed(reason));
}
var hasSyncScope = User.HasClaim("scope", "notesnook.sync");
var isInAudience = User.HasClaim("aud", "notesnook");
var hasRole = User.HasClaim("role", "notesnook");
var isEmailVerified = User.HasClaim("verified", "true");
if (!isEmailVerified)
{
var phrase = "continue";
foreach (var item in pathErrorPhraseMap)
{
if (requestPath != null && requestPath.StartsWithSegments(item.Key))
phrase = item.Value;
}
var error = $"Please confirm your email to {phrase}.";
var reason = new AuthorizationFailureReason[]
{
new AuthorizationFailureReason(this, error)
};
return PolicyAuthorizationResult.Forbid(AuthorizationFailure.Failed(reason));
// context.Fail(new AuthorizationFailureReason(this, error));
}
var isProOrTrial = User.HasClaim((c) => c.Type == "notesnook:status" && allowedClaims.Contains(c.Value));
if (hasSyncScope && isInAudience && hasRole && isEmailVerified)
return PolicyAuthorizationResult.Success(); //(requirement);
return PolicyAuthorizationResult.Forbid();
}
public override Task HandleAsync(AuthorizationHandlerContext context)
{
return this.HandleRequirementAsync(context, this);
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
namespace Notesnook.API.Authorization
{
public class SyncRequirement : AuthorizationHandler<SyncRequirement>, IAuthorizationRequirement
{
private readonly Dictionary<string, string> pathErrorPhraseMap = new()
{
["/sync/attachments"] = "use attachments",
["/sync"] = "sync your notes",
["/hubs/sync"] = "sync your notes",
["/hubs/sync/v2"] = "sync your notes",
["/monographs"] = "publish monographs"
};
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncRequirement requirement)
{
PathString path = context.Resource is DefaultHttpContext httpContext ? httpContext.Request.Path : null;
var result = this.IsAuthorized(context.User, path);
if (result.Succeeded) context.Succeed(requirement);
else if (result.AuthorizationFailure.FailureReasons.Any())
context.Fail(result.AuthorizationFailure.FailureReasons.First());
else context.Fail();
return Task.CompletedTask;
}
public PolicyAuthorizationResult IsAuthorized(ClaimsPrincipal User, PathString requestPath)
{
var id = User.FindFirstValue("sub");
if (string.IsNullOrEmpty(id))
{
var reason = new[]
{
new AuthorizationFailureReason(this, "Invalid token.")
};
return PolicyAuthorizationResult.Forbid(AuthorizationFailure.Failed(reason));
}
var hasSyncScope = User.HasClaim("scope", "notesnook.sync");
var isInAudience = User.HasClaim("aud", "notesnook");
var hasRole = User.HasClaim("role", "notesnook");
var isEmailVerified = User.HasClaim("verified", "true");
if (!isEmailVerified)
{
var phrase = "continue";
foreach (var item in pathErrorPhraseMap)
{
if (requestPath != null && requestPath.StartsWithSegments(item.Key))
phrase = item.Value;
}
var error = $"Please confirm your email to {phrase}.";
var reason = new[]
{
new AuthorizationFailureReason(this, error)
};
return PolicyAuthorizationResult.Forbid(AuthorizationFailure.Failed(reason));
// context.Fail(new AuthorizationFailureReason(this, error));
}
if (hasSyncScope && isInAudience && hasRole && isEmailVerified)
return PolicyAuthorizationResult.Success(); //(requirement);
return PolicyAuthorizationResult.Forbid();
}
public override Task HandleAsync(AuthorizationHandlerContext context)
{
return this.HandleRequirementAsync(context, this);
}
}
}

View File

@@ -0,0 +1,18 @@
namespace Notesnook.API
{
public class Collections
{
public const string SettingsKey = "settingsv2";
public const string AttachmentsKey = "attachments";
public const string ContentKey = "content";
public const string NotesKey = "notes";
public const string NotebooksKey = "notebooks";
public const string RelationsKey = "relations";
public const string RemindersKey = "reminders";
public const string LegacySettingsKey = "settings";
public const string ShortcutsKey = "shortcuts";
public const string TagsKey = "tags";
public const string ColorsKey = "colors";
public const string VaultsKey = "vaults";
}
}

View File

@@ -1,51 +1,69 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Notesnook.API.Models;
using Streetwriters.Data.Repositories;
namespace Notesnook.API.Controllers
{
// TODO: this should be moved out into its own microservice
[ApiController]
[Route("announcements")]
public class AnnouncementController : ControllerBase
{
private Repository<Announcement> Announcements { get; set; }
public AnnouncementController(Repository<Announcement> announcements)
{
Announcements = announcements;
}
[HttpGet("active")]
[AllowAnonymous]
public async Task<IActionResult> GetActiveAnnouncements([FromQuery] string userId)
{
var announcements = await Announcements.FindAsync((a) => a.IsActive);
return Ok(announcements.Where((a) => a.UserIds != null && a.UserIds.Length > 0
? a.UserIds.Contains(userId)
: true));
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
using Notesnook.API.Models;
using Streetwriters.Data.Repositories;
namespace Notesnook.API.Controllers
{
// TODO: this should be moved out into its own microservice
[ApiController]
[Route("announcements")]
public class AnnouncementController : ControllerBase
{
private Repository<Announcement> Announcements { get; set; }
public AnnouncementController(Repository<Announcement> announcements)
{
Announcements = announcements;
}
[HttpGet("active")]
[AllowAnonymous]
public async Task<IActionResult> GetActiveAnnouncements([FromQuery] string userId)
{
var totalActive = await Announcements.Collection.CountDocumentsAsync(Builders<Announcement>.Filter.Eq("IsActive", true));
if (totalActive <= 0) return Ok(new Announcement[] { });
var announcements = (await Announcements.FindAsync((a) => a.IsActive)).Where((a) => a.UserIds == null || a.UserIds.Length == 0 || a.UserIds.Contains(userId));
foreach (var announcement in announcements)
{
if (announcement.UserIds != null && !announcement.UserIds.Contains(userId)) continue;
foreach (var item in announcement.Body)
{
if (item.Type != "callToActions") continue;
foreach (var action in item.Actions)
{
if (action.Type != "link") continue;
action.Data = action.Data.Replace("{{UserId}}", userId ?? "0");
}
}
}
return Ok(announcements);
}
}
}

View File

@@ -18,12 +18,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Models;
using Notesnook.API.Services;
using Streetwriters.Common;
using Streetwriters.Common.Messages;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;
@@ -34,6 +41,7 @@ namespace Notesnook.API.Controllers
[Authorize("Sync")]
public class MonographsController : ControllerBase
{
const string SVG_PIXEL = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><circle r='9'/></svg>";
private Repository<Monograph> Monographs { get; set; }
private readonly IUnitOfWork unit;
private const int MAX_DOC_SIZE = 15 * 1024 * 1024;
@@ -43,50 +51,145 @@ namespace Notesnook.API.Controllers
unit = unitOfWork;
}
[HttpPost]
public async Task<IActionResult> PublishAsync([FromBody] Monograph monograph)
private static FilterDefinition<Monograph> CreateMonographFilter(string userId, Monograph monograph)
{
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
var userIdFilter = Builders<Monograph>.Filter.Eq("UserId", userId);
return ObjectId.TryParse(monograph.ItemId, out ObjectId id)
? Builders<Monograph>.Filter
.And(userIdFilter,
Builders<Monograph>.Filter.Or(
Builders<Monograph>.Filter.Eq("_id", id), Builders<Monograph>.Filter.Eq("ItemId", monograph.ItemId)
)
)
: Builders<Monograph>.Filter
.And(userIdFilter,
Builders<Monograph>.Filter.Eq("ItemId", monograph.ItemId)
);
}
if (await Monographs.GetAsync(monograph.Id) != null) return base.Conflict("This monograph is already published.");
private static FilterDefinition<Monograph> CreateMonographFilter(string itemId)
{
return ObjectId.TryParse(itemId, out ObjectId id)
? Builders<Monograph>.Filter.Or(
Builders<Monograph>.Filter.Eq("_id", id),
Builders<Monograph>.Filter.Eq("ItemId", itemId))
: Builders<Monograph>.Filter.Eq("ItemId", itemId);
}
if (monograph.EncryptedContent == null)
monograph.CompressedContent = monograph.Content.CompressBrotli();
monograph.UserId = userId;
monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (monograph.EncryptedContent?.Cipher.Length > MAX_DOC_SIZE || monograph.CompressedContent?.Length > MAX_DOC_SIZE)
return base.BadRequest("Monograph is too big. Max allowed size is 15mb.");
Monographs.Insert(monograph);
if (!await unit.Commit()) return BadRequest();
return Ok(new
private async Task<Monograph> FindMonographAsync(string userId, Monograph monograph)
{
var result = await Monographs.Collection.FindAsync(CreateMonographFilter(userId, monograph), new FindOptions<Monograph>
{
id = monograph.Id
Limit = 1
});
return await result.FirstOrDefaultAsync();
}
private async Task<Monograph> FindMonographAsync(string itemId)
{
var result = await Monographs.Collection.FindAsync(CreateMonographFilter(itemId), new FindOptions<Monograph>
{
Limit = 1
});
return await result.FirstOrDefaultAsync();
}
[HttpPost]
public async Task<IActionResult> PublishAsync([FromQuery] string deviceId, [FromBody] Monograph monograph)
{
try
{
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
var existingMonograph = await FindMonographAsync(userId, monograph);
if (existingMonograph != null && !existingMonograph.Deleted)
{
return base.Conflict("This monograph is already published.");
}
if (monograph.EncryptedContent == null)
monograph.CompressedContent = monograph.Content.CompressBrotli();
monograph.UserId = userId;
monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (monograph.EncryptedContent?.Cipher.Length > MAX_DOC_SIZE || monograph.CompressedContent?.Length > MAX_DOC_SIZE)
return base.BadRequest("Monograph is too big. Max allowed size is 15mb.");
if (existingMonograph != null)
{
monograph.Id = existingMonograph?.Id;
}
monograph.Deleted = false;
await Monographs.Collection.ReplaceOneAsync(
CreateMonographFilter(userId, monograph),
monograph,
new ReplaceOptions { IsUpsert = true }
);
await MarkMonographForSyncAsync(monograph.ItemId ?? monograph.Id, deviceId);
return Ok(new
{
id = monograph.ItemId,
datePublished = monograph.DatePublished,
});
}
catch (Exception e)
{
await Slogger<MonographsController>.Error(nameof(PublishAsync), e.ToString());
return BadRequest();
}
}
[HttpPatch]
public async Task<IActionResult> UpdateAsync([FromBody] Monograph monograph)
public async Task<IActionResult> UpdateAsync([FromQuery] string deviceId, [FromBody] Monograph monograph)
{
if (await Monographs.GetAsync(monograph.Id) == null) return NotFound();
if (monograph.EncryptedContent == null)
monograph.CompressedContent = monograph.Content.CompressBrotli();
else
monograph.Content = null;
monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
Monographs.Update(monograph.Id, monograph);
if (!await unit.Commit()) return BadRequest();
return Ok(new
try
{
id = monograph.Id
});
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
var existingMonograph = await FindMonographAsync(userId, monograph);
if (existingMonograph == null || existingMonograph.Deleted)
{
return NotFound();
}
if (monograph.EncryptedContent?.Cipher.Length > MAX_DOC_SIZE || monograph.CompressedContent?.Length > MAX_DOC_SIZE)
return base.BadRequest("Monograph is too big. Max allowed size is 15mb.");
if (monograph.EncryptedContent == null)
monograph.CompressedContent = monograph.Content.CompressBrotli();
else
monograph.Content = null;
monograph.DatePublished = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var result = await Monographs.Collection.UpdateOneAsync(
CreateMonographFilter(userId, monograph),
Builders<Monograph>.Update
.Set(m => m.DatePublished, monograph.DatePublished)
.Set(m => m.CompressedContent, monograph.CompressedContent)
.Set(m => m.EncryptedContent, monograph.EncryptedContent)
.Set(m => m.SelfDestruct, monograph.SelfDestruct)
.Set(m => m.Title, monograph.Title)
.Set(m => m.Password, monograph.Password)
);
if (!result.IsAcknowledged) return BadRequest();
await MarkMonographForSyncAsync(monograph.ItemId ?? monograph.Id, deviceId);
return Ok(new
{
id = monograph.ItemId,
datePublished = monograph.DatePublished,
});
}
catch (Exception e)
{
await Slogger<MonographsController>.Error(nameof(UpdateAsync), e.ToString());
return BadRequest();
}
}
[HttpGet]
@@ -95,17 +198,24 @@ namespace Notesnook.API.Controllers
var userId = this.User.FindFirstValue("sub");
if (userId == null) return Unauthorized();
var userMonographs = await Monographs.FindAsync((m) => m.UserId == userId);
return Ok(userMonographs.Select((m) => m.Id));
var monographs = (await Monographs.Collection.FindAsync(
Builders<Monograph>.Filter.And(
Builders<Monograph>.Filter.Eq("UserId", userId),
Builders<Monograph>.Filter.Ne("Deleted", true)
)
, new FindOptions<Monograph, ObjectWithId>
{
Projection = Builders<Monograph>.Projection.Include("_id").Include("ItemId"),
})).ToEnumerable();
return Ok(monographs.Select((m) => m.ItemId ?? m.Id));
}
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<IActionResult> GetMonographAsync([FromRoute] string id)
{
var monograph = await Monographs.FindOneAsync((m) => m.Id == id);
if (monograph == null)
var monograph = await FindMonographAsync(id);
if (monograph == null || monograph.Deleted)
{
return NotFound(new
{
@@ -114,21 +224,100 @@ namespace Notesnook.API.Controllers
});
}
if (monograph.SelfDestruct)
await Monographs.DeleteByIdAsync(monograph.Id);
if (monograph.EncryptedContent == null)
monograph.Content = monograph.CompressedContent.DecompressBrotli();
if (monograph.ItemId == null) monograph.ItemId = monograph.Id;
return Ok(monograph);
}
[HttpGet("{id}/view")]
[AllowAnonymous]
public async Task<IActionResult> TrackView([FromRoute] string id)
{
var monograph = await FindMonographAsync(id);
if (monograph == null || monograph.Deleted) return Content(SVG_PIXEL, "image/svg+xml");
if (monograph.SelfDestruct)
{
var userId = this.User.FindFirstValue("sub");
await Monographs.Collection.ReplaceOneAsync(
CreateMonographFilter(userId, monograph),
new Monograph
{
ItemId = id,
Id = monograph.Id,
Deleted = true
}
);
await MarkMonographForSyncAsync(id);
}
return Content(SVG_PIXEL, "image/svg+xml");
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteAsync([FromRoute] string id)
public async Task<IActionResult> DeleteAsync([FromQuery] string deviceId, [FromRoute] string id)
{
Monographs.DeleteById(id);
if (!await unit.Commit()) return BadRequest();
var monograph = await FindMonographAsync(id);
if (monograph == null || monograph.Deleted)
{
return NotFound(new
{
error = "invalid_id",
error_description = $"No such monograph found."
});
}
var userId = this.User.FindFirstValue("sub");
await Monographs.Collection.ReplaceOneAsync(
CreateMonographFilter(userId, monograph),
new Monograph
{
ItemId = id,
Id = monograph.Id,
Deleted = true,
UserId = monograph.UserId
}
);
await MarkMonographForSyncAsync(id, deviceId);
return Ok();
}
private async Task MarkMonographForSyncAsync(string monographId, string deviceId)
{
if (deviceId == null) return;
var userId = this.User.FindFirstValue("sub");
new SyncDeviceService(new SyncDevice(userId, deviceId)).AddIdsToOtherDevices([$"{monographId}:monograph"]);
await SendTriggerSyncEventAsync();
}
private async Task MarkMonographForSyncAsync(string monographId)
{
var userId = this.User.FindFirstValue("sub");
new SyncDeviceService(new SyncDevice(userId, string.Empty)).AddIdsToAllDevices([$"{monographId}:monograph"]);
await SendTriggerSyncEventAsync(sendToAllDevices: true);
}
private async Task SendTriggerSyncEventAsync(bool sendToAllDevices = false)
{
var userId = this.User.FindFirstValue("sub");
var jti = this.User.FindFirstValue("jti");
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
OriginTokenId = sendToAllDevices ? null : jti,
UserId = userId,
Message = new Message
{
Type = "triggerSync",
Data = JsonSerializer.Serialize(new { reason = "Monographs updated." })
}
});
}
}
}

View File

@@ -24,12 +24,12 @@ using System.Threading.Tasks;
using System.Security.Claims;
using Notesnook.API.Interfaces;
using System;
using Notesnook.API.Models;
namespace Notesnook.API.Controllers
{
[ApiController]
[Route("s3")]
[Authorize("Sync")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public class S3Controller : ControllerBase
{
@@ -40,6 +40,7 @@ namespace Notesnook.API.Controllers
}
[HttpPut]
[Authorize("Pro")]
public IActionResult Upload([FromQuery] string name)
{
var userId = this.User.FindFirstValue("sub");
@@ -50,6 +51,7 @@ namespace Notesnook.API.Controllers
[HttpGet("multipart")]
[Authorize("Pro")]
public async Task<IActionResult> MultipartUpload([FromQuery] string name, [FromQuery] int parts, [FromQuery] string uploadId)
{
var userId = this.User.FindFirstValue("sub");
@@ -62,6 +64,7 @@ namespace Notesnook.API.Controllers
}
[HttpDelete("multipart")]
[Authorize("Pro")]
public async Task<IActionResult> AbortMultipartUpload([FromQuery] string name, [FromQuery] string uploadId)
{
var userId = this.User.FindFirstValue("sub");
@@ -74,19 +77,20 @@ namespace Notesnook.API.Controllers
}
[HttpPost("multipart")]
public async Task<IActionResult> CompleteMultipartUpload([FromBody] CompleteMultipartUploadRequest uploadRequest)
[Authorize("Pro")]
public async Task<IActionResult> CompleteMultipartUpload([FromBody] CompleteMultipartUploadRequestWrapper uploadRequestWrapper)
{
var userId = this.User.FindFirstValue("sub");
try
{
await S3Service.CompleteMultipartUploadAsync(userId, uploadRequest);
await S3Service.CompleteMultipartUploadAsync(userId, uploadRequestWrapper.ToRequest());
return Ok();
}
catch (Exception ex) { return BadRequest(ex.Message); }
}
[HttpGet]
[Authorize]
[Authorize("Sync")]
public IActionResult Download([FromQuery] string name)
{
var userId = this.User.FindFirstValue("sub");
@@ -96,18 +100,17 @@ namespace Notesnook.API.Controllers
}
[HttpHead]
[Authorize]
[Authorize("Sync")]
public async Task<IActionResult> Info([FromQuery] string name)
{
var userId = this.User.FindFirstValue("sub");
var size = await S3Service.GetObjectSizeAsync(userId, name);
if (size == null) return BadRequest();
HttpContext.Response.Headers.ContentLength = size;
return Ok();
}
[HttpDelete]
[Authorize("Sync")]
public async Task<IActionResult> DeleteAsync([FromQuery] string name)
{
try
@@ -122,4 +125,4 @@ namespace Notesnook.API.Controllers
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Notesnook.API.Interfaces;
using Notesnook.API.Models.Responses;
using Notesnook.API.Services;
using Streetwriters.Common;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Models;
namespace Notesnook.API.Controllers
{
[ApiController]
[Authorize]
[Route("devices")]
public class SyncDeviceController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> RegisterDevice([FromQuery] string deviceId)
{
try
{
var userId = this.User.FindFirstValue("sub") ?? throw new Exception("User not found.");
new SyncDeviceService(new SyncDevice(userId, deviceId)).RegisterDevice();
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(UnregisterDevice), "Couldn't register device.", ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
[HttpDelete]
public async Task<IActionResult> UnregisterDevice([FromQuery] string deviceId)
{
try
{
var userId = this.User.FindFirstValue("sub") ?? throw new Exception("User not found.");
new SyncDeviceService(new SyncDevice(userId, deviceId)).UnregisterDevice();
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(UnregisterDevice), "Couldn't unregister device.", ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
}
}

View File

@@ -1,118 +1,120 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Notesnook.API.Interfaces;
using Notesnook.API.Models.Responses;
using Streetwriters.Common;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Models;
namespace Notesnook.API.Controllers
{
[ApiController]
[Authorize]
[Route("users")]
public class UsersController : ControllerBase
{
private readonly HttpClient httpClient;
private readonly IHttpContextAccessor HttpContextAccessor;
private IUserService UserService { get; set; }
public UsersController(IUserService userService, IHttpContextAccessor accessor)
{
httpClient = new HttpClient();
HttpContextAccessor = accessor;
UserService = userService;
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Signup()
{
try
{
await UserService.CreateUserAsync();
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(Signup), "Couldn't sign up.", ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
[HttpGet]
public async Task<IActionResult> GetUser()
{
UserResponse response = await UserService.GetUserAsync();
if (!response.Success) return BadRequest(response);
return Ok(response);
}
[HttpPatch]
public async Task<IActionResult> UpdateUser([FromBody] UserResponse user)
{
UserResponse response = await UserService.GetUserAsync(false);
if (user.AttachmentsKey != null)
await UserService.SetUserAttachmentsKeyAsync(response.UserId, user.AttachmentsKey);
else return BadRequest();
return Ok();
}
[HttpPost("reset")]
public async Task<IActionResult> Reset([FromForm] bool removeAttachments)
{
var userId = this.User.FindFirstValue("sub");
if (await UserService.ResetUserAsync(userId, removeAttachments))
return Ok();
return BadRequest();
}
[HttpPost("delete")]
public async Task<IActionResult> Delete()
{
try
{
var userId = this.User.FindFirstValue("sub");
if (await UserService.DeleteUserAsync(userId, User.FindFirstValue("jti")))
{
Response response = await this.httpClient.ForwardAsync<Response>(this.HttpContextAccessor, $"{Servers.IdentityServer.ToString()}/account/unregister", HttpMethod.Post);
if (!response.Success) return BadRequest();
return Ok();
}
return BadRequest();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Timeouts;
using Microsoft.AspNetCore.Mvc;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
using Streetwriters.Common;
namespace Notesnook.API.Controllers
{
[ApiController]
[Authorize]
[Route("users")]
public class UsersController(IUserService UserService) : ControllerBase
{
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Signup()
{
try
{
await UserService.CreateUserAsync();
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(Signup), "Couldn't sign up.", ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
[HttpGet]
public async Task<IActionResult> GetUser()
{
var userId = User.FindFirstValue("sub");
try
{
UserResponse response = await UserService.GetUserAsync(userId);
if (!response.Success) return BadRequest(response);
return Ok(response);
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't get user for id.", userId, ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
[HttpPatch]
public async Task<IActionResult> UpdateUser([FromBody] UserResponse user)
{
var userId = User.FindFirstValue("sub");
try
{
var keys = new UserKeys
{
AttachmentsKey = user.AttachmentsKey,
MonographPasswordsKey = user.MonographPasswordsKey
};
await UserService.SetUserKeysAsync(userId, keys);
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't update user with id.", userId, ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
[HttpPost("reset")]
public async Task<IActionResult> Reset([FromForm] bool removeAttachments)
{
var userId = this.User.FindFirstValue("sub");
if (await UserService.ResetUserAsync(userId, removeAttachments))
return Ok();
return BadRequest();
}
[HttpPost("delete")]
[RequestTimeout(5 * 60 * 1000)]
public async Task<IActionResult> Delete([FromForm] DeleteAccountForm form)
{
var userId = this.User.FindFirstValue("sub");
var jti = User.FindFirstValue("jti");
try
{
await UserService.DeleteUserAsync(userId, jti, form.Password);
return Ok();
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't delete user with id.", userId, ex.ToString());
return BadRequest(new { error = ex.Message });
}
}
}
}

View File

@@ -1,28 +1,50 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app
# restore all project dependencies
COPY Streetwriters.Data/*.csproj ./Streetwriters.Data/
RUN dotnet restore /app/Streetwriters.Data/Streetwriters.Data.csproj --use-current-runtime
COPY Streetwriters.Common/*.csproj ./Streetwriters.Common/
RUN dotnet restore /app/Streetwriters.Common/Streetwriters.Common.csproj --use-current-runtime
COPY Notesnook.API/*.csproj ./Notesnook.API/
RUN dotnet restore /app/Notesnook.API/Notesnook.API.csproj --use-current-runtime
# copy everything else
COPY Streetwriters.Data/ ./Streetwriters.Data/
COPY Streetwriters.Common/ ./Streetwriters.Common/
COPY Notesnook.API/ ./Notesnook.API/
# build
WORKDIR /app/Notesnook.API/
ENV DOTNET_TC_QuickJitForLoops="1" DOTNET_ReadyToRun="0" DOTNET_TieredPGO="1" DOTNET_SYSTEM_GLOBALIZATION_INVARIANT="true"
RUN dotnet publish -c Release -o /app/out --use-current-runtime --self-contained false --no-restore
# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=build /app/out .
ENTRYPOINT ["dotnet", "Notesnook.API.dll"]
FROM mcr.microsoft.com/dotnet/runtime-deps:8.0-alpine AS base
WORKDIR /app
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG TARGETARCH
ARG BUILDPLATFORM
ENV DOTNET_TC_QuickJitForLoops="1" DOTNET_ReadyToRun="0" DOTNET_TieredPGO="1" DOTNET_SYSTEM_GLOBALIZATION_INVARIANT="true"
WORKDIR /src
COPY Streetwriters.Data/*.csproj ./Streetwriters.Data/
COPY Streetwriters.Common/*.csproj ./Streetwriters.Common/
COPY Notesnook.API/*.csproj ./Notesnook.API/
# restore dependencies
RUN dotnet restore -v d /src/Notesnook.API/Notesnook.API.csproj --use-current-runtime
COPY Streetwriters.Data/ ./Streetwriters.Data/
COPY Streetwriters.Common/ ./Streetwriters.Common/
COPY Notesnook.API/ ./Notesnook.API/
WORKDIR /src/Notesnook.API/
RUN dotnet build -c Release -o /app/build -a $TARGETARCH
FROM build AS publish
RUN dotnet publish -c Release -o /app/publish \
#--runtime alpine-x64 \
--self-contained true \
/p:TrimMode=partial \
/p:PublishTrimmed=true \
/p:PublishSingleFile=true \
/p:JsonSerializerIsReflectionEnabledByDefault=true \
-a $TARGETARCH
FROM --platform=$BUILDPLATFORM base AS final
ARG TARGETARCH
ARG BUILDPLATFORM
# create a new user and change directory ownership
RUN adduser --disabled-password \
--home /app \
--gecos '' dotnetuser && chown -R dotnetuser /app
# impersonate into the new user
USER dotnetuser
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["./Notesnook.API"]

View File

@@ -0,0 +1,49 @@
using System;
using System.Diagnostics.Metrics;
using System.Diagnostics.Tracing;
[EventSource(Name = "Notesnook.API.EventCounter.Sync")]
public sealed class SyncEventCounterSource : EventSource
{
public static readonly SyncEventCounterSource Log = new();
private Meter meter = new("Notesnook.API.Metrics.Sync", "1.0.0");
private Counter<int> fetchCounter;
private Counter<int> pushCounter;
private Counter<int> legacyFetchCounter;
private Counter<int> pushV2Counter;
private Counter<int> fetchV2Counter;
private Histogram<long> fetchV2Duration;
private Histogram<long> pushV2Duration;
private SyncEventCounterSource()
{
fetchCounter = meter.CreateCounter<int>("sync.fetches", "fetches", "Total fetches");
pushCounter = meter.CreateCounter<int>("sync.pushes", "pushes", "Total pushes");
legacyFetchCounter = meter.CreateCounter<int>("sync.legacy-fetches", "fetches", "Total legacy fetches");
fetchV2Counter = meter.CreateCounter<int>("sync.v2.fetches", "fetches", "Total v2 fetches");
pushV2Counter = meter.CreateCounter<int>("sync.v2.pushes", "pushes", "Total v2 pushes");
fetchV2Duration = meter.CreateHistogram<long>("sync.v2.fetch_duration");
pushV2Duration = meter.CreateHistogram<long>("sync.v2.push_duration");
}
public void Fetch() => fetchCounter.Add(1);
public void LegacyFetch() => legacyFetchCounter.Add(1);
public void FetchV2() => fetchV2Counter.Add(1);
public void PushV2() => pushV2Counter.Add(1);
public void Push() => pushCounter.Add(1);
public void RecordFetchDuration(long durationMs) => fetchV2Duration.Record(durationMs);
public void RecordPushDuration(long durationMs) => pushV2Duration.Record(durationMs);
protected override void Dispose(bool disposing)
{
legacyFetchCounter = null;
fetchV2Counter = null;
pushV2Counter = null;
pushCounter = null;
fetchCounter = null;
meter.Dispose();
meter = null;
base.Dispose(disposing);
}
}

View File

@@ -1,71 +1,71 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
namespace Notesnook.API.Extensions
{
public class AuthorizationResultTransformer : IAuthorizationMiddlewareResultHandler
{
private readonly IAuthorizationMiddlewareResultHandler _handler;
public AuthorizationResultTransformer()
{
_handler = new AuthorizationMiddlewareResultHandler();
}
public async Task HandleAsync(
RequestDelegate requestDelegate,
HttpContext httpContext,
AuthorizationPolicy authorizationPolicy,
PolicyAuthorizationResult policyAuthorizationResult)
{
var isWebsocket = httpContext.Request.Headers.Upgrade == "websocket";
if (!isWebsocket && policyAuthorizationResult.Forbidden && policyAuthorizationResult.AuthorizationFailure != null)
{
var error = string.Join("\n", policyAuthorizationResult.AuthorizationFailure.FailureReasons.Select((r) => r.Message));
if (!string.IsNullOrEmpty(error) && !isWebsocket)
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(new { error }));
return;
}
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
else if (isWebsocket)
{
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, PolicyAuthorizationResult.Success());
}
else
{
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
namespace Notesnook.API.Extensions
{
public class AuthorizationResultTransformer : IAuthorizationMiddlewareResultHandler
{
private readonly IAuthorizationMiddlewareResultHandler _handler;
public AuthorizationResultTransformer()
{
_handler = new AuthorizationMiddlewareResultHandler();
}
public async Task HandleAsync(
RequestDelegate requestDelegate,
HttpContext httpContext,
AuthorizationPolicy authorizationPolicy,
PolicyAuthorizationResult policyAuthorizationResult)
{
var isWebsocket = httpContext.Request.Headers.Upgrade == "websocket";
if (!isWebsocket && policyAuthorizationResult.Forbidden && policyAuthorizationResult.AuthorizationFailure != null)
{
var error = string.Join("\n", policyAuthorizationResult.AuthorizationFailure.FailureReasons.Select((r) => r.Message));
if (!string.IsNullOrEmpty(error))
{
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(JsonSerializer.Serialize(new { error }));
return;
}
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
else if (isWebsocket)
{
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, PolicyAuthorizationResult.Success());
}
else
{
await _handler.HandleAsync(requestDelegate, httpContext, authorizationPolicy, policyAuthorizationResult);
}
}
}
}

View File

@@ -1,44 +1,44 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading;
using System;
using System.Threading.Tasks;
namespace MongoDB.Driver
{
public static class TransactionHelper
{
public static async Task StartTransaction(this IMongoClient client, Action<CancellationToken> operate, CancellationToken ct)
{
using (var session = await client.StartSessionAsync())
{
var transactionOptions = new TransactionOptions(readPreference: ReadPreference.Nearest, readConcern: ReadConcern.Local, writeConcern: WriteConcern.WMajority);
await session.WithTransactionAsync((handle, token) =>
{
return Task.Run(() =>
{
operate(token);
return true;
});
}, transactionOptions, ct);
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading;
using System;
using System.Threading.Tasks;
namespace MongoDB.Driver
{
public static class TransactionHelper
{
public static async Task StartTransaction(this IMongoClient client, Action<CancellationToken> operate, CancellationToken ct)
{
using (var session = await client.StartSessionAsync())
{
var transactionOptions = new TransactionOptions(readPreference: ReadPreference.Nearest, readConcern: ReadConcern.Local, writeConcern: WriteConcern.WMajority);
await session.WithTransactionAsync((handle, token) =>
{
return Task.Run(() =>
{
operate(token);
return true;
});
}, transactionOptions, ct);
}
}
}
}

View File

@@ -23,24 +23,114 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using MongoDB.Driver;
using Notesnook.API.Authorization;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Repositories;
using Streetwriters.Common.Models;
using Streetwriters.Data.Interfaces;
namespace Notesnook.API.Hubs
{
public struct RunningPush
{
public long Timestamp { get; set; }
public long Validity { get; set; }
public string ConnectionId { get; set; }
}
public interface ISyncHubClient
{
Task SyncItem(SyncTransferItem transferItem);
Task RemoteSyncCompleted(long lastSynced);
Task SyncCompleted();
Task PushItems(SyncTransferItemV2 transferItem);
Task<bool> SendItems(SyncTransferItemV2 transferItem);
Task PushCompleted(long lastSynced);
}
public class GlobalSync
{
private const long PUSH_VALIDITY_EXTENSION_PERIOD = 16 * 1000; // 16 second
private const int PUSH_VALIDITY_PERIOD_PER_ITEM = 5 * 100; // 0.5 second
private const long BASE_PUSH_VALIDITY_PERIOD = 5 * 1000; // 5 seconds
private const long BASE_PUSH_VALIDITY_PERIOD_NEW = 16 * 1000; // 16 seconds
private readonly static Dictionary<string, List<RunningPush>> PushOperations = new();
public static void ClearPushOperations(string userId, string connectionId)
{
if (PushOperations.TryGetValue(userId, out List<RunningPush> operations))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (var push in operations.ToArray())
if (push.ConnectionId == connectionId || !IsPushValid(push, now))
operations.Remove(push);
}
}
public static bool IsPushing(string userId, string connectionId)
{
if (PushOperations.TryGetValue(userId, out List<RunningPush> operations))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (var push in operations)
if (push.ConnectionId == connectionId && IsPushValid(push, now)) return true;
}
return false;
}
public static bool IsUserPushing(string userId)
{
var count = 0;
if (PushOperations.TryGetValue(userId, out List<RunningPush> operations))
{
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (var push in operations)
if (IsPushValid(push, now)) ++count;
}
return count > 0;
}
public static void StartPush(string userId, string connectionId, long? totalItems = null)
{
if (IsPushing(userId, connectionId)) return;
if (!PushOperations.ContainsKey(userId))
PushOperations[userId] = new List<RunningPush>();
PushOperations[userId].Add(new RunningPush
{
ConnectionId = connectionId,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Validity = totalItems.HasValue ? BASE_PUSH_VALIDITY_PERIOD + (totalItems.Value * PUSH_VALIDITY_PERIOD_PER_ITEM) : BASE_PUSH_VALIDITY_PERIOD_NEW
});
}
public static void ExtendPush(string userId, string connectionId)
{
if (!IsPushing(userId, connectionId) || !PushOperations.ContainsKey(userId))
{
StartPush(userId, connectionId);
return;
}
var index = PushOperations[userId].FindIndex((push) => push.ConnectionId == connectionId);
if (index < 0)
{
StartPush(userId, connectionId);
return;
}
var pushOperation = PushOperations[userId][index];
pushOperation.Validity += PUSH_VALIDITY_EXTENSION_PERIOD;
}
private static bool IsPushValid(RunningPush push, long now)
{
return now < push.Timestamp + push.Validity;
}
}
[Authorize("Sync")]
@@ -48,6 +138,16 @@ namespace Notesnook.API.Hubs
{
private ISyncItemsRepositoryAccessor Repositories { get; }
private readonly IUnitOfWork unit;
private readonly string[] CollectionKeys = new[] {
"settings",
"attachment",
"note",
"notebook",
"content",
"shortcut",
"reminder",
"relation", // relations must sync at the end to prevent invalid state
};
public SyncHub(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, IUnitOfWork unitOfWork)
{
@@ -70,185 +170,235 @@ namespace Notesnook.API.Hubs
public override async Task OnDisconnectedAsync(Exception exception)
{
var id = Context.User.FindFirstValue("sub");
await Groups.RemoveFromGroupAsync(Context.ConnectionId, id);
await base.OnDisconnectedAsync(exception);
try
{
await base.OnDisconnectedAsync(exception);
}
finally
{
var id = Context.User.FindFirstValue("sub");
GlobalSync.ClearPushOperations(id, Context.ConnectionId);
}
}
public async Task<int> SyncItem(BatchedSyncTransferItem transferItem)
private Action<SyncItem, string, long> MapTypeToUpsertAction(string type)
{
return type switch
{
"attachment" => Repositories.Attachments.Upsert,
"note" => Repositories.Notes.Upsert,
"notebook" => Repositories.Notebooks.Upsert,
"content" => Repositories.Contents.Upsert,
"shortcut" => Repositories.Shortcuts.Upsert,
"reminder" => Repositories.Reminders.Upsert,
"relation" => Repositories.Relations.Upsert,
_ => null,
};
}
public async Task<long> InitializePush(SyncMetadata syncMetadata)
{
if (syncMetadata.LastSynced <= 0) throw new HubException("Last synced time cannot be zero or less than zero.");
var userId = Context.User.FindFirstValue("sub");
if (string.IsNullOrEmpty(userId)) return 0;
var others = Clients.OthersInGroup(userId);
UserSettings userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
long dateSynced = Math.Max(syncMetadata.LastSynced, userSettings.LastSynced);
UserSettings userSettings = await this.Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
GlobalSync.StartPush(userId, Context.ConnectionId);
long dateSynced = transferItem.LastSynced > userSettings.LastSynced ? transferItem.LastSynced : userSettings.LastSynced;
for (int i = 0; i < transferItem.Items.Length; ++i)
if (
(userSettings.VaultKey != null &&
syncMetadata.VaultKey != null &&
!userSettings.VaultKey.Equals(syncMetadata.VaultKey) &&
!syncMetadata.VaultKey.IsEmpty()) ||
(userSettings.VaultKey == null &&
syncMetadata.VaultKey != null &&
!syncMetadata.VaultKey.IsEmpty()))
{
var data = transferItem.Items[i];
var type = transferItem.Types[i];
// We intentionally don't await here to speed up the sync. Fire and forget
// suits here because we don't really care if the item reaches the other
// devices.
others.SyncItem(
new SyncTransferItem
{
Item = data,
ItemType = type,
LastSynced = dateSynced,
Total = transferItem.Total,
Current = transferItem.Current + i
});
switch (type)
{
case "content":
Repositories.Contents.Upsert(JsonSerializer.Deserialize<Content>(data), userId, dateSynced);
break;
case "attachment":
Repositories.Attachments.Upsert(JsonSerializer.Deserialize<Attachment>(data), userId, dateSynced);
break;
case "note":
Repositories.Notes.Upsert(JsonSerializer.Deserialize<Note>(data), userId, dateSynced);
break;
case "notebook":
Repositories.Notebooks.Upsert(JsonSerializer.Deserialize<Notebook>(data), userId, dateSynced);
break;
case "shortcut":
Repositories.Shortcuts.Upsert(JsonSerializer.Deserialize<Shortcut>(data), userId, dateSynced);
break;
case "reminder":
Repositories.Reminders.Upsert(JsonSerializer.Deserialize<Reminder>(data), userId, dateSynced);
break;
case "relation":
Repositories.Relations.Upsert(JsonSerializer.Deserialize<Relation>(data), userId, dateSynced);
break;
case "settings":
var settings = JsonSerializer.Deserialize<Setting>(data);
settings.Id = MongoDB.Bson.ObjectId.Parse(userId);
settings.ItemId = userId;
Repositories.Settings.Upsert(settings, userId, dateSynced);
break;
case "vaultKey":
userSettings.VaultKey = JsonSerializer.Deserialize<EncryptedData>(data);
Repositories.UsersSettings.Upsert(userSettings, (u) => u.UserId == userId);
break;
default:
throw new HubException("Invalid item type.");
}
userSettings.VaultKey = syncMetadata.VaultKey;
await Repositories.UsersSettings.UpsertAsync(userSettings, (u) => u.UserId == userId);
}
return await unit.Commit() ? 1 : 0;
return dateSynced;
}
public async Task<int> PushItems(SyncTransferItemV2 pushItem, long dateSynced)
{
var userId = Context.User.FindFirstValue("sub");
if (string.IsNullOrEmpty(userId)) return 0;
SyncEventCounterSource.Log.Push();
try
{
var others = Clients.OthersInGroup(userId);
others.PushItems(pushItem);
GlobalSync.ExtendPush(userId, Context.ConnectionId);
if (pushItem.Type == "settings")
{
var settings = pushItem.Items.First();
if (settings == null) return 0;
settings.Id = MongoDB.Bson.ObjectId.Parse(userId);
settings.ItemId = userId;
Repositories.LegacySettings.Upsert(settings, userId, dateSynced);
}
else
{
var UpsertItem = MapTypeToUpsertAction(pushItem.Type) ?? throw new Exception("Invalid item type.");
foreach (var item in pushItem.Items)
{
UpsertItem(item, userId, dateSynced);
}
}
return await unit.Commit() ? 1 : 0;
}
catch (Exception ex)
{
GlobalSync.ClearPushOperations(userId, Context.ConnectionId);
throw ex;
}
}
public async Task<bool> SyncCompleted(long dateSynced)
{
var userId = Context.User.FindFirstValue("sub");
try
{
UserSettings userSettings = await this.Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
UserSettings userSettings = await this.Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
long lastSynced = Math.Max(dateSynced, userSettings.LastSynced);
long lastSynced = dateSynced > userSettings.LastSynced ? dateSynced : userSettings.LastSynced;
userSettings.LastSynced = lastSynced;
userSettings.LastSynced = lastSynced;
await this.Repositories.UsersSettings.UpsertAsync(userSettings, (u) => u.UserId == userId);
await this.Repositories.UsersSettings.UpsertAsync(userSettings, (u) => u.UserId == userId);
await Clients.OthersInGroup(userId).PushCompleted(lastSynced);
await Clients.OthersInGroup(userId).RemoteSyncCompleted(lastSynced);
return true;
return true;
}
finally
{
GlobalSync.ClearPushOperations(userId, Context.ConnectionId);
}
}
public async IAsyncEnumerable<SyncTransferItem> FetchItems(long lastSyncedTimestamp, [EnumeratorCancellation]
CancellationToken cancellationToken)
private static async IAsyncEnumerable<SyncTransferItemV2> PrepareChunks(Func<string, long, int, Task<IAsyncCursor<SyncItem>>>[] collections, string[] types, string userId, long lastSyncedTimestamp, int size, long maxBytes, int skipChunks)
{
var userId = Context.User.FindFirstValue("sub");
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
if (userSettings.LastSynced > 0 && lastSyncedTimestamp > userSettings.LastSynced)
throw new HubException($"Provided timestamp value is too large. Server timestamp: {userSettings.LastSynced} Sent timestamp: {lastSyncedTimestamp}");
// var client = Clients.Caller;
if (lastSyncedTimestamp > 0 && userSettings.LastSynced == lastSyncedTimestamp)
var chunksProcessed = 0;
for (int i = 0; i < collections.Length; i++)
{
yield return new SyncTransferItem
var type = types[i];
using var cursor = await collections[i](userId, lastSyncedTimestamp, size);
var chunk = new List<SyncItem>();
long totalBytes = 0;
long METADATA_BYTES = 5 * 1024;
while (await cursor.MoveNextAsync())
{
LastSynced = userSettings.LastSynced,
Synced = true
};
yield break;
}
var attachments = await Repositories.Attachments.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var notes = await Repositories.Notes.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var notebooks = await Repositories.Notebooks.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var contents = await Repositories.Contents.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var settings = await Repositories.Settings.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var shortcuts = await Repositories.Shortcuts.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var reminders = await Repositories.Reminders.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var relations = await Repositories.Relations.GetItemsSyncedAfterAsync(userId, lastSyncedTimestamp);
var collections = new Dictionary<string, IEnumerable<object>>
{
["attachment"] = attachments,
["note"] = notes,
["notebook"] = notebooks,
["content"] = contents,
["shortcut"] = shortcuts,
["reminder"] = reminders,
["relation"] = relations,
["settings"] = settings,
};
if (userSettings.VaultKey != null)
{
collections.Add("vaultKey", new object[] { userSettings.VaultKey });
}
var total = collections.Values.Sum((a) => a.Count());
if (total == 0)
{
yield return new SyncTransferItem
{
Synced = true,
LastSynced = userSettings.LastSynced
};
yield break;
}
foreach (var collection in collections)
{
foreach (var item in collection.Value)
{
if (item == null) continue;
// Check the cancellation token regularly so that the server will stop producing items if the client disconnects.
cancellationToken.ThrowIfCancellationRequested();
yield return new SyncTransferItem
if (chunksProcessed++ < skipChunks) continue;
foreach (var item in cursor.Current)
{
LastSynced = userSettings.LastSynced,
Synced = false,
Item = JsonSerializer.Serialize(item),
ItemType = collection.Key,
Total = total,
chunk.Add(item);
totalBytes += item.Length + METADATA_BYTES;
if (totalBytes >= maxBytes)
{
yield return new SyncTransferItemV2
{
Items = chunk,
Type = type,
Count = chunksProcessed
};
totalBytes = 0;
chunk.Clear();
}
}
}
if (chunk.Count > 0)
{
if (chunksProcessed++ < skipChunks) continue;
yield return new SyncTransferItemV2
{
Items = chunk,
Type = type,
Count = chunksProcessed
};
}
}
}
public Task<SyncMetadata> RequestFetch(long lastSyncedTimestamp)
{
return RequestResumableFetch(lastSyncedTimestamp);
}
public async Task<SyncMetadata> RequestResumableFetch(long lastSyncedTimestamp, int cursor = 0)
{
var userId = Context.User.FindFirstValue("sub");
if (GlobalSync.IsUserPushing(userId))
{
throw new HubException("Cannot fetch data while another sync is in progress. Please try again later.");
}
SyncEventCounterSource.Log.Fetch();
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
if (userSettings.LastSynced > 0 && lastSyncedTimestamp > userSettings.LastSynced)
{
throw new HubException($"Provided timestamp value is too large. Server timestamp: {userSettings.LastSynced} Sent timestamp: {lastSyncedTimestamp}. Please run a Force Sync to fix this issue.");
}
// var client = Clients.Caller;
if (lastSyncedTimestamp > 0 && userSettings.LastSynced == lastSyncedTimestamp)
{
return new SyncMetadata
{
LastSynced = userSettings.LastSynced,
};
}
var isResumable = lastSyncedTimestamp == 0;
if (!isResumable) cursor = 0;
var chunks = PrepareChunks(
collections: new[] {
Repositories.LegacySettings.FindItemsSyncedAfter,
Repositories.Attachments.FindItemsSyncedAfter,
Repositories.Notes.FindItemsSyncedAfter,
Repositories.Notebooks.FindItemsSyncedAfter,
Repositories.Contents.FindItemsSyncedAfter,
Repositories.Shortcuts.FindItemsSyncedAfter,
Repositories.Reminders.FindItemsSyncedAfter,
Repositories.Relations.FindItemsSyncedAfter,
},
types: CollectionKeys,
userId,
lastSyncedTimestamp,
size: 1000,
maxBytes: 7 * 1024 * 1024,
skipChunks: cursor
);
await foreach (var chunk in chunks)
{
_ = await Clients.Caller.SendItems(chunk).WaitAsync(TimeSpan.FromMinutes(10));
}
return new SyncMetadata
{
VaultKey = userSettings.VaultKey,
LastSynced = userSettings.LastSynced,
};
}
}
[MessagePack.MessagePackObject]
@@ -291,4 +441,33 @@ namespace Notesnook.API.Hubs
[MessagePack.Key("current")]
public int Current { get; set; }
}
[MessagePack.MessagePackObject]
public struct SyncTransferItemV2
{
[MessagePack.Key("items")]
[JsonPropertyName("items")]
public IEnumerable<SyncItem> Items { get; set; }
[MessagePack.Key("type")]
[JsonPropertyName("type")]
public string Type { get; set; }
[MessagePack.Key("count")]
[JsonPropertyName("count")]
public int Count { get; set; }
}
[MessagePack.MessagePackObject]
public struct SyncMetadata
{
[MessagePack.Key("vaultKey")]
[JsonPropertyName("vaultKey")]
public EncryptedData VaultKey { get; set; }
[MessagePack.Key("lastSynced")]
[JsonPropertyName("lastSynced")]
public long LastSynced { get; set; }
// [MessagePack.Key("total")]
// public long TotalItems { get; set; }
}
}

View File

@@ -0,0 +1,305 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR;
using MongoDB.Driver;
using Notesnook.API.Authorization;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Services;
using Streetwriters.Data.Interfaces;
namespace Notesnook.API.Hubs
{
public interface ISyncV2HubClient
{
Task<bool> SendItems(SyncTransferItemV2 transferItem);
Task<bool> SendVaultKey(EncryptedData vaultKey);
Task<bool> SendMonographs(IEnumerable<Monograph> monographs);
Task PushCompleted();
}
[Authorize("Sync")]
public class SyncV2Hub : Hub<ISyncV2HubClient>
{
private ISyncItemsRepositoryAccessor Repositories { get; }
private readonly IUnitOfWork unit;
private readonly string[] CollectionKeys = [
"settingitem",
"attachment",
"note",
"notebook",
"content",
"shortcut",
"reminder",
"color",
"tag",
"vault",
"relation", // relations must sync at the end to prevent invalid state
];
public SyncV2Hub(ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor, IUnitOfWork unitOfWork)
{
Repositories = syncItemsRepositoryAccessor;
unit = unitOfWork;
}
public override async Task OnConnectedAsync()
{
var result = new SyncRequirement().IsAuthorized(Context.User, new PathString("/hubs/sync/v2"));
if (!result.Succeeded)
{
var reason = result.AuthorizationFailure.FailureReasons.FirstOrDefault();
throw new HubException(reason?.Message ?? "Unauthorized");
}
var id = Context.User.FindFirstValue("sub");
await Groups.AddToGroupAsync(Context.ConnectionId, id);
await base.OnConnectedAsync();
}
private Action<IEnumerable<SyncItem>, string, long> MapTypeToUpsertAction(string type)
{
return type switch
{
"settingitem" => Repositories.Settings.UpsertMany,
"attachment" => Repositories.Attachments.UpsertMany,
"note" => Repositories.Notes.UpsertMany,
"notebook" => Repositories.Notebooks.UpsertMany,
"content" => Repositories.Contents.UpsertMany,
"shortcut" => Repositories.Shortcuts.UpsertMany,
"reminder" => Repositories.Reminders.UpsertMany,
"relation" => Repositories.Relations.UpsertMany,
"color" => Repositories.Colors.UpsertMany,
"vault" => Repositories.Vaults.UpsertMany,
"tag" => Repositories.Tags.UpsertMany,
_ => null,
};
}
public async Task<int> PushItems(string deviceId, SyncTransferItemV2 pushItem)
{
var userId = Context.User.FindFirstValue("sub");
if (string.IsNullOrEmpty(userId)) throw new HubException("Please login to sync.");
SyncEventCounterSource.Log.PushV2();
var stopwatch = new Stopwatch();
stopwatch.Start();
try
{
var UpsertItems = MapTypeToUpsertAction(pushItem.Type) ?? throw new Exception($"Invalid item type: {pushItem.Type}.");
UpsertItems(pushItem.Items, userId, 1);
if (!await unit.Commit()) return 0;
new SyncDeviceService(new SyncDevice(userId, deviceId)).AddIdsToOtherDevices(pushItem.Items.Select((i) => $"{i.ItemId}:{pushItem.Type}").ToList());
return 1;
}
finally
{
stopwatch.Stop();
SyncEventCounterSource.Log.RecordPushDuration(stopwatch.ElapsedMilliseconds);
}
}
public async Task<bool> PushCompleted()
{
var userId = Context.User.FindFirstValue("sub");
await Clients.OthersInGroup(userId).PushCompleted();
return true;
}
private static async IAsyncEnumerable<SyncTransferItemV2> PrepareChunks(Func<string, string[], bool, int, Task<IAsyncCursor<SyncItem>>>[] collections, string[] types, string userId, string[] ids, int size, bool resetSync, long maxBytes)
{
var itemsProcessed = 0;
for (int i = 0; i < collections.Length; i++)
{
var type = types[i];
var filteredIds = ids.Where((id) => id.EndsWith($":{type}")).Select((id) => id.Split(":")[0]).ToArray();
if (!resetSync && filteredIds.Length == 0) continue;
using var cursor = await collections[i](userId, filteredIds, resetSync, size);
var chunk = new List<SyncItem>();
long totalBytes = 0;
long METADATA_BYTES = 5 * 1024;
while (await cursor.MoveNextAsync())
{
foreach (var item in cursor.Current)
{
chunk.Add(item);
totalBytes += item.Length + METADATA_BYTES;
if (totalBytes >= maxBytes)
{
itemsProcessed += chunk.Count;
yield return new SyncTransferItemV2
{
Items = chunk,
Type = type,
Count = itemsProcessed
};
totalBytes = 0;
chunk.Clear();
}
}
}
if (chunk.Count > 0)
{
itemsProcessed += chunk.Count;
yield return new SyncTransferItemV2
{
Items = chunk,
Type = type,
Count = itemsProcessed
};
}
}
}
public async Task<SyncV2Metadata> RequestFetch(string deviceId)
{
var userId = Context.User.FindFirstValue("sub");
if (string.IsNullOrEmpty(userId)) throw new HubException("Please login to sync.");
SyncEventCounterSource.Log.FetchV2();
var device = new SyncDevice(userId, deviceId);
var deviceService = new SyncDeviceService(device);
if (!deviceService.IsDeviceRegistered()) deviceService.RegisterDevice();
device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var isResetSync = deviceService.IsSyncReset();
if (!deviceService.IsUnsynced() &&
!deviceService.IsSyncPending() &&
!isResetSync)
return new SyncV2Metadata { Synced = true };
var stopwatch = new Stopwatch();
stopwatch.Start();
try
{
string[] ids = deviceService.FetchUnsyncedIds();
var chunks = PrepareChunks(
collections: [
Repositories.Settings.FindItemsById,
Repositories.Attachments.FindItemsById,
Repositories.Notes.FindItemsById,
Repositories.Notebooks.FindItemsById,
Repositories.Contents.FindItemsById,
Repositories.Shortcuts.FindItemsById,
Repositories.Reminders.FindItemsById,
Repositories.Colors.FindItemsById,
Repositories.Tags.FindItemsById,
Repositories.Vaults.FindItemsById,
Repositories.Relations.FindItemsById,
],
types: CollectionKeys,
userId,
ids,
size: 1000,
resetSync: isResetSync,
maxBytes: 7 * 1024 * 1024
);
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId.Equals(userId));
if (userSettings.VaultKey != null)
{
if (!await Clients.Caller.SendVaultKey(userSettings.VaultKey).WaitAsync(TimeSpan.FromMinutes(10))) throw new HubException("Client rejected vault key.");
}
await foreach (var chunk in chunks)
{
if (!await Clients.Caller.SendItems(chunk).WaitAsync(TimeSpan.FromMinutes(10))) throw new HubException("Client rejected sent items.");
if (!isResetSync)
{
var syncedIds = chunk.Items.Select((i) => $"{i.ItemId}:{chunk.Type}").ToHashSet();
ids = ids.Where((id) => !syncedIds.Contains(id)).ToArray();
deviceService.WritePendingIds(ids);
}
}
// var unsyncedMonographs = ids.Where((id) => id.EndsWith(":monograph")).ToHashSet();
// var unsyncedMonographIds = unsyncedMonographs.Select((id) => id.Split(":")[0]).ToArray();
// var userMonographs = isResetSync
// ? await Repositories.Monographs.FindAsync(m => m.UserId == userId)
// : await Repositories.Monographs.FindAsync(m => m.UserId == userId && unsyncedMonographIds.Contains(m.ItemId));
// if (userMonographs.Any() && !await Clients.Caller.SendMonographs(userMonographs).WaitAsync(TimeSpan.FromMinutes(10)))
// throw new HubException("Client rejected monographs.");
deviceService.Reset();
return new SyncV2Metadata
{
Synced = true,
};
}
finally
{
stopwatch.Stop();
SyncEventCounterSource.Log.RecordFetchDuration(stopwatch.ElapsedMilliseconds);
}
}
}
[MessagePack.MessagePackObject]
public struct SyncV2Metadata
{
[MessagePack.Key("synced")]
[JsonPropertyName("synced")]
public bool Synced { get; set; }
}
[MessagePack.MessagePackObject]
public struct SyncV2TransferItem
{
[MessagePack.Key("items")]
[JsonPropertyName("items")]
public IEnumerable<SyncItem> Items { get; set; }
[MessagePack.Key("type")]
[JsonPropertyName("type")]
public string Type { get; set; }
[MessagePack.Key("final")]
[JsonPropertyName("final")]
public bool Final { get; set; }
[MessagePack.Key("vaultKey")]
[JsonPropertyName("vaultKey")]
public EncryptedData VaultKey { get; set; }
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Interfaces
{
public interface IEncrypted
{
string Cipher { get; set; }
string IV { get; set; }
long Length { get; set; }
string Salt { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Interfaces
{
public interface IEncrypted
{
string Cipher { get; set; }
string IV { get; set; }
long Length { get; set; }
string Salt { get; set; }
}
}

View File

@@ -1,33 +1,33 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Notesnook.API.Models;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
public interface IMonograph : IDocument
{
string Title { get; set; }
string UserId { get; set; }
byte[] CompressedContent { get; set; }
EncryptedData EncryptedContent { get; set; }
long DatePublished { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Notesnook.API.Models;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
public interface IMonograph : IDocument
{
string Title { get; set; }
string UserId { get; set; }
byte[] CompressedContent { get; set; }
EncryptedData EncryptedContent { get; set; }
long DatePublished { get; set; }
}
}

View File

@@ -1,40 +1,40 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading;
using System.Threading.Tasks;
using Amazon.S3.Model;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
public interface IS3Service
{
Task DeleteObjectAsync(string userId, string name);
Task DeleteDirectoryAsync(string userId);
Task<long?> GetObjectSizeAsync(string userId, string name);
string GetUploadObjectUrl(string userId, string name);
string GetDownloadObjectUrl(string userId, string name);
Task<MultipartUploadMeta> StartMultipartUploadAsync(string userId, string name, int parts, string uploadId = null);
Task AbortMultipartUploadAsync(string userId, string name, string uploadId);
Task CompleteMultipartUploadAsync(string userId, CompleteMultipartUploadRequest uploadRequest);
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading;
using System.Threading.Tasks;
using Amazon.S3.Model;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
public interface IS3Service
{
Task DeleteObjectAsync(string userId, string name);
Task DeleteDirectoryAsync(string userId);
Task<long> GetObjectSizeAsync(string userId, string name);
string GetUploadObjectUrl(string userId, string name);
string GetDownloadObjectUrl(string userId, string name);
Task<MultipartUploadMeta> StartMultipartUploadAsync(string userId, string name, int parts, string uploadId = null);
Task AbortMultipartUploadAsync(string userId, string name, string uploadId);
Task CompleteMultipartUploadAsync(string userId, CompleteMultipartUploadRequest uploadRequest);
}
}

View File

@@ -1,43 +0,0 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Text.Json.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using Notesnook.API.Models;
using Streetwriters.Common.Attributes;
using Streetwriters.Common.Converters;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
[BsonSerializer(typeof(ImpliedImplementationInterfaceSerializer<ISyncItem, SyncItem>))]
[JsonInterfaceConverter(typeof(InterfaceConverter<ISyncItem, SyncItem>))]
public interface ISyncItem
{
long DateSynced
{
get; set;
}
string UserId { get; set; }
string Algorithm { get; set; }
string IV { get; set; }
}
}

View File

@@ -26,14 +26,18 @@ namespace Notesnook.API.Interfaces
{
public interface ISyncItemsRepositoryAccessor
{
SyncItemsRepository<Note> Notes { get; }
SyncItemsRepository<Notebook> Notebooks { get; }
SyncItemsRepository<Shortcut> Shortcuts { get; }
SyncItemsRepository<Reminder> Reminders { get; }
SyncItemsRepository<Relation> Relations { get; }
SyncItemsRepository<Content> Contents { get; }
SyncItemsRepository<Setting> Settings { get; }
SyncItemsRepository<Attachment> Attachments { get; }
SyncItemsRepository Notes { get; }
SyncItemsRepository Notebooks { get; }
SyncItemsRepository Shortcuts { get; }
SyncItemsRepository Reminders { get; }
SyncItemsRepository Relations { get; }
SyncItemsRepository Contents { get; }
SyncItemsRepository LegacySettings { get; }
SyncItemsRepository Attachments { get; }
SyncItemsRepository Settings { get; }
SyncItemsRepository Colors { get; }
SyncItemsRepository Vaults { get; }
SyncItemsRepository Tags { get; }
Repository<UserSettings> UsersSettings { get; }
Repository<Monograph> Monographs { get; }
}

View File

@@ -1,35 +1,35 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading;
using System.Threading.Tasks;
using Notesnook.API.Models.Responses;
using Streetwriters.Common.Interfaces;
namespace Notesnook.API.Interfaces
{
public interface IUserService
{
Task CreateUserAsync();
Task<bool> DeleteUserAsync(string userId, string jti);
Task<bool> ResetUserAsync(string userId, bool removeAttachments);
Task<UserResponse> GetUserAsync(bool repair = true);
Task SetUserAttachmentsKeyAsync(string userId, IEncrypted key);
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
namespace Notesnook.API.Interfaces
{
public interface IUserService
{
Task CreateUserAsync();
Task DeleteUserAsync(string userId);
Task DeleteUserAsync(string userId, string jti, string password);
Task<bool> ResetUserAsync(string userId, bool removeAttachments);
Task<UserResponse> GetUserAsync(string userId);
Task SetUserKeysAsync(string userId, UserKeys keys);
}
}

View File

@@ -1,42 +1,42 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Notesnook.API.Models;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
namespace Notesnook.API.Interfaces
{
public interface IUserSettings : IDocument
{
string UserId { get; set; }
long LastSynced
{
get; set;
}
EncryptedData VaultKey
{
get; set;
}
string Salt { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Notesnook.API.Models;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
namespace Notesnook.API.Interfaces
{
public interface IUserSettings : IDocument
{
string UserId { get; set; }
long LastSynced
{
get; set;
}
EncryptedData VaultKey
{
get; set;
}
string Salt { get; set; }
}
}

View File

@@ -1,26 +1,26 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class Algorithms
{
public static string Default => "xcha-argon2i13-7";
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class Algorithms
{
public static string Default => "xcha-argon2i13-7";
}
}

View File

@@ -1,161 +1,159 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Data.Attributes;
namespace Notesnook.API.Models
{
[BsonCollection("notesnook", "announcements")]
public class Announcement
{
public Announcement()
{
this.Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("id")]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("timestamp")]
[BsonElement("timestamp")]
public long Timestamp { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("isActive")]
[BsonElement("isActive")]
public bool IsActive { get; set; }
[JsonPropertyName("userTypes")]
[BsonElement("userTypes")]
public string[] UserTypes { get; set; }
[JsonPropertyName("appVersion")]
[BsonElement("appVersion")]
public int AppVersion { get; set; }
[JsonPropertyName("body")]
[BsonElement("body")]
public BodyComponent[] Body { get; set; }
[JsonIgnore]
[BsonElement("userIds")]
public string[] UserIds { get; set; }
[Obsolete]
[JsonPropertyName("title")]
[DataMember(Name = "title")]
[BsonElement("title")]
public string Title { get; set; }
[Obsolete]
[JsonPropertyName("description")]
[BsonElement("description")]
public string Description { get; set; }
[Obsolete]
[JsonPropertyName("callToActions")]
[BsonElement("callToActions")]
public CallToAction[] CallToActions { get; set; }
}
public class BodyComponent
{
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("style")]
[BsonElement("style")]
public Style Style { get; set; }
[JsonPropertyName("src")]
[BsonElement("src")]
public string Src { get; set; }
[JsonPropertyName("text")]
[BsonElement("text")]
public string Text { get; set; }
[JsonPropertyName("value")]
[BsonElement("value")]
public string Value { get; set; }
[JsonPropertyName("items")]
[BsonElement("items")]
public BodyComponent[] Items { get; set; }
[JsonPropertyName("actions")]
[BsonElement("actions")]
public CallToAction[] Actions { get; set; }
}
public class Style
{
[JsonPropertyName("marginTop")]
[BsonElement("marginTop")]
public int MarginTop { get; set; }
[JsonPropertyName("marginBottom")]
[BsonElement("marginBottom")]
public int MarginBottom { get; set; }
[JsonPropertyName("textAlign")]
[BsonElement("textAlign")]
public string TextAlign { get; set; }
}
public class CallToAction
{
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("data")]
[BsonElement("data")]
public string Data { get; set; }
[JsonPropertyName("title")]
[BsonElement("title")]
public string Title { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Notesnook.API.Models
{
public class Announcement
{
public Announcement()
{
this.Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonElement("id")]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("timestamp")]
[BsonElement("timestamp")]
public long Timestamp { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("isActive")]
[BsonElement("isActive")]
public bool IsActive { get; set; }
[JsonPropertyName("userTypes")]
[BsonElement("userTypes")]
public string[] UserTypes { get; set; }
[JsonPropertyName("appVersion")]
[BsonElement("appVersion")]
public int AppVersion { get; set; }
[JsonPropertyName("body")]
[BsonElement("body")]
public BodyComponent[] Body { get; set; }
[JsonIgnore]
[BsonElement("userIds")]
public string[] UserIds { get; set; }
[Obsolete]
[JsonPropertyName("title")]
[DataMember(Name = "title")]
[BsonElement("title")]
public string Title { get; set; }
[Obsolete]
[JsonPropertyName("description")]
[BsonElement("description")]
public string Description { get; set; }
[Obsolete]
[JsonPropertyName("callToActions")]
[BsonElement("callToActions")]
public CallToAction[] CallToActions { get; set; }
}
public class BodyComponent
{
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("style")]
[BsonElement("style")]
public Style Style { get; set; }
[JsonPropertyName("src")]
[BsonElement("src")]
public string Src { get; set; }
[JsonPropertyName("text")]
[BsonElement("text")]
public string Text { get; set; }
[JsonPropertyName("value")]
[BsonElement("value")]
public string Value { get; set; }
[JsonPropertyName("items")]
[BsonElement("items")]
public BodyComponent[] Items { get; set; }
[JsonPropertyName("actions")]
[BsonElement("actions")]
public CallToAction[] Actions { get; set; }
}
public class Style
{
[JsonPropertyName("marginTop")]
[BsonElement("marginTop")]
public int MarginTop { get; set; }
[JsonPropertyName("marginBottom")]
[BsonElement("marginBottom")]
public int MarginBottom { get; set; }
[JsonPropertyName("textAlign")]
[BsonElement("textAlign")]
public string TextAlign { get; set; }
}
public class CallToAction
{
[JsonPropertyName("type")]
[BsonElement("type")]
public string Type { get; set; }
[JsonPropertyName("platforms")]
[BsonElement("platforms")]
public string[] Platforms { get; set; }
[JsonPropertyName("data")]
[BsonElement("data")]
public string Data { get; set; }
[JsonPropertyName("title")]
[BsonElement("title")]
public string Title { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using Amazon.S3.Model;
namespace Notesnook.API.Models;
public class CompleteMultipartUploadRequestWrapper
{
public string Key { get; set; }
public List<PartETagWrapper> PartETags { get; set; }
public string UploadId { get; set; }
public CompleteMultipartUploadRequest ToRequest()
{
CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest();
completeMultipartUploadRequest.Key = Key;
completeMultipartUploadRequest.UploadId = UploadId;
completeMultipartUploadRequest.PartETags = [];
foreach (var partETagWrapper in PartETags)
{
var partETag = new PartETag
{
PartNumber = partETagWrapper.PartNumber,
ETag = partETagWrapper.ETag
};
completeMultipartUploadRequest.PartETags.Add(partETag);
}
return completeMultipartUploadRequest;
}
}

View File

@@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Notesnook.API.Models
{
public class DeleteAccountForm
{
[Required]
public string Password
{
get; set;
}
}
}

View File

@@ -1,56 +1,75 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Notesnook.API.Models
{
public class EncryptedData : IEncrypted
{
[JsonPropertyName("iv")]
[BsonElement("iv")]
[DataMember(Name = "iv")]
public string IV
{
get; set;
}
[JsonPropertyName("cipher")]
[BsonElement("cipher")]
[DataMember(Name = "cipher")]
public string Cipher
{
get; set;
}
[JsonPropertyName("length")]
[BsonElement("length")]
[DataMember(Name = "length")]
public long Length { get; set; }
[JsonPropertyName("salt")]
[BsonElement("salt")]
[DataMember(Name = "salt")]
public string Salt { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
namespace Notesnook.API.Models
{
[MessagePack.MessagePackObject]
public class EncryptedData : IEncrypted
{
[MessagePack.Key("iv")]
[JsonPropertyName("iv")]
[BsonElement("iv")]
[DataMember(Name = "iv")]
public string IV
{
get; set;
}
[MessagePack.Key("cipher")]
[JsonPropertyName("cipher")]
[BsonElement("cipher")]
[DataMember(Name = "cipher")]
public string Cipher
{
get; set;
}
[MessagePack.Key("length")]
[JsonPropertyName("length")]
[BsonElement("length")]
[DataMember(Name = "length")]
public long Length { get; set; }
[MessagePack.Key("salt")]
[JsonPropertyName("salt")]
[BsonElement("salt")]
[DataMember(Name = "salt")]
public string Salt { get; set; }
public override bool Equals(object obj)
{
if (obj is EncryptedData encryptedData)
{
return IV == encryptedData.IV && Salt == encryptedData.Salt && Cipher == encryptedData.Cipher && Length == encryptedData.Length;
}
return base.Equals(obj);
}
public bool IsEmpty()
{
return this.Cipher == null && this.IV == null && this.Length == 0 && this.Salt == null;
}
}
}

View File

@@ -1,62 +1,96 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces;
using Streetwriters.Data.Attributes;
namespace Notesnook.API.Models
{
[BsonCollection("notesnook", "monographs")]
public class Monograph : IMonograph
{
public Monograph()
{
Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("selfDestruct")]
public bool SelfDestruct { get; set; }
[JsonPropertyName("encryptedContent")]
public EncryptedData EncryptedContent { get; set; }
[JsonPropertyName("datePublished")]
public long DatePublished { get; set; }
[JsonPropertyName("content")]
[BsonIgnore]
public string Content { get; set; }
[JsonIgnore]
public byte[] CompressedContent { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Runtime.Serialization;
namespace Notesnook.API.Models
{
public class ObjectWithId
{
[BsonId]
[BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)]
public string Id
{
get; set;
}
public string ItemId
{
get; set;
}
}
public class Monograph
{
public Monograph()
{
Id = ObjectId.GenerateNewId().ToString();
}
[DataMember(Name = "id")]
[JsonPropertyName("id")]
[MessagePack.Key("id")]
public string ItemId
{
get; set;
}
[BsonId]
[BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)]
[JsonIgnore]
[MessagePack.IgnoreMember]
public string Id
{
get; set;
}
[JsonPropertyName("title")]
public string Title { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("selfDestruct")]
public bool SelfDestruct { get; set; }
[JsonPropertyName("encryptedContent")]
public EncryptedData EncryptedContent { get; set; }
[JsonPropertyName("datePublished")]
public long DatePublished { get; set; }
[JsonPropertyName("content")]
[BsonIgnore]
public string Content { get; set; }
[JsonIgnore]
public byte[] CompressedContent { get; set; }
[JsonPropertyName("password")]
public EncryptedData Password { get; set; }
[JsonPropertyName("deleted")]
public bool Deleted { get; set; }
}
}

View File

@@ -1,27 +1,27 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class MultipartUploadMeta
{
public string UploadId { get; set; }
public string[] Parts { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class MultipartUploadMeta
{
public string UploadId { get; set; }
public string[] Parts { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace Notesnook.API.Models;
public class PartETagWrapper
{
public int PartNumber { get; set; }
public string ETag { get; set; }
}

View File

@@ -1,14 +1,14 @@
using System.Text.Json.Serialization;
using Streetwriters.Common.Models;
namespace Notesnook.API.Models.Responses
{
public class SignupResponse : Response
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("errors")]
public string[] Errors { get; set; }
}
}
using System.Text.Json.Serialization;
using Streetwriters.Common.Models;
namespace Notesnook.API.Models.Responses
{
public class SignupResponse : Response
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("errors")]
public string[] Errors { get; set; }
}
}

View File

@@ -1,22 +1,28 @@
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
namespace Notesnook.API.Models.Responses
{
public class UserResponse : UserModel, IResponse
{
[JsonPropertyName("salt")]
public string Salt { get; set; }
[JsonPropertyName("attachmentsKey")]
public EncryptedData AttachmentsKey { get; set; }
[JsonPropertyName("subscription")]
public ISubscription Subscription { get; set; }
[JsonIgnore]
public bool Success { get; set; }
public int StatusCode { get; set; }
}
}
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Models;
namespace Notesnook.API.Models.Responses
{
public class UserResponse : UserModel, IResponse
{
[JsonPropertyName("salt")]
public string Salt { get; set; }
[JsonPropertyName("attachmentsKey")]
public EncryptedData AttachmentsKey { get; set; }
[JsonPropertyName("monographPasswordsKey")]
public EncryptedData MonographPasswordsKey { get; set; }
[JsonPropertyName("subscription")]
public ISubscription Subscription { get; set; }
[JsonPropertyName("profile")]
public EncryptedData Profile { get; set; }
[JsonIgnore]
public bool Success { get; set; }
public int StatusCode { get; set; }
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class S3Options
{
public string ServiceUrl { get; set; }
public string Region { get; set; }
public string AccessKeyId { get; set; }
public string SecretAccessKey { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Notesnook.API.Models
{
public class S3Options
{
public string ServiceUrl { get; set; }
public string Region { get; set; }
public string AccessKeyId { get; set; }
public string SecretAccessKey { get; set; }
}
}

View File

@@ -17,19 +17,24 @@ You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using Notesnook.API.Interfaces;
using Streetwriters.Data.Attributes;
namespace Notesnook.API.Models
{
public class SyncItem : ISyncItem
[MessagePack.MessagePackObject]
public class SyncItem
{
[IgnoreDataMember]
[MessagePack.IgnoreMember]
[JsonPropertyName("dateSynced")]
public long DateSynced
{
@@ -38,6 +43,7 @@ namespace Notesnook.API.Models
[DataMember(Name = "userId")]
[JsonPropertyName("userId")]
[MessagePack.Key("userId")]
public string UserId
{
get; set;
@@ -45,6 +51,7 @@ namespace Notesnook.API.Models
[JsonPropertyName("iv")]
[DataMember(Name = "iv")]
[MessagePack.Key("iv")]
[Required]
public string IV
{
@@ -54,6 +61,7 @@ namespace Notesnook.API.Models
[JsonPropertyName("cipher")]
[DataMember(Name = "cipher")]
[MessagePack.Key("cipher")]
[Required]
public string Cipher
{
@@ -62,6 +70,7 @@ namespace Notesnook.API.Models
[DataMember(Name = "id")]
[JsonPropertyName("id")]
[MessagePack.Key("id")]
public string ItemId
{
get; set;
@@ -71,6 +80,7 @@ namespace Notesnook.API.Models
[BsonIgnoreIfDefault]
[BsonRepresentation(BsonType.ObjectId)]
[JsonIgnore]
[MessagePack.IgnoreMember]
public ObjectId Id
{
get; set;
@@ -78,6 +88,7 @@ namespace Notesnook.API.Models
[JsonPropertyName("length")]
[DataMember(Name = "length")]
[MessagePack.Key("length")]
[Required]
public long Length
{
@@ -86,6 +97,7 @@ namespace Notesnook.API.Models
[JsonPropertyName("v")]
[DataMember(Name = "v")]
[MessagePack.Key("v")]
[Required]
public double Version
{
@@ -94,6 +106,7 @@ namespace Notesnook.API.Models
[JsonPropertyName("alg")]
[DataMember(Name = "alg")]
[MessagePack.Key("alg")]
[Required]
public string Algorithm
{
@@ -101,27 +114,92 @@ namespace Notesnook.API.Models
} = Algorithms.Default;
}
[BsonCollection("notesnook", "attachments")]
public class Attachment : SyncItem { }
public class SyncItemBsonSerializer : SerializerBase<SyncItem>
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, SyncItem value)
{
var writer = context.Writer;
writer.WriteStartDocument();
[BsonCollection("notesnook", "content")]
public class Content : SyncItem { }
if (value.Id != ObjectId.Empty)
{
writer.WriteName("_id");
writer.WriteObjectId(value.Id);
}
[BsonCollection("notesnook", "notes")]
public class Note : SyncItem { }
writer.WriteName("DateSynced");
writer.WriteInt64(value.DateSynced);
[BsonCollection("notesnook", "notebooks")]
public class Notebook : SyncItem { }
writer.WriteName("UserId");
writer.WriteString(value.UserId);
[BsonCollection("notesnook", "relations")]
public class Relation : SyncItem { }
writer.WriteName("IV");
writer.WriteString(value.IV);
[BsonCollection("notesnook", "reminders")]
public class Reminder : SyncItem { }
writer.WriteName("Cipher");
writer.WriteString(value.Cipher);
[BsonCollection("notesnook", "settings")]
public class Setting : SyncItem { }
writer.WriteName("ItemId");
writer.WriteString(value.ItemId);
[BsonCollection("notesnook", "shortcuts")]
public class Shortcut : SyncItem { }
writer.WriteName("Length");
writer.WriteInt64(value.Length);
writer.WriteName("Version");
writer.WriteDouble(value.Version);
writer.WriteName("Algorithm");
writer.WriteString(value.Algorithm);
writer.WriteEndDocument();
}
public override SyncItem Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var syncItem = new SyncItem();
var bsonReader = context.Reader;
bsonReader.ReadStartDocument();
while (bsonReader.ReadBsonType() != BsonType.EndOfDocument)
{
var fieldName = bsonReader.ReadName();
switch (fieldName)
{
case "DateSynced":
syncItem.DateSynced = bsonReader.ReadInt64();
break;
case "UserId":
syncItem.UserId = bsonReader.ReadString();
break;
case "IV":
syncItem.IV = bsonReader.ReadString();
break;
case "Cipher":
syncItem.Cipher = bsonReader.ReadString();
break;
case "ItemId":
syncItem.ItemId = bsonReader.ReadString();
break;
case "_id":
syncItem.Id = bsonReader.ReadObjectId();
break;
case "Length":
syncItem.Length = bsonReader.ReadInt64();
break;
case "Version":
syncItem.Version = bsonReader.ReadDouble();
break;
case "Algorithm":
syncItem.Algorithm = bsonReader.ReadString();
break;
default:
bsonReader.SkipValue();
break;
}
}
bsonReader.ReadEndDocument();
return syncItem;
}
}
}

View File

@@ -17,17 +17,11 @@ You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Streetwriters.Identity.Models
namespace Notesnook.API.Models
{
public class DeleteAccountForm
{
[Required]
public string Password
{
get; set;
}
}
}
public class UserKeys
{
public EncryptedData AttachmentsKey { get; set; }
public EncryptedData MonographPasswordsKey { get; set; }
}
}

View File

@@ -1,44 +1,50 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces;
using Streetwriters.Data.Attributes;
namespace Notesnook.API.Models
{
[BsonCollection("notesnook", "user_settings")]
public class UserSettings : IUserSettings
{
public UserSettings()
{
this.Id = ObjectId.GenerateNewId().ToString();
}
public string UserId { get; set; }
public long LastSynced { get; set; }
public string Salt { get; set; }
public EncryptedData VaultKey { get; set; }
public EncryptedData AttachmentsKey { get; set; }
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Notesnook.API.Interfaces;
namespace Notesnook.API.Models
{
public class Limit
{
public long Value { get; set; }
public long UpdatedAt { get; set; }
}
public class UserSettings : IUserSettings
{
public UserSettings()
{
this.Id = ObjectId.GenerateNewId().ToString();
}
public string UserId { get; set; }
public long LastSynced { get; set; }
public string Salt { get; set; }
public EncryptedData VaultKey { get; set; }
public EncryptedData AttachmentsKey { get; set; }
public EncryptedData MonographPasswordsKey { get; set; }
public Limit StorageLimit { get; set; }
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
}
}

View File

@@ -1,24 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<StartupObject>Notesnook.API.Program</StartupObject>
<LangVersion>10.0</LangVersion>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.7.12.5" />
<PackageReference Include="AWSSDK.Core" Version="3.7.304.31" />
<PackageReference Include="DotNetEnv" Version="2.3.0" />
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
<PackageReference Include="IdentityModel.AspNetCore.OAuth2Introspection" Version="6.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="6.0.1-rc2.2" />
<PackageReference Include="AWSSDK.S3" Version="3.7.9.21" />
<PackageReference Include="AWSSDK.S3" Version="3.7.310.8" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
</ItemGroup>
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.9.0-alpha.2" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.8.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Streetwriters.Common\Streetwriters.Common.csproj" />
@@ -26,4 +25,4 @@
</ItemGroup>
</Project>
</Project>

View File

@@ -1,65 +1,64 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if !DEBUG
using System.Net;
#endif
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Streetwriters.Common;
using System.Linq;
namespace Notesnook.API
{
public class Program
{
public static async Task Main(string[] args)
{
#if DEBUG
DotNetEnv.Env.TraversePath().Load(".env.local");
#else
DotNetEnv.Env.TraversePath().Load(".env");
#endif
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseStartup<Startup>()
.UseKestrel((options) =>
{
options.Limits.MaxRequestBodySize = long.MaxValue;
options.ListenAnyIP(Servers.NotesnookAPI.Port);
if (Servers.NotesnookAPI.IsSecure)
{
options.ListenAnyIP(443, listenerOptions =>
{
listenerOptions.UseHttps(Servers.NotesnookAPI.SSLCertificate);
});
}
});
});
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using System.Net;
namespace Notesnook.API
{
public class Program
{
public static async Task Main(string[] args)
{
#if (DEBUG || STAGING)
DotNetEnv.Env.TraversePath().Load(".env.local");
#else
DotNetEnv.Env.TraversePath().Load(".env");
#endif
IHost host = CreateHostBuilder(args).Build();
await host.RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseStartup<Startup>()
.UseKestrel((options) =>
{
options.Limits.MaxRequestBodySize = long.MaxValue;
options.ListenAnyIP(Servers.NotesnookAPI.Port);
if (Servers.NotesnookAPI.IsSecure)
{
options.ListenAnyIP(443, listenerOptions =>
{
listenerOptions.UseHttps(Servers.NotesnookAPI.SSLCertificate);
});
}
options.Listen(IPAddress.Parse("127.0.0.1"), 5067);
});
});
}
}

View File

@@ -24,51 +24,79 @@ using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel;
using Microsoft.VisualBasic;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Hubs;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Streetwriters.Common;
using Streetwriters.Data.DbContexts;
using Streetwriters.Data.Interfaces;
using Streetwriters.Data.Repositories;
namespace Notesnook.API.Repositories
{
public class SyncItemsRepository<T> : Repository<T> where T : SyncItem
public class SyncItemsRepository : Repository<SyncItem>
{
public SyncItemsRepository(IDbContext dbContext) : base(dbContext)
private readonly string collectionName;
public SyncItemsRepository(IDbContext dbContext, IMongoCollection<SyncItem> collection) : base(dbContext, collection)
{
Collection.Indexes.CreateOne(new CreateIndexModel<T>(Builders<T>.IndexKeys.Ascending(i => i.UserId).Descending(i => i.DateSynced)));
Collection.Indexes.CreateOne(new CreateIndexModel<T>(Builders<T>.IndexKeys.Ascending(i => i.UserId).Ascending((i) => i.ItemId)));
Collection.Indexes.CreateOne(new CreateIndexModel<T>(Builders<T>.IndexKeys.Ascending(i => i.UserId)));
this.collectionName = collection.CollectionNamespace.CollectionName;
}
private readonly List<string> ALGORITHMS = new List<string> { Algorithms.Default };
private readonly List<string> ALGORITHMS = [Algorithms.Default];
private bool IsValidAlgorithm(string algorithm)
{
return ALGORITHMS.Contains(algorithm);
}
public async Task<IEnumerable<T>> GetItemsSyncedAfterAsync(string userId, long timestamp)
public Task<long> CountItemsSyncedAfterAsync(string userId, long timestamp)
{
var cursor = await Collection.FindAsync(n => (n.DateSynced > timestamp) && n.UserId.Equals(userId));
return cursor.ToList();
var filter = Builders<SyncItem>.Filter.And(Builders<SyncItem>.Filter.Gt("DateSynced", timestamp), Builders<SyncItem>.Filter.Eq("UserId", userId));
return Collection.CountDocumentsAsync(filter);
}
public Task<IAsyncCursor<SyncItem>> FindItemsSyncedAfter(string userId, long timestamp, int batchSize)
{
var filter = Builders<SyncItem>.Filter.And(Builders<SyncItem>.Filter.Gt("DateSynced", timestamp), Builders<SyncItem>.Filter.Eq("UserId", userId));
return Collection.FindAsync(filter, new FindOptions<SyncItem>
{
BatchSize = batchSize,
AllowDiskUse = true,
AllowPartialResults = false,
NoCursorTimeout = true,
Sort = new SortDefinitionBuilder<SyncItem>().Ascending("_id")
});
}
// public async Task DeleteIdsAsync(string[] ids, string userId, CancellationToken token = default(CancellationToken))
// {
// await Collection.DeleteManyAsync<T>((i) => ids.Contains(i.Id) && i.UserId == userId, token);
// }
public Task<IAsyncCursor<SyncItem>> FindItemsById(string userId, IEnumerable<string> ids, bool all, int batchSize)
{
var filters = new List<FilterDefinition<SyncItem>>(new[] { Builders<SyncItem>.Filter.Eq("UserId", userId) });
if (!all) filters.Add(Builders<SyncItem>.Filter.In("ItemId", ids));
return Collection.FindAsync(Builders<SyncItem>.Filter.And(filters), new FindOptions<SyncItem>
{
BatchSize = batchSize,
AllowDiskUse = true,
AllowPartialResults = false,
NoCursorTimeout = true
});
}
public void DeleteByUserId(string userId)
{
dbContext.AddCommand((handle, ct) => Collection.DeleteManyAsync<T>(handle, (i) => i.UserId == userId, cancellationToken: ct));
var filter = Builders<SyncItem>.Filter.Eq("UserId", userId);
var writes = new List<WriteModel<SyncItem>>
{
new DeleteManyModel<SyncItem>(filter)
};
dbContext.AddCommand((handle, ct) => Collection.BulkWriteAsync(handle, writes, options: null, ct));
}
public async Task UpsertAsync(T item, string userId, long dateSynced)
public void Upsert(SyncItem item, string userId, long dateSynced)
{
if (item.Length > 15 * 1024 * 1024)
{
throw new Exception($"Size of item \"{item.ItemId}\" is too large. Maximum allowed size is 15 MB.");
@@ -79,30 +107,92 @@ namespace Notesnook.API.Repositories
throw new Exception($"Invalid alg identifier {item.Algorithm}");
}
// Handle case where the cipher is corrupted.
if (!IsBase64String(item.Cipher))
{
Slogger<SyncHub>.Error("Upsert", "Corrupted", item.ItemId, item.Length.ToString(), item.Cipher);
throw new Exception($"Corrupted item \"{item.ItemId}\" in collection \"{this.collectionName}\". Please report this error to support@streetwriters.co.");
}
item.DateSynced = dateSynced;
item.UserId = userId;
await base.UpsertAsync(item, (x) => (x.ItemId == item.ItemId) && x.UserId == userId);
var filter = Builders<SyncItem>.Filter.And(
Builders<SyncItem>.Filter.Eq("UserId", userId),
Builders<SyncItem>.Filter.Eq("ItemId", item.ItemId)
);
dbContext.AddCommand((handle, ct) => Collection.ReplaceOneAsync(handle, filter, item, new ReplaceOptions { IsUpsert = true }, ct));
}
public void Upsert(T item, string userId, long dateSynced)
public void UpsertMany(IEnumerable<SyncItem> items, string userId, long dateSynced)
{
if (item.Length > 15 * 1024 * 1024)
var userIdFilter = Builders<SyncItem>.Filter.Eq("UserId", userId);
var writes = new List<WriteModel<SyncItem>>();
foreach (var item in items)
{
throw new Exception($"Size of item \"{item.ItemId}\" is too large. Maximum allowed size is 15 MB.");
if (item.Length > 15 * 1024 * 1024)
{
throw new Exception($"Size of item \"{item.ItemId}\" is too large. Maximum allowed size is 15 MB.");
}
if (!IsValidAlgorithm(item.Algorithm))
{
throw new Exception($"Invalid alg identifier {item.Algorithm}");
}
// Handle case where the cipher is corrupted.
if (!IsBase64String(item.Cipher))
{
Slogger<SyncHub>.Error("Upsert", "Corrupted", item.ItemId, item.Length.ToString(), item.Cipher);
throw new Exception($"Corrupted item \"{item.ItemId}\" in collection \"{this.collectionName}\". Please report this error to support@streetwriters.co.");
}
var filter = Builders<SyncItem>.Filter.And(
userIdFilter,
Builders<SyncItem>.Filter.Eq("ItemId", item.ItemId)
);
item.DateSynced = dateSynced;
item.UserId = userId;
writes.Add(new ReplaceOneModel<SyncItem>(filter, item)
{
IsUpsert = true
});
}
dbContext.AddCommand((handle, ct) => Collection.BulkWriteAsync(handle, writes, options: new BulkWriteOptions { IsOrdered = false }, ct));
}
if (!IsValidAlgorithm(item.Algorithm))
{
throw new Exception($"Invalid alg identifier {item.Algorithm}");
}
private static bool IsBase64String(string value)
{
if (value == null || value.Length == 0 || value.Contains(' ') || value.Contains('\t') || value.Contains('\r') || value.Contains('\n'))
return false;
var index = value.Length - 1;
if (value[index] == '=')
index--;
if (value[index] == '=')
index--;
for (var i = 0; i <= index; i++)
if (IsInvalidBase64Char(value[i]))
return false;
return true;
}
item.DateSynced = dateSynced;
item.UserId = userId;
// await base.UpsertAsync(item, (x) => (x.ItemId == item.ItemId) && x.UserId == userId);
base.Upsert(item, (x) => (x.ItemId == item.ItemId) && x.UserId == userId);
private static bool IsInvalidBase64Char(char value)
{
var code = (int)value;
// 1 - 9
if (code >= 48 && code <= 57)
return false;
// A - Z
if (code >= 65 && code <= 90)
return false;
// a - z
if (code >= 97 && code <= 122)
return false;
// - & _
return code != 45 && code != 95;
}
}
}

View File

@@ -42,7 +42,8 @@ namespace Notesnook.API.Services
public class S3Service : IS3Service
{
private readonly string BUCKET_NAME = "nn-attachments";
private readonly string BUCKET_NAME = Constants.S3_BUCKET_NAME ?? "";
private readonly string INTERNAL_BUCKET_NAME = Constants.S3_INTERNAL_BUCKET_NAME ?? "";
private AmazonS3Client S3Client { get; }
// When running in a dockerized environment the sync server doesn't have access
@@ -60,7 +61,7 @@ namespace Notesnook.API.Services
{
var config = new AmazonS3Config
{
#if DEBUG
#if (DEBUG || STAGING)
ServiceURL = Servers.S3Server.ToString(),
#else
ServiceURL = Constants.S3_SERVICE_URL,
@@ -70,7 +71,7 @@ namespace Notesnook.API.Services
SignatureMethod = SigningAlgorithm.HmacSHA256,
SignatureVersion = "4"
};
#if DEBUG
#if (DEBUG || STAGING)
S3Client = new AmazonS3Client("S3RVER", "S3RVER", config);
#else
S3Client = new AmazonS3Client(Constants.S3_ACCESS_KEY_ID, Constants.S3_ACCESS_KEY, config);
@@ -96,7 +97,7 @@ namespace Notesnook.API.Services
var objectName = GetFullObjectName(userId, name);
if (objectName == null) throw new Exception("Invalid object name."); ;
var response = await GetS3Client(S3ClientMode.INTERNAL).DeleteObjectAsync(BUCKET_NAME, objectName);
var response = await GetS3Client(S3ClientMode.INTERNAL).DeleteObjectAsync(GetBucketName(S3ClientMode.INTERNAL), objectName);
if (!IsSuccessStatusCode(((int)response.HttpStatusCode)))
throw new Exception("Could not delete object.");
@@ -106,7 +107,7 @@ namespace Notesnook.API.Services
{
var request = new ListObjectsV2Request
{
BucketName = BUCKET_NAME,
BucketName = GetBucketName(S3ClientMode.INTERNAL),
Prefix = userId,
};
@@ -126,10 +127,10 @@ namespace Notesnook.API.Services
if (keys.Count <= 0) return;
var deleteObjectsResponse = await S3Client
var deleteObjectsResponse = await GetS3Client(S3ClientMode.INTERNAL)
.DeleteObjectsAsync(new DeleteObjectsRequest
{
BucketName = BUCKET_NAME,
BucketName = GetBucketName(S3ClientMode.INTERNAL),
Objects = keys,
});
@@ -137,14 +138,20 @@ namespace Notesnook.API.Services
throw new Exception("Could not delete directory.");
}
public async Task<long?> GetObjectSizeAsync(string userId, string name)
public async Task<long> GetObjectSizeAsync(string userId, string name)
{
var url = this.GetPresignedURL(userId, name, HttpVerb.HEAD, S3ClientMode.INTERNAL);
if (url == null) return null;
if (url == null) return 0;
var request = new HttpRequestMessage(HttpMethod.Head, url);
var response = await httpClient.SendAsync(request);
return response.Content.Headers.ContentLength;
const long MAX_SIZE = 513 * 1024 * 1024; // 512 MB
if (!Constants.IS_SELF_HOSTED && response.Content.Headers.ContentLength >= MAX_SIZE)
{
await this.DeleteObjectAsync(userId, name);
throw new Exception("File size exceeds the maximum allowed size.");
}
return response.Content.Headers.ContentLength ?? 0;
}
@@ -169,7 +176,7 @@ namespace Notesnook.API.Services
if (string.IsNullOrEmpty(uploadId))
{
var response = await GetS3Client(S3ClientMode.INTERNAL).InitiateMultipartUploadAsync(BUCKET_NAME, objectName);
var response = await GetS3Client(S3ClientMode.INTERNAL).InitiateMultipartUploadAsync(GetBucketName(S3ClientMode.INTERNAL), objectName);
if (!IsSuccessStatusCode(((int)response.HttpStatusCode))) throw new Exception("Failed to initiate multipart upload.");
uploadId = response.UploadId;
@@ -193,7 +200,7 @@ namespace Notesnook.API.Services
var objectName = GetFullObjectName(userId, name);
if (userId == null || objectName == null) throw new Exception("Could not abort multipart upload.");
var response = await GetS3Client(S3ClientMode.INTERNAL).AbortMultipartUploadAsync(BUCKET_NAME, objectName, uploadId);
var response = await GetS3Client(S3ClientMode.INTERNAL).AbortMultipartUploadAsync(GetBucketName(S3ClientMode.INTERNAL), objectName, uploadId);
if (!IsSuccessStatusCode(((int)response.HttpStatusCode))) throw new Exception("Failed to abort multipart upload.");
}
@@ -203,7 +210,7 @@ namespace Notesnook.API.Services
if (userId == null || objectName == null) throw new Exception("Could not abort multipart upload.");
uploadRequest.Key = objectName;
uploadRequest.BucketName = BUCKET_NAME;
uploadRequest.BucketName = GetBucketName(S3ClientMode.INTERNAL);
var response = await GetS3Client(S3ClientMode.INTERNAL).CompleteMultipartUploadAsync(uploadRequest);
if (!IsSuccessStatusCode(((int)response.HttpStatusCode))) throw new Exception("Failed to complete multipart upload.");
}
@@ -213,36 +220,38 @@ namespace Notesnook.API.Services
var objectName = GetFullObjectName(userId, name);
if (userId == null || objectName == null) return null;
var client = GetS3Client(mode);
var request = new GetPreSignedUrlRequest
{
BucketName = BUCKET_NAME,
BucketName = GetBucketName(mode),
Expires = System.DateTime.Now.AddHours(1),
Verb = httpVerb,
Key = objectName,
#if DEBUG
#if (DEBUG || STAGING)
Protocol = Protocol.HTTP,
#else
Protocol = Constants.IS_SELF_HOSTED ? Protocol.HTTP : Protocol.HTTPS,
Protocol = client.Config.ServiceURL.StartsWith("http://") ? Protocol.HTTP : Protocol.HTTPS,
#endif
};
return GetS3Client(mode).GetPreSignedURL(request);
return client.GetPreSignedURL(request);
}
private string GetPresignedURLForUploadPart(string objectName, string uploadId, int partNumber)
private string GetPresignedURLForUploadPart(string objectName, string uploadId, int partNumber, S3ClientMode mode = S3ClientMode.EXTERNAL)
{
return GetS3Client().GetPreSignedURL(new GetPreSignedUrlRequest
var client = GetS3Client(mode);
return client.GetPreSignedURL(new GetPreSignedUrlRequest
{
BucketName = BUCKET_NAME,
BucketName = GetBucketName(mode),
Expires = System.DateTime.Now.AddHours(1),
Verb = HttpVerb.PUT,
Key = objectName,
PartNumber = partNumber,
UploadId = uploadId,
#if DEBUG
#if (DEBUG || STAGING)
Protocol = Protocol.HTTP,
#else
Protocol = Constants.IS_SELF_HOSTED ? Protocol.HTTP : Protocol.HTTPS,
Protocol = client.Config.ServiceURL.StartsWith("http://") ? Protocol.HTTP : Protocol.HTTPS,
#endif
});
}
@@ -263,5 +272,11 @@ namespace Notesnook.API.Services
if (mode == S3ClientMode.INTERNAL && S3InternalClient != null) return S3InternalClient;
return S3Client;
}
string GetBucketName(S3ClientMode mode = S3ClientMode.EXTERNAL)
{
if (mode == S3ClientMode.INTERNAL && S3InternalClient != null) return INTERNAL_BUCKET_NAME;
return BUCKET_NAME;
}
}
}

View File

@@ -0,0 +1,233 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Notesnook.API.Services
{
public struct SyncDevice(string userId, string deviceId)
{
public readonly string DeviceId => deviceId;
public readonly string UserId => userId;
public string UserSyncDirectoryPath = CreateFilePath(userId);
public string UserDeviceDirectoryPath = CreateFilePath(userId, deviceId);
public string PendingIdsFilePath = CreateFilePath(userId, deviceId, "pending");
public string UnsyncedIdsFilePath = CreateFilePath(userId, deviceId, "unsynced");
public string ResetSyncFilePath = CreateFilePath(userId, deviceId, "reset-sync");
public readonly long LastAccessTime
{
get => long.Parse(GetMetadata("LastAccessTime") ?? "0");
set => SetMetadata("LastAccessTime", value.ToString());
}
private static string CreateFilePath(string userId, string? deviceId = null, string? metadataKey = null)
{
return Path.Join("sync", userId, deviceId, metadataKey);
}
private readonly string? GetMetadata(string metadataKey)
{
var path = CreateFilePath(userId, deviceId, metadataKey);
if (!File.Exists(path)) return null;
return File.ReadAllText(path);
}
private readonly void SetMetadata(string metadataKey, string value)
{
try
{
var path = CreateFilePath(userId, deviceId, metadataKey);
File.WriteAllText(path, value);
}
catch (DirectoryNotFoundException) { }
}
}
public class SyncDeviceService(SyncDevice device)
{
public string[] GetUnsyncedIds()
{
try
{
return File.ReadAllLines(device.UnsyncedIdsFilePath);
}
catch { return []; }
}
public string[] GetUnsyncedIds(string deviceId)
{
try
{
return File.ReadAllLines(Path.Join(device.UserSyncDirectoryPath, deviceId, "unsynced"));
}
catch { return []; }
}
public string[] FetchUnsyncedIds()
{
if (IsSyncReset()) return [];
try
{
var unsyncedIds = GetUnsyncedIds();
lock (device.DeviceId)
{
if (IsSyncPending())
{
unsyncedIds = unsyncedIds.Union(File.ReadAllLines(device.PendingIdsFilePath)).ToArray();
}
if (unsyncedIds.Length == 0) return [];
File.Delete(device.UnsyncedIdsFilePath);
File.WriteAllLines(device.PendingIdsFilePath, unsyncedIds);
}
return unsyncedIds;
}
catch
{
return [];
}
}
public void WritePendingIds(IEnumerable<string> ids)
{
lock (device.DeviceId)
{
File.WriteAllLines(device.PendingIdsFilePath, ids);
}
}
public bool IsSyncReset()
{
return File.Exists(device.ResetSyncFilePath);
}
public bool IsSyncReset(string deviceId)
{
return File.Exists(Path.Join(device.UserSyncDirectoryPath, deviceId, "reset-sync"));
}
public bool IsSyncPending()
{
return File.Exists(device.PendingIdsFilePath);
}
public bool IsUnsynced()
{
return File.Exists(device.UnsyncedIdsFilePath);
}
public void Reset()
{
try
{
lock (device.UserId)
{
File.Delete(device.ResetSyncFilePath);
File.Delete(device.PendingIdsFilePath);
}
}
catch (FileNotFoundException) { }
catch (DirectoryNotFoundException) { }
}
public bool IsDeviceRegistered()
{
return Directory.Exists(device.UserDeviceDirectoryPath);
}
public bool IsDeviceRegistered(string deviceId)
{
return Directory.Exists(Path.Join(device.UserSyncDirectoryPath, deviceId));
}
public string[] ListDevices()
{
return Directory.GetDirectories(device.UserSyncDirectoryPath).Select((path) => path[(path.LastIndexOf(Path.DirectorySeparatorChar) + 1)..]).ToArray();
}
public void ResetDevices()
{
lock (device.UserId)
{
if (File.Exists(device.UserSyncDirectoryPath)) File.Delete(device.UserSyncDirectoryPath);
Directory.CreateDirectory(device.UserSyncDirectoryPath);
}
}
public void AddIdsToOtherDevices(List<string> ids)
{
device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
foreach (string id in ListDevices())
{
if (id == device.DeviceId || IsSyncReset(id)) continue;
lock (id)
{
if (!IsDeviceRegistered(id)) Directory.CreateDirectory(Path.Join(device.UserSyncDirectoryPath, id));
var oldIds = GetUnsyncedIds(id);
File.WriteAllLines(Path.Join(device.UserSyncDirectoryPath, id, "unsynced"), ids.Union(oldIds));
}
}
}
public void AddIdsToAllDevices(List<string> ids)
{
foreach (var id in ListDevices())
{
if (IsSyncReset(id)) return;
lock (id)
{
if (!IsDeviceRegistered(id)) Directory.CreateDirectory(Path.Join(device.UserSyncDirectoryPath, id));
var oldIds = GetUnsyncedIds(id);
File.WriteAllLinesAsync(Path.Join(device.UserSyncDirectoryPath, id, "unsynced"), ids.Union(oldIds));
}
}
}
public void RegisterDevice()
{
lock (device.UserId)
{
if (Directory.Exists(device.UserDeviceDirectoryPath))
Directory.Delete(device.UserDeviceDirectoryPath, true);
Directory.CreateDirectory(device.UserDeviceDirectoryPath);
File.Create(device.ResetSyncFilePath).Close();
device.LastAccessTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
}
public void UnregisterDevice()
{
lock (device.UserId)
{
if (!Path.Exists(device.UserDeviceDirectoryPath)) return;
Directory.Delete(device.UserDeviceDirectoryPath, true);
}
}
}
}

View File

@@ -18,7 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -63,7 +65,8 @@ namespace Notesnook.API.Services
if (!response.Success || (response.Errors != null && response.Errors.Length > 0))
{
await Slogger<UserService>.Error(nameof(CreateUserAsync), "Couldn't sign up.", JsonSerializer.Serialize(response));
if (response.Errors != null && response.Errors.Length > 0) throw new Exception(string.Join(" ", response.Errors));
if (response.Errors != null && response.Errors.Length > 0)
throw new Exception(string.Join(" ", response.Errors));
else throw new Exception("Could not create a new account. Error code: " + response.StatusCode);
}
@@ -76,7 +79,7 @@ namespace Notesnook.API.Services
if (!Constants.IS_SELF_HOSTED)
{
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.CreateSubscriptionTopic, new CreateSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
@@ -89,10 +92,11 @@ namespace Notesnook.API.Services
await Slogger<UserService>.Info(nameof(CreateUserAsync), "New user created.", JsonSerializer.Serialize(response));
}
public async Task<UserResponse> GetUserAsync(bool repair = true)
public async Task<UserResponse> GetUserAsync(string userId)
{
UserResponse response = await httpClient.ForwardAsync<UserResponse>(this.HttpContextAccessor, $"{Servers.IdentityServer.ToString()}/account", HttpMethod.Get);
if (!response.Success) return response;
var userService = await WampServers.IdentityServer.GetServiceAsync<IUserAccountService>(IdentityServerTopics.UserAccountServiceTopic);
var user = await userService.GetUserAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User not found.");
ISubscription subscription = null;
if (Constants.IS_SELF_HOSTED)
@@ -102,7 +106,7 @@ namespace Notesnook.API.Services
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.PREMIUM,
UserId = response.UserId,
UserId = user.UserId,
StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
// this date doesn't matter as the subscription is static.
ExpiryDate = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeMilliseconds()
@@ -110,61 +114,48 @@ namespace Notesnook.API.Services
}
else
{
SubscriptionResponse subscriptionResponse = await httpClient.ForwardAsync<SubscriptionResponse>(this.HttpContextAccessor, $"{Servers.SubscriptionServer}/subscriptions", HttpMethod.Get);
if (repair && subscriptionResponse.StatusCode == 404)
{
await Slogger<UserService>.Error(nameof(GetUserAsync), "Repairing user subscription.", JsonSerializer.Serialize(response));
// user was partially created. We should continue the process here.
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.CreateSubscriptionTopic, new CreateSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
UserId = response.UserId,
StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
});
// just a dummy object
subscriptionResponse.Subscription = new Subscription
{
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.TRIAL,
UserId = response.UserId,
StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
ExpiryDate = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
};
}
subscription = subscriptionResponse.Subscription;
var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync<IUserSubscriptionService>(SubscriptionServerTopics.UserSubscriptionServiceTopic);
subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId);
}
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == response.UserId);
if (repair && userSettings == null)
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == user.UserId) ?? throw new Exception("User settings not found.");
return new UserResponse
{
await Slogger<UserService>.Error(nameof(GetUserAsync), "Repairing user settings.", JsonSerializer.Serialize(response));
userSettings = new UserSettings
{
UserId = response.UserId,
LastSynced = 0,
Salt = GetSalt()
};
await Repositories.UsersSettings.InsertAsync(userSettings);
}
response.AttachmentsKey = userSettings.AttachmentsKey;
response.Salt = userSettings.Salt;
response.Subscription = subscription;
return response;
UserId = user.UserId,
Email = user.Email,
IsEmailConfirmed = user.IsEmailConfirmed,
MarketingConsent = user.MarketingConsent,
MFA = user.MFA,
PhoneNumber = user.PhoneNumber,
AttachmentsKey = userSettings.AttachmentsKey,
MonographPasswordsKey = userSettings.MonographPasswordsKey,
Salt = userSettings.Salt,
Subscription = subscription,
Success = true,
StatusCode = 200
};
}
public async Task SetUserAttachmentsKeyAsync(string userId, IEncrypted key)
public async Task SetUserKeysAsync(string userId, UserKeys keys)
{
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
userSettings.AttachmentsKey = (EncryptedData)key;
var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId) ?? throw new Exception("User not found.");
if (keys.AttachmentsKey != null)
{
userSettings.AttachmentsKey = keys.AttachmentsKey;
}
if (keys.MonographPasswordsKey != null)
{
userSettings.MonographPasswordsKey = keys.MonographPasswordsKey;
}
await Repositories.UsersSettings.UpdateAsync(userSettings.Id, userSettings);
}
public async Task<bool> DeleteUserAsync(string userId, string jti)
public async Task DeleteUserAsync(string userId)
{
new SyncDeviceService(new SyncDevice(userId, userId)).ResetDevices();
var cc = new CancellationTokenSource();
Repositories.Notes.DeleteByUserId(userId);
@@ -172,40 +163,59 @@ namespace Notesnook.API.Services
Repositories.Shortcuts.DeleteByUserId(userId);
Repositories.Contents.DeleteByUserId(userId);
Repositories.Settings.DeleteByUserId(userId);
Repositories.LegacySettings.DeleteByUserId(userId);
Repositories.Attachments.DeleteByUserId(userId);
Repositories.Reminders.DeleteByUserId(userId);
Repositories.Relations.DeleteByUserId(userId);
Repositories.Colors.DeleteByUserId(userId);
Repositories.Tags.DeleteByUserId(userId);
Repositories.Vaults.DeleteByUserId(userId);
Repositories.UsersSettings.Delete((u) => u.UserId == userId);
Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
var result = await unit.Commit();
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "User data deleted", userId, result.ToString());
if (!result) throw new Exception("Could not delete user data.");
if (!Constants.IS_SELF_HOSTED)
{
await WampServers.SubscriptionServer.PublishMessageAsync(WampServers.SubscriptionServer.Topics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
AppId = ApplicationType.NOTESNOOK,
UserId = userId
});
}
await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
await S3Service.DeleteDirectoryAsync(userId);
}
public async Task DeleteUserAsync(string userId, string jti, string password)
{
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
var userService = await WampServers.IdentityServer.GetServiceAsync<IUserAccountService>(IdentityServerTopics.UserAccountServiceTopic);
await userService.DeleteUserAsync(Clients.Notesnook.Id, userId, password);
await DeleteUserAsync(userId);
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
SendToAll = false,
OriginTokenId = jti,
UserId = userId,
Message = new Message
{
Type = "userDeleted",
Data = JsonSerializer.Serialize(new { reason = "accountDeleted" })
Type = "logout",
Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
}
});
await S3Service.DeleteDirectoryAsync(userId);
return await unit.Commit();
}
public async Task<bool> ResetUserAsync(string userId, bool removeAttachments)
{
new SyncDeviceService(new SyncDevice(userId, userId)).ResetDevices();
var cc = new CancellationTokenSource();
Repositories.Notes.DeleteByUserId(userId);
@@ -213,15 +223,20 @@ namespace Notesnook.API.Services
Repositories.Shortcuts.DeleteByUserId(userId);
Repositories.Contents.DeleteByUserId(userId);
Repositories.Settings.DeleteByUserId(userId);
Repositories.LegacySettings.DeleteByUserId(userId);
Repositories.Attachments.DeleteByUserId(userId);
Repositories.Reminders.DeleteByUserId(userId);
Repositories.Relations.DeleteByUserId(userId);
Repositories.Colors.DeleteByUserId(userId);
Repositories.Tags.DeleteByUserId(userId);
Repositories.Vaults.DeleteByUserId(userId);
Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
if (!await unit.Commit()) return false;
var userSettings = await Repositories.UsersSettings.FindOneAsync((s) => s.UserId == userId);
userSettings.AttachmentsKey = null;
userSettings.MonographPasswordsKey = null;
userSettings.VaultKey = null;
userSettings.LastSynced = 0;
@@ -233,7 +248,7 @@ namespace Notesnook.API.Services
return true;
}
private string GetSalt()
private static string GetSalt()
{
byte[] salt = new byte[16];
Rng.GetNonZeroBytes(salt);

View File

@@ -34,6 +34,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -50,6 +51,8 @@ using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Repositories;
using Notesnook.API.Services;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using Streetwriters.Common;
using Streetwriters.Common.Extensions;
using Streetwriters.Common.Messages;
@@ -73,12 +76,11 @@ namespace Notesnook.API
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var dbSettings = new DbSettings
services.AddSingleton(MongoDbContext.CreateMongoDbClient(new DbSettings
{
ConnectionString = Constants.MONGODB_CONNECTION_STRING,
DatabaseName = Constants.MONGODB_DATABASE_NAME
};
services.AddSingleton<IDbSettings>(dbSettings);
}));
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@@ -106,23 +108,13 @@ namespace Notesnook.API
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new SyncRequirement());
});
options.AddPolicy("Verified", policy =>
{
policy.AuthenticationSchemes.Add("introspection");
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new EmailVerifiedRequirement());
});
options.AddPolicy("Pro", policy =>
{
policy.AuthenticationSchemes.Add("introspection");
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new SyncRequirement());
policy.Requirements.Add(new ProUserRequirement());
});
options.AddPolicy("BasicAdmin", policy =>
{
policy.AuthenticationSchemes.Add("BasicAuthentication");
policy.RequireClaim(ClaimTypes.Role, "Admin");
});
options.DefaultPolicy = options.GetPolicy("Notesnook");
}).AddSingleton<IAuthorizationMiddlewareResultHandler, AuthorizationResultTransformer>(); ;
@@ -152,48 +144,55 @@ namespace Notesnook.API
context.HttpContext.User = context.Principal;
return Task.CompletedTask;
};
options.CacheKeyGenerator = (options, token) => (token + ":" + "reference_token").Sha256();
options.SaveToken = true;
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(30);
});
// Serializer.RegisterSerializer(new SyncItemBsonSerializer());
if (!BsonClassMap.IsClassMapRegistered(typeof(UserSettings)))
{
BsonClassMap.RegisterClassMap<UserSettings>();
}
if (!BsonClassMap.IsClassMapRegistered(typeof(EncryptedData)))
{
BsonClassMap.RegisterClassMap<EncryptedData>();
}
if (!BsonClassMap.IsClassMapRegistered(typeof(CallToAction)))
{
BsonClassMap.RegisterClassMap<CallToAction>();
}
if (!BsonClassMap.IsClassMapRegistered(typeof(Announcement)))
{
BsonClassMap.RegisterClassMap<Announcement>();
}
services.AddScoped<IDbContext, MongoDbContext>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped(typeof(Repository<>));
services.AddScoped(typeof(SyncItemsRepository<>));
services.TryAddTransient<ISyncItemsRepositoryAccessor, SyncItemsRepositoryAccessor>();
services.TryAddTransient<IUserService, UserService>();
services.TryAddTransient<IS3Service, S3Service>();
services.AddRepository<UserSettings>("user_settings", "notesnook")
.AddRepository<Monograph>("monographs", "notesnook")
.AddRepository<Announcement>("announcements", "notesnook");
services.AddMongoCollection(Collections.SettingsKey)
.AddMongoCollection(Collections.AttachmentsKey)
.AddMongoCollection(Collections.ContentKey)
.AddMongoCollection(Collections.NotesKey)
.AddMongoCollection(Collections.NotebooksKey)
.AddMongoCollection(Collections.RelationsKey)
.AddMongoCollection(Collections.RemindersKey)
.AddMongoCollection(Collections.LegacySettingsKey)
.AddMongoCollection(Collections.ShortcutsKey)
.AddMongoCollection(Collections.TagsKey)
.AddMongoCollection(Collections.ColorsKey)
.AddMongoCollection(Collections.VaultsKey);
services.AddScoped<ISyncItemsRepositoryAccessor, SyncItemsRepositoryAccessor>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IS3Service, S3Service>();
services.AddControllers();
services.AddHealthChecks().AddMongoDb(dbSettings.ConnectionString, dbSettings.DatabaseName, "database-check");
services.AddHealthChecks(); // .AddMongoDb(dbSettings.ConnectionString, dbSettings.DatabaseName, "database-check");
services.AddSignalR((hub) =>
{
hub.MaximumReceiveMessageSize = 100 * 1024 * 1024;
hub.ClientTimeoutInterval = TimeSpan.FromMinutes(10);
hub.EnableDetailedErrors = true;
}).AddMessagePackProtocol();
}).AddMessagePackProtocol().AddJsonProtocol();
services.AddResponseCompression(options =>
{
@@ -210,6 +209,13 @@ namespace Notesnook.API
{
options.Level = CompressionLevel.Fastest;
});
services.AddOpenTelemetry()
.ConfigureResource(resource => resource
.AddService(serviceName: "Notesnook.API"))
.WithMetrics((builder) => builder
.AddMeter("Notesnook.API.Metrics.Sync")
.AddPrometheusExporter());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -223,17 +229,24 @@ namespace Notesnook.API
});
}
app.UseOpenTelemetryPrometheusScrapingEndpoint((context) => context.Request.Path == "/metrics" && context.Connection.LocalPort == 5067);
app.UseResponseCompression();
app.UseCors("notesnook");
app.UseVersion();
app.UseVersion(Servers.NotesnookAPI);
app.UseWamp(WampServers.NotesnookServer, (realm, server) =>
{
IUserService service = app.GetScopedService<IUserService>();
realm.Subscribe<DeleteUserMessage>(server.Topics.DeleteUserTopic, async (ev) =>
realm.Subscribe<DeleteUserMessage>(IdentityServerTopics.DeleteUserTopic, async (ev) =>
{
await service.DeleteUserAsync(ev.UserId, null);
IUserService service = app.GetScopedService<IUserService>();
await service.DeleteUserAsync(ev.UserId);
});
realm.Subscribe<ClearCacheMessage>(IdentityServerTopics.ClearCacheTopic, (ev) =>
{
IDistributedCache cache = app.GetScopedService<IDistributedCache>();
ev.Keys.ForEach((key) => cache.Remove(key));
});
});
@@ -244,6 +257,7 @@ namespace Notesnook.API
app.UseEndpoints(endpoints =>
{
endpoints.MapPrometheusScrapingEndpoint();
endpoints.MapControllers();
endpoints.MapHealthChecks("/health");
endpoints.MapHub<SyncHub>("/hubs/sync", options =>
@@ -251,7 +265,21 @@ namespace Notesnook.API
options.CloseOnAuthenticationExpiration = false;
options.Transports = HttpTransportType.WebSockets;
});
endpoints.MapHub<SyncV2Hub>("/hubs/sync/v2", options =>
{
options.CloseOnAuthenticationExpiration = false;
options.Transports = HttpTransportType.WebSockets;
});
});
}
}
public static class ServiceCollectionMongoCollectionExtensions
{
public static IServiceCollection AddMongoCollection(this IServiceCollection services, string collectionName, string database = "notesnook")
{
services.AddKeyedSingleton(collectionName, (provider, key) => MongoDbContext.GetMongoCollection<SyncItem>(provider.GetService<MongoDB.Driver.IMongoClient>(), database, collectionName));
return services;
}
}
}

View File

@@ -1,13 +1,15 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"MongoDbSettings": {
"ConnectionString": "mongodb://localhost:27017/notesnook",
"DatabaseName": "notesnook"
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore.SignalR": "Trace",
"Microsoft.AspNetCore.Http.Connections": "Trace"
}
},
"MongoDbSettings": {
"ConnectionString": "mongodb://localhost:27017/notesnook",
"DatabaseName": "notesnook"
}
}

View File

@@ -0,0 +1,7 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}

View File

@@ -8,7 +8,7 @@ This repo contains the full source code of the Notesnook Sync Server licensed un
Requirements:
1. [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)
1. [.NET 8](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
2. [git](https://git-scm.com/downloads)
The first step is to `clone` the repository:
@@ -55,35 +55,30 @@ dotnet run --project Streetwriters.Identity/Streetwriters.Identity.csproj
The sync server can easily be started using Docker.
The first step is to `clone` the repository:
```bash
git clone https://github.com/streetwriters/notesnook-sync-server.git
# change directory
cd notesnook-sync-server
wget https://raw.githubusercontent.com/streetwriters/notesnook-sync-server/master/docker-compose.yml
```
And then use Docker Compose to start the servers:
```bash
docker-compose up
docker compose up
```
This takes care of setting up everything including MongoDB, Minio etc.
## TODO Self-hosting
**Note: Self-hosting the Notesnook Sync Server is not yet possible. We are working to enable full on-premise self hosting so stay tuned!**
**Note: Self-hosting the Notesnook Sync Server is now possible, but without support. Documentation will be provided at a later date. We are working to enable full on-premise self-hosting, so stay tuned!**
- [x] Open source the Sync server
- [x] Open source the Identity server
- [x] Open source the SSE Messaging infrastructure
- [x] Fully Dockerize all services
- [x] Use self-hosted Minio for S3 storage
- [ ] Publish on DockerHub
- [x] Publish on DockerHub
- [x] Add settings to change server URLs in Notesnook client apps (starting from v3.0.18)
- [ ] Write self hosting docs
- [ ] Add settings to change server URLs in Notesnook client apps
## License

View File

@@ -1,33 +1,33 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Attributes
{
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Attributes
{
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
}

View File

@@ -1,82 +1,82 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
namespace Streetwriters.Common
{
public class Clients
{
private static Client Notesnook = new Client
{
Id = "notesnook",
Name = "Notesnook",
SenderEmail = Constants.NOTESNOOK_SENDER_EMAIL,
SenderName = Constants.NOTESNOOK_SENDER_NAME,
Type = ApplicationType.NOTESNOOK,
AppId = ApplicationType.NOTESNOOK,
AccountRecoveryRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/recovery",
EmailConfirmedRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/verified",
OnEmailConfirmed = async (userId) =>
{
await WampServers.MessengerServer.PublishMessageAsync(WampServers.MessengerServer.Topics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
Message = new Message
{
Type = "emailConfirmed",
Data = null
}
});
}
};
public static Dictionary<string, Client> ClientsMap = new Dictionary<string, Client>
{
{ "notesnook", Notesnook }
};
public static Client FindClientById(string id)
{
if (!IsValidClient(id)) return null;
return ClientsMap[id];
}
public static Client FindClientByAppId(ApplicationType appId)
{
switch (appId)
{
case ApplicationType.NOTESNOOK:
return ClientsMap["notesnook"];
}
return null;
}
public static bool IsValidClient(string id)
{
return ClientsMap.ContainsKey(id);
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
namespace Streetwriters.Common
{
public class Clients
{
public static readonly Client Notesnook = new()
{
Id = "notesnook",
Name = "Notesnook",
SenderEmail = Constants.NOTESNOOK_SENDER_EMAIL,
SenderName = "Notesnook",
Type = ApplicationType.NOTESNOOK,
AppId = ApplicationType.NOTESNOOK,
AccountRecoveryRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/recovery",
EmailConfirmedRedirectURL = $"{Constants.NOTESNOOK_APP_HOST}/account/verified",
OnEmailConfirmed = async (userId) =>
{
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
UserId = userId,
Message = new Message
{
Type = "emailConfirmed",
Data = null
}
});
}
};
public static Dictionary<string, Client> ClientsMap = new()
{
{ "notesnook", Notesnook }
};
public static Client FindClientById(string id)
{
if (!IsValidClient(id)) return null;
return ClientsMap[id];
}
public static Client FindClientByAppId(ApplicationType appId)
{
switch (appId)
{
case ApplicationType.NOTESNOOK:
return ClientsMap["notesnook"];
}
return null;
}
public static bool IsValidClient(string id)
{
return ClientsMap.ContainsKey(id);
}
}
}

View File

@@ -1,80 +1,81 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace Streetwriters.Common
{
public class Constants
{
public static bool IS_SELF_HOSTED => Environment.GetEnvironmentVariable("SELF_HOSTED") == "1";
// S3 related
public static string S3_ACCESS_KEY => Environment.GetEnvironmentVariable("S3_ACCESS_KEY");
public static string S3_ACCESS_KEY_ID => Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID");
public static string S3_SERVICE_URL => Environment.GetEnvironmentVariable("S3_SERVICE_URL");
public static string S3_REGION => Environment.GetEnvironmentVariable("S3_REGION");
// SMTP settings
public static string SMTP_USERNAME => Environment.GetEnvironmentVariable("SMTP_USERNAME");
public static string SMTP_PASSWORD => Environment.GetEnvironmentVariable("SMTP_PASSWORD");
public static string SMTP_HOST => Environment.GetEnvironmentVariable("SMTP_HOST");
public static string SMTP_PORT => Environment.GetEnvironmentVariable("SMTP_PORT");
public static string SMTP_REPLYTO_NAME => Environment.GetEnvironmentVariable("SMTP_REPLYTO_NAME");
public static string SMTP_REPLYTO_EMAIL => Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL");
public static string NOTESNOOK_SENDER_EMAIL => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL");
public static string NOTESNOOK_SENDER_NAME => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_NAME");
public static string NOTESNOOK_APP_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_APP_HOST");
public static string NOTESNOOK_API_SECRET => Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET");
// MessageBird is used for SMS sending
public static string MESSAGEBIRD_ACCESS_KEY => Environment.GetEnvironmentVariable("MESSAGEBIRD_ACCESS_KEY");
// Server discovery
public static int NOTESNOOK_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_PORT"));
public static string NOTESNOOK_SERVER_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_HOST");
public static string NOTESNOOK_SERVER_DOMAIN => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_DOMAIN");
public static string NOTESNOOK_CERT_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_PATH");
public static string NOTESNOOK_CERT_KEY_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_KEY_PATH");
public static int IDENTITY_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("IDENTITY_SERVER_PORT"));
public static string IDENTITY_SERVER_HOST => Environment.GetEnvironmentVariable("IDENTITY_SERVER_HOST");
public static string IDENTITY_SERVER_DOMAIN => Environment.GetEnvironmentVariable("IDENTITY_SERVER_DOMAIN");
public static string IDENTITY_CERT_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_PATH");
public static string IDENTITY_CERT_KEY_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_KEY_PATH");
public static int SSE_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SSE_SERVER_PORT"));
public static string SSE_SERVER_HOST => Environment.GetEnvironmentVariable("SSE_SERVER_HOST");
public static string SSE_SERVER_DOMAIN => Environment.GetEnvironmentVariable("SSE_SERVER_DOMAIN");
public static string SSE_CERT_PATH => Environment.GetEnvironmentVariable("SSE_CERT_PATH");
public static string SSE_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SSE_CERT_KEY_PATH");
// internal
public static string MONGODB_CONNECTION_STRING => Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING");
public static string MONGODB_DATABASE_NAME => Environment.GetEnvironmentVariable("MONGODB_DATABASE_NAME");
public static string S3_INTERNAL_SERVICE_URL => Environment.GetEnvironmentVariable("S3_INTERNAL_SERVICE_URL");
public static int SUBSCRIPTIONS_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_PORT"));
public static string SUBSCRIPTIONS_SERVER_HOST => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_HOST");
public static string SUBSCRIPTIONS_SERVER_DOMAIN => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_DOMAIN");
public static string SUBSCRIPTIONS_CERT_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_PATH");
public static string SUBSCRIPTIONS_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_KEY_PATH");
public static string[] NOTESNOOK_CORS_ORIGINS => Environment.GetEnvironmentVariable("NOTESNOOK_CORS")?.Split(",") ?? new string[] { };
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace Streetwriters.Common
{
public class Constants
{
public static int COMPATIBILITY_VERSION = 1;
public static bool IS_SELF_HOSTED => Environment.GetEnvironmentVariable("SELF_HOSTED") == "1";
public static bool DISABLE_SIGNUPS => Environment.GetEnvironmentVariable("DISABLE_SIGNUPS") == "true";
public static string INSTANCE_NAME => Environment.GetEnvironmentVariable("INSTANCE_NAME") ?? "default";
// S3 related
public static string S3_ACCESS_KEY => Environment.GetEnvironmentVariable("S3_ACCESS_KEY");
public static string S3_ACCESS_KEY_ID => Environment.GetEnvironmentVariable("S3_ACCESS_KEY_ID");
public static string S3_SERVICE_URL => Environment.GetEnvironmentVariable("S3_SERVICE_URL");
public static string S3_REGION => Environment.GetEnvironmentVariable("S3_REGION");
public static string S3_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_BUCKET_NAME");
public static string S3_INTERNAL_BUCKET_NAME => Environment.GetEnvironmentVariable("S3_INTERNAL_BUCKET_NAME");
public static string S3_INTERNAL_SERVICE_URL => Environment.GetEnvironmentVariable("S3_INTERNAL_SERVICE_URL");
// SMTP settings
public static string SMTP_USERNAME => Environment.GetEnvironmentVariable("SMTP_USERNAME");
public static string SMTP_PASSWORD => Environment.GetEnvironmentVariable("SMTP_PASSWORD");
public static string SMTP_HOST => Environment.GetEnvironmentVariable("SMTP_HOST");
public static string SMTP_PORT => Environment.GetEnvironmentVariable("SMTP_PORT");
public static string SMTP_REPLYTO_EMAIL => Environment.GetEnvironmentVariable("SMTP_REPLYTO_EMAIL");
public static string NOTESNOOK_SENDER_EMAIL => Environment.GetEnvironmentVariable("NOTESNOOK_SENDER_EMAIL") ?? Environment.GetEnvironmentVariable("SMTP_USERNAME");
public static string NOTESNOOK_APP_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_APP_HOST");
public static string NOTESNOOK_API_SECRET => Environment.GetEnvironmentVariable("NOTESNOOK_API_SECRET");
// MessageBird is used for SMS sending
public static string TWILIO_ACCOUNT_SID => Environment.GetEnvironmentVariable("TWILIO_ACCOUNT_SID");
public static string TWILIO_AUTH_TOKEN => Environment.GetEnvironmentVariable("TWILIO_AUTH_TOKEN");
public static string TWILIO_SERVICE_SID => Environment.GetEnvironmentVariable("TWILIO_SERVICE_SID");
// Server discovery
public static int NOTESNOOK_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_PORT") ?? "80");
public static string NOTESNOOK_SERVER_HOST => Environment.GetEnvironmentVariable("NOTESNOOK_SERVER_HOST");
public static string NOTESNOOK_CERT_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_PATH");
public static string NOTESNOOK_CERT_KEY_PATH => Environment.GetEnvironmentVariable("NOTESNOOK_CERT_KEY_PATH");
public static int IDENTITY_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("IDENTITY_SERVER_PORT") ?? "80");
public static string IDENTITY_SERVER_HOST => Environment.GetEnvironmentVariable("IDENTITY_SERVER_HOST");
public static Uri IDENTITY_SERVER_URL => new(Environment.GetEnvironmentVariable("IDENTITY_SERVER_URL"));
public static string IDENTITY_CERT_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_PATH");
public static string IDENTITY_CERT_KEY_PATH => Environment.GetEnvironmentVariable("IDENTITY_CERT_KEY_PATH");
public static int SSE_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SSE_SERVER_PORT") ?? "80");
public static string SSE_SERVER_HOST => Environment.GetEnvironmentVariable("SSE_SERVER_HOST");
public static string SSE_CERT_PATH => Environment.GetEnvironmentVariable("SSE_CERT_PATH");
public static string SSE_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SSE_CERT_KEY_PATH");
// internal
public static string MONGODB_CONNECTION_STRING => Environment.GetEnvironmentVariable("MONGODB_CONNECTION_STRING");
public static string MONGODB_DATABASE_NAME => Environment.GetEnvironmentVariable("MONGODB_DATABASE_NAME");
public static int SUBSCRIPTIONS_SERVER_PORT => int.Parse(Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_PORT") ?? "80");
public static string SUBSCRIPTIONS_SERVER_HOST => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_SERVER_HOST");
public static string SUBSCRIPTIONS_CERT_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_PATH");
public static string SUBSCRIPTIONS_CERT_KEY_PATH => Environment.GetEnvironmentVariable("SUBSCRIPTIONS_CERT_KEY_PATH");
public static string[] NOTESNOOK_CORS_ORIGINS => Environment.GetEnvironmentVariable("NOTESNOOK_CORS")?.Split(",") ?? new string[] { };
}
}

View File

@@ -1,54 +1,54 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Converters
{
/// <summary>
/// Converts simple interface into an object (assumes that there is only one class of TInterface)
/// </summary>
/// <typeparam name="TInterface">Interface type</typeparam>
/// <typeparam name="TClass">Class type</typeparam>
public class InterfaceConverter<TInterface, TClass> : JsonConverter<TInterface> where TClass : TInterface
{
public override TInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<TClass>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, null, options);
break;
default:
{
var type = value.GetType();
JsonSerializer.Serialize(writer, value, type, options);
break;
}
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Converters
{
/// <summary>
/// Converts simple interface into an object (assumes that there is only one class of TInterface)
/// </summary>
/// <typeparam name="TInterface">Interface type</typeparam>
/// <typeparam name="TClass">Class type</typeparam>
public class InterfaceConverter<TInterface, TClass> : JsonConverter<TInterface> where TClass : TInterface
{
public override TInterface Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return JsonSerializer.Deserialize<TClass>(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, null, options);
break;
default:
{
var type = value.GetType();
JsonSerializer.Serialize(writer, value, type, options);
break;
}
}
}
}
}

View File

@@ -1,26 +1,26 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum ApplicationType
{
NOTESNOOK = 0
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum ApplicationType
{
NOTESNOOK = 0
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public class MFAMethods
{
public static string Email => "email";
public static string SMS => "sms";
public static string App => "app";
public static string RecoveryCode => "recoveryCode";
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public class MFAMethods
{
public static string Email => "email";
public static string SMS => "sms";
public static string App => "app";
public static string RecoveryCode => "recoveryCode";
}
}

View File

@@ -1,29 +1,30 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum SubscriptionProvider
{
STREETWRITERS = 0,
APPLE = 1,
GOOGLE = 2,
PADDLE = 3
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum SubscriptionProvider
{
STREETWRITERS = 0,
APPLE = 1,
GOOGLE = 2,
PADDLE = 3,
GIFT_CARD = 4,
}
}

View File

@@ -1,31 +1,32 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum SubscriptionType
{
BASIC = 0,
TRIAL = 1,
BETA = 2,
PREMIUM = 5,
PREMIUM_EXPIRED = 6,
PREMIUM_CANCELED = 7
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Enums
{
public enum SubscriptionType
{
BASIC = 0,
TRIAL = 1,
BETA = 2,
PREMIUM = 5,
PREMIUM_EXPIRED = 6,
PREMIUM_CANCELED = 7,
PREMIUM_PAUSED = 8
}
}

View File

@@ -1,77 +1,86 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using WampSharp.AspNetCore.WebSockets.Server;
using WampSharp.Binding;
using WampSharp.V2;
using WampSharp.V2.Realm;
namespace Streetwriters.Common.Extensions
{
public static class AppBuilderExtensions
{
public static IApplicationBuilder UseVersion(this IApplicationBuilder app)
{
app.Map("/version", (app) =>
{
app.Run(async context =>
{
await context.Response.WriteAsync(Version.AsString());
});
});
return app;
}
public static IApplicationBuilder UseWamp<T>(this IApplicationBuilder app, WampServer<T> server, Action<IWampHostedRealm, WampServer<T>> action) where T : new()
{
WampHost host = new WampHost();
app.Map(server.Endpoint, builder =>
{
builder.UseWebSockets();
host.RegisterTransport(new AspNetCoreWebSocketTransport(builder),
new JTokenJsonBinding(),
new JTokenMsgpackBinding());
});
host.Open();
action.Invoke(host.RealmContainer.GetRealmByName(server.Realm), server);
return app;
}
public static T GetService<T>(this IApplicationBuilder app)
{
return app.ApplicationServices.GetRequiredService<T>();
}
public static T GetScopedService<T>(this IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.CreateScope())
{
return scope.ServiceProvider.GetRequiredService<T>();
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using WampSharp.AspNetCore.WebSockets.Server;
using WampSharp.Binding;
using WampSharp.V2;
using WampSharp.V2.Realm;
namespace Streetwriters.Common.Extensions
{
public static class AppBuilderExtensions
{
public static IApplicationBuilder UseVersion(this IApplicationBuilder app, Server server)
{
app.Map("/version", (app) =>
{
app.Run(async context =>
{
context.Response.ContentType = "application/json";
var data = new Dictionary<string, object>
{
{ "version", Constants.COMPATIBILITY_VERSION },
{ "id", server.Id },
{ "instance", Constants.INSTANCE_NAME }
};
await context.Response.WriteAsync(JsonSerializer.Serialize(data));
});
});
return app;
}
public static IApplicationBuilder UseWamp<T>(this IApplicationBuilder app, WampServer<T> server, Action<IWampHostedRealm, WampServer<T>> action) where T : new()
{
WampHost host = new WampHost();
app.Map(server.Endpoint, builder =>
{
builder.UseWebSockets();
host.RegisterTransport(new AspNetCoreWebSocketTransport(builder),
new JTokenJsonBinding(),
new JTokenMsgpackBinding());
});
host.Open();
action.Invoke(host.RealmContainer.GetRealmByName(server.Realm), server);
return app;
}
public static T GetService<T>(this IApplicationBuilder app)
{
return app.ApplicationServices.GetRequiredService<T>();
}
public static T GetScopedService<T>(this IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.CreateScope())
{
return scope.ServiceProvider.GetRequiredService<T>();
}
}
}
}

View File

@@ -1,72 +1,72 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Extensions
{
public static class HttpClientExtensions
{
public static async Task<T> SendRequestAsync<T>(this HttpClient httpClient, string url, IHeaderDictionary headers, HttpMethod method, HttpContent content = null) where T : IResponse, new()
{
var request = new HttpRequestMessage(method, url);
if (method != HttpMethod.Get && method != HttpMethod.Delete)
{
request.Content = content;
}
foreach (var header in headers)
{
if (header.Key == "Content-Type" || header.Key == "Content-Length")
{
if (request.Content != null)
request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());
continue;
}
request.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());
}
var response = await httpClient.SendAsync(request);
if (response.Content.Headers.ContentLength > 0)
{
var res = await response.Content.ReadFromJsonAsync<T>();
res.Success = response.IsSuccessStatusCode;
res.StatusCode = (int)response.StatusCode;
return res;
}
else
{
return new T { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode };
}
}
public static Task<T> ForwardAsync<T>(this HttpClient httpClient, IHttpContextAccessor accessor, string url, HttpMethod method) where T : IResponse, new()
{
var httpContext = accessor.HttpContext;
var content = new StreamContent(httpContext.Request.BodyReader.AsStream());
return httpClient.SendRequestAsync<T>(url, httpContext.Request.Headers, method, content);
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Extensions
{
public static class HttpClientExtensions
{
public static async Task<T> SendRequestAsync<T>(this HttpClient httpClient, string url, IHeaderDictionary headers, HttpMethod method, HttpContent content = null) where T : IResponse, new()
{
var request = new HttpRequestMessage(method, url);
if (method != HttpMethod.Get && method != HttpMethod.Delete)
{
request.Content = content;
}
foreach (var header in headers)
{
if (header.Key == "Content-Type" || header.Key == "Content-Length")
{
if (request.Content != null)
request.Content.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());
continue;
}
request.Headers.TryAddWithoutValidation(header.Key, header.Value.AsEnumerable());
}
var response = await httpClient.SendAsync(request);
if (response.Content.Headers.ContentLength > 0)
{
var res = await response.Content.ReadFromJsonAsync<T>();
res.Success = response.IsSuccessStatusCode;
res.StatusCode = (int)response.StatusCode;
return res;
}
else
{
return new T { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode };
}
}
public static Task<T> ForwardAsync<T>(this HttpClient httpClient, IHttpContextAccessor accessor, string url, HttpMethod method) where T : IResponse, new()
{
var httpContext = accessor.HttpContext;
var content = new StreamContent(httpContext.Request.BodyReader.AsStream());
return httpClient.SendRequestAsync<T>(url, httpContext.Request.Headers, method, content);
}
}
}

View File

@@ -1,44 +1,53 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Microsoft.Extensions.DependencyInjection;
namespace Streetwriters.Common.Extensions
{
public static class ServiceCollectionServiceExtensions
{
public static IServiceCollection AddDefaultCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("notesnook", (b) =>
{
if (Constants.NOTESNOOK_CORS_ORIGINS.Length <= 0)
b.AllowAnyOrigin();
else
b.WithOrigins(Constants.NOTESNOOK_CORS_ORIGINS);
b.AllowAnyMethod()
.AllowAnyHeader();
});
});
return services;
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Microsoft.Extensions.DependencyInjection;
using Streetwriters.Data.DbContexts;
using Streetwriters.Data.Repositories;
namespace Streetwriters.Common.Extensions
{
public static class ServiceCollectionServiceExtensions
{
public static IServiceCollection AddRepository<T>(this IServiceCollection services, string collectionName, string database) where T : class
{
services.AddSingleton((provider) => MongoDbContext.GetMongoCollection<T>(provider.GetService<MongoDB.Driver.IMongoClient>(), database, collectionName));
services.AddScoped<Repository<T>>();
return services;
}
public static IServiceCollection AddDefaultCors(this IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("notesnook", (b) =>
{
if (Constants.NOTESNOOK_CORS_ORIGINS.Length <= 0)
b.AllowAnyOrigin();
else
b.WithOrigins(Constants.NOTESNOOK_CORS_ORIGINS);
b.AllowAnyMethod()
.AllowAnyHeader();
});
});
return services;
}
}
}

View File

@@ -1,91 +1,87 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
namespace System
{
public static class StringExtensions
{
public static string ToSha256(this string rawData, int maxLength = 12)
{
// Create a SHA256
using (SHA256 sha256Hash = SHA256.Create())
{
// ComputeHash - returns byte array
byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData));
return ToHex(bytes, 0, maxLength);
}
}
public static byte[] CompressBrotli(this string input)
{
var raw = Encoding.Default.GetBytes(input);
using (MemoryStream memory = new MemoryStream())
{
using (BrotliStream brotli = new BrotliStream(memory, CompressionLevel.Optimal))
{
brotli.Write(raw, 0, raw.Length);
}
return memory.ToArray();
}
}
public static string DecompressBrotli(this byte[] compressed)
{
using (BrotliStream stream = new BrotliStream(new MemoryStream(compressed), CompressionMode.Decompress))
{
const int size = 4096;
byte[] buffer = new byte[size];
using (MemoryStream memory = new MemoryStream())
{
int count = 0;
do
{
count = stream.Read(buffer, 0, size);
if (count > 0)
{
memory.Write(buffer, 0, count);
}
}
while (count > 0);
return Encoding.Default.GetString(memory.ToArray());
}
}
}
private static string ToHex(byte[] bytes, int startIndex, int length)
{
char[] c = new char[length * 2];
byte b;
for (int bx = startIndex, cx = startIndex; bx < length; ++bx, ++cx)
{
b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);
}
return new string(c);
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
namespace System
{
public static class StringExtensions
{
public static string Sha256(this string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = SHA256.HashData(bytes);
return Convert.ToBase64String(hash);
}
public static byte[] CompressBrotli(this string input)
{
var raw = Encoding.Default.GetBytes(input);
using (MemoryStream memory = new MemoryStream())
{
using (BrotliStream brotli = new BrotliStream(memory, CompressionLevel.Optimal))
{
brotli.Write(raw, 0, raw.Length);
}
return memory.ToArray();
}
}
public static string DecompressBrotli(this byte[] compressed)
{
using (BrotliStream stream = new BrotliStream(new MemoryStream(compressed), CompressionMode.Decompress))
{
const int size = 4096;
byte[] buffer = new byte[size];
using (MemoryStream memory = new MemoryStream())
{
int count = 0;
do
{
count = stream.Read(buffer, 0, size);
if (count > 0)
{
memory.Write(buffer, 0, count);
}
}
while (count > 0);
return Encoding.Default.GetString(memory.ToArray());
}
}
}
private static string ToHex(byte[] bytes, int startIndex, int length)
{
char[] c = new char[length * 2];
byte b;
for (int bx = startIndex, cx = startIndex; bx < length; ++bx, ++cx)
{
b = ((byte)(bytes[bx] >> 4));
c[cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);
b = ((byte)(bytes[bx] & 0x0F));
c[++cx] = (char)(b > 9 ? b + 0x37 + 0x20 : b + 0x30);
}
return new string(c);
}
}
}

View File

@@ -1,42 +1,42 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Microsoft.AspNetCore.Builder;
using Streetwriters.Common.Interfaces;
using WampSharp.AspNetCore.WebSockets.Server;
using WampSharp.Binding;
using WampSharp.V2;
using WampSharp.V2.Realm;
namespace Streetwriters.Common.Extensions
{
public static class WampRealmExtensions
{
public static IDisposable Subscribe<T>(this IWampHostedRealm realm, string topicName, Action<T> onNext)
{
return realm.Services.GetSubject<T>(topicName).Subscribe<T>(onNext);
}
public static IDisposable Subscribe<T>(this IWampHostedRealm realm, string topicName, IMessageHandler<T> handler)
{
return realm.Services.GetSubject<T>(topicName).Subscribe<T>(async (message) => await handler.Process(message));
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Microsoft.AspNetCore.Builder;
using Streetwriters.Common.Interfaces;
using WampSharp.AspNetCore.WebSockets.Server;
using WampSharp.Binding;
using WampSharp.V2;
using WampSharp.V2.Realm;
namespace Streetwriters.Common.Extensions
{
public static class WampRealmExtensions
{
public static IDisposable Subscribe<T>(this IWampHostedRealm realm, string topicName, Action<T> onNext)
{
return realm.Services.GetSubject<T>(topicName).Subscribe<T>(onNext);
}
public static IDisposable Subscribe<T>(this IWampHostedRealm realm, string topicName, IMessageHandler<T> handler)
{
return realm.Services.GetSubject<T>(topicName).Subscribe<T>(async (message) => await handler.Process(message));
}
}
}

View File

@@ -0,0 +1,23 @@
using System.IO;
using WebMarkupMin.Core;
using WebMarkupMin.Core.Loggers;
namespace Streetwriters.Common.Helpers
{
public static class HtmlHelper
{
public static string ReadMinifiedHtmlFile(string path)
{
var settings = new HtmlMinificationSettings()
{
WhitespaceMinificationMode = WhitespaceMinificationMode.Medium,
};
var cssMinifier = new KristensenCssMinifier();
var jsMinifier = new CrockfordJsMinifier();
var minifier = new HtmlMinifier(settings, cssMinifier, jsMinifier, new NullLogger());
return minifier.Minify(File.ReadAllText(path), false).MinifiedContent;
}
}
}

View File

@@ -1,47 +1,47 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Streetwriters.Common.Messages;
using WampSharp.V2;
using WampSharp.V2.Client;
namespace Streetwriters.Common.Helpers
{
public class WampHelper
{
public static async Task<IWampRealmProxy> OpenWampChannelAsync<T>(string server, string realmName)
{
DefaultWampChannelFactory channelFactory = new DefaultWampChannelFactory();
IWampChannel channel = channelFactory.CreateJsonChannel(server, realmName);
await channel.Open();
return channel.RealmProxy;
}
public static void PublishMessage<T>(IWampRealmProxy realm, string topicName, T message)
{
var subject = realm.Services.GetSubject<T>(topicName);
subject.OnNext(message);
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Streetwriters.Common.Messages;
using WampSharp.V2;
using WampSharp.V2.Client;
namespace Streetwriters.Common.Helpers
{
public class WampHelper
{
public static async Task<IWampRealmProxy> OpenWampChannelAsync(string server, string realmName)
{
DefaultWampChannelFactory channelFactory = new();
IWampChannel channel = channelFactory.CreateJsonChannel(server, realmName);
await channel.Open();
return channel.RealmProxy;
}
public static void PublishMessage<T>(IWampRealmProxy realm, string topicName, T message)
{
var subject = realm.Services.GetSubject<T>(topicName);
subject.OnNext(message);
}
}
}

View File

@@ -1,38 +1,38 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Threading.Tasks;
using Streetwriters.Common.Enums;
namespace Streetwriters.Common.Interfaces
{
public interface IClient
{
string Id { get; set; }
string Name { get; set; }
ApplicationType Type { get; set; }
ApplicationType AppId { get; set; }
string SenderEmail { get; set; }
string SenderName { get; set; }
string EmailConfirmedRedirectURL { get; }
string AccountRecoveryRedirectURL { get; }
Func<string, Task> OnEmailConfirmed { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Threading.Tasks;
using Streetwriters.Common.Enums;
namespace Streetwriters.Common.Interfaces
{
public interface IClient
{
string Id { get; set; }
string Name { get; set; }
ApplicationType Type { get; set; }
ApplicationType AppId { get; set; }
string SenderEmail { get; set; }
string SenderName { get; set; }
string EmailConfirmedRedirectURL { get; }
string AccountRecoveryRedirectURL { get; }
Func<string, Task> OnEmailConfirmed { get; set; }
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Interfaces
{
public interface IDocument
{
string Id
{
get; set;
}
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Interfaces
{
public interface IDocument
{
string Id
{
get; set;
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MimeKit;
using MimeKit.Cryptography;
using Streetwriters.Common.Models;
namespace Streetwriters.Common.Interfaces
{
public interface IEmailSender
{
Task SendEmailAsync(
string email,
EmailTemplate template,
IClient client,
GnuPGContext gpgContext = null,
Dictionary<string, byte[]> attachments = null
);
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Interfaces
{
public interface IMessageHandler<T>
{
Task Process(T message);
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Threading.Tasks;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Interfaces
{
public interface IMessageHandler<T>
{
Task Process(T message);
}
}

View File

@@ -1,32 +1,32 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
namespace Streetwriters.Common.Interfaces
{
public interface IOffer : IDocument
{
ApplicationType AppId { get; set; }
string PromoCode { get; set; }
PromoCode[] Codes { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
namespace Streetwriters.Common.Interfaces
{
public interface IOffer : IDocument
{
ApplicationType AppId { get; set; }
string PromoCode { get; set; }
PromoCode[] Codes { get; set; }
}
}

View File

@@ -1,27 +1,27 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Interfaces
{
public interface IResponse
{
bool Success { get; set; }
int StatusCode { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Interfaces
{
public interface IResponse
{
bool Success { get; set; }
int StatusCode { get; set; }
}
}

View File

@@ -1,41 +1,41 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Streetwriters.Common.Attributes;
using Streetwriters.Common.Converters;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
namespace Streetwriters.Common.Interfaces
{
[JsonInterfaceConverter(typeof(InterfaceConverter<ISubscription, Subscription>))]
public interface ISubscription : IDocument
{
string UserId { get; set; }
ApplicationType AppId { get; set; }
SubscriptionProvider Provider { get; set; }
long StartDate { get; set; }
long ExpiryDate { get; set; }
SubscriptionType Type { get; set; }
string OrderId { get; set; }
string SubscriptionId { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using Streetwriters.Common.Attributes;
using Streetwriters.Common.Converters;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
namespace Streetwriters.Common.Interfaces
{
[JsonInterfaceConverter(typeof(InterfaceConverter<ISubscription, Subscription>))]
public interface ISubscription : IDocument
{
string UserId { get; set; }
ApplicationType AppId { get; set; }
SubscriptionProvider Provider { get; set; }
long StartDate { get; set; }
long ExpiryDate { get; set; }
SubscriptionType Type { get; set; }
string OrderId { get; set; }
string SubscriptionId { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Streetwriters.Common.Models;
using WampSharp.V2.Rpc;
namespace Streetwriters.Common.Interfaces
{
public interface IUserAccountService
{
[WampProcedure("co.streetwriters.identity.users.get_user")]
Task<UserModel> GetUserAsync(string clientId, string userId);
[WampProcedure("co.streetwriters.identity.users.delete_user")]
Task DeleteUserAsync(string clientId, string userId, string password);
// [WampProcedure("co.streetwriters.identity.users.create_user")]
// Task<UserModel> CreateUserAsync();
}
}

View File

@@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Streetwriters.Common.Helpers;
using Streetwriters.Common.Models;
using WampSharp.V2.Rpc;
namespace Streetwriters.Common.Interfaces
{
public interface IUserSubscriptionService
{
[WampProcedure("co.streetwriters.subscriptions.subscriptions.get_user_subscription")]
Task<Subscription> GetUserSubscriptionAsync(string clientId, string userId);
}
}

View File

@@ -1,52 +1,52 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Threading.Tasks;
namespace Streetwriters.Common
{
public class Slogger<T>
{
public static Task Info(string scope, params string[] messages)
{
return Write(Format("info", scope, messages));
}
public static Task Error(string scope, params string[] messages)
{
return Write(Format("error", scope, messages));
}
private static string Format(string level, string scope, params string[] messages)
{
var date = DateTime.UtcNow.ToString("MM-dd-yyyy HH:mm:ss");
var messageText = string.Join(" ", messages);
return $"[{date}] | {level} | <{scope}> {messageText}";
}
private static Task Write(string line)
{
var logDirectory = Path.GetFullPath("./logs");
if (!Directory.Exists(logDirectory))
Directory.CreateDirectory(logDirectory);
var path = Path.Join(logDirectory, typeof(T).FullName + "-" + DateTime.UtcNow.ToString("MM-dd-yyyy") + ".log");
return File.AppendAllLinesAsync(path, new string[1] { line });
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Threading.Tasks;
namespace Streetwriters.Common
{
public class Slogger<T>
{
public static Task Info(string scope, params string[] messages)
{
return Write(Format("info", scope, messages));
}
public static Task Error(string scope, params string[] messages)
{
return Write(Format("error", scope, messages));
}
private static string Format(string level, string scope, params string[] messages)
{
var date = DateTime.UtcNow.ToString("MM-dd-yyyy HH:mm:ss");
var messageText = string.Join(" ", messages);
return $"[{date}] | {level} | <{scope}> {messageText}";
}
private static Task Write(string line)
{
var logDirectory = Path.GetFullPath("./logs");
if (!Directory.Exists(logDirectory))
Directory.CreateDirectory(logDirectory);
var path = Path.Join(logDirectory, typeof(T).FullName + "-" + DateTime.UtcNow.ToString("MM-dd-yyyy") + ".log");
return File.AppendAllLinesAsync(path, new string[1] { line });
}
}
}

View File

@@ -1,36 +1,38 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
namespace Streetwriters.Data.Attributes
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class BsonCollectionAttribute : Attribute
{
public string CollectionName { get; }
public string DatabaseName { get; }
public BsonCollectionAttribute(string databaseName, string collectionName)
{
CollectionName = collectionName;
DatabaseName = databaseName;
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class ClearCacheMessage
{
public ClearCacheMessage(List<string> keys)
{
this.Keys = keys;
}
[JsonPropertyName("keys")]
public List<string> Keys { get; set; }
}
}

View File

@@ -1,63 +1,63 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class CreateSubscriptionMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("type")]
public SubscriptionType Type { get; set; }
[JsonPropertyName("start")]
public long StartTime { get; set; }
[JsonPropertyName("expiry")]
public long ExpiryTime { get; set; }
[JsonPropertyName("orderId")]
public string OrderId { get; set; }
[JsonPropertyName("updateURL")]
public string UpdateURL { get; set; }
[JsonPropertyName("cancelURL")]
public string CancelURL { get; set; }
[JsonPropertyName("subscriptionId")]
public string SubscriptionId { get; set; }
[JsonPropertyName("productId")]
public string ProductId { get; set; }
public bool Extend { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class CreateSubscriptionMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("type")]
public SubscriptionType Type { get; set; }
[JsonPropertyName("start")]
public long StartTime { get; set; }
[JsonPropertyName("expiry")]
public long ExpiryTime { get; set; }
[JsonPropertyName("orderId")]
public string OrderId { get; set; }
[JsonPropertyName("updateURL")]
public string UpdateURL { get; set; }
[JsonPropertyName("cancelURL")]
public string CancelURL { get; set; }
[JsonPropertyName("subscriptionId")]
public string SubscriptionId { get; set; }
[JsonPropertyName("productId")]
public string ProductId { get; set; }
public bool Extend { get; set; }
}
}

View File

@@ -1,35 +1,35 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class DeleteSubscriptionMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class DeleteSubscriptionMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
}
}

View File

@@ -1,32 +1,32 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class DeleteUserMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class DeleteUserMessage
{
[JsonPropertyName("userId")]
public string UserId { get; set; }
}
}

View File

@@ -1,48 +1,48 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class Message
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("data")]
public string Data { get; set; }
}
public class SendSSEMessage
{
[JsonPropertyName("sendToAll")]
public bool SendToAll { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("message")]
public Message Message { get; set; }
[JsonPropertyName("originTokenId")]
public string OriginTokenId { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Messages
{
public class Message
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("data")]
public string Data { get; set; }
}
public class SendSSEMessage
{
[JsonPropertyName("sendToAll")]
public bool SendToAll { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonPropertyName("message")]
public Message Message { get; set; }
[JsonPropertyName("originTokenId")]
public string OriginTokenId { get; set; }
}
}

View File

@@ -1,45 +1,45 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Client : IClient
{
public string Id { get; set; }
public string Name { get; set; }
public ApplicationType Type { get; set; }
public ApplicationType AppId { get; set; }
public string SenderEmail { get; set; }
public string SenderName { get; set; }
public string EmailConfirmedRedirectURL { get; set; }
public string AccountRecoveryRedirectURL { get; set; }
public Func<string, Task> OnEmailConfirmed { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Client : IClient
{
public string Id { get; set; }
public string Name { get; set; }
public ApplicationType Type { get; set; }
public ApplicationType AppId { get; set; }
public string SenderEmail { get; set; }
public string SenderName { get; set; }
public string EmailConfirmedRedirectURL { get; set; }
public string AccountRecoveryRedirectURL { get; set; }
public Func<string, Task> OnEmailConfirmed { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Streetwriters.Common.Models
{
public class EmailTemplate
{
public int? Id { get; set; }
public object Data { get; set; }
public string Subject { get; set; }
public string Html { get; set; }
public string Text { get; set; }
}
}

View File

@@ -1,31 +1,31 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class SubscriptionResponse : Response
{
[JsonPropertyName("subscription")]
public ISubscription Subscription { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class SubscriptionResponse : Response
{
[JsonPropertyName("subscription")]
public ISubscription Subscription { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class GiftCard : IDocument
{
public GiftCard()
{
Id = ObjectId.GenerateNewId().ToString();
}
public string Code { get; set; }
public string OrderId { get; set; }
public string OrderIdType { get; set; }
public string ProductId { get; set; }
public string RedeemedBy { get; set; }
public long RedeemedAt { get; set; }
public long Timestamp { get; set; }
public long Term { get; set; }
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonIgnore]
public string Id { get; set; }
}
}

View File

@@ -1,29 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Models
{
public class MFAConfig
{
public bool IsEnabled { get; set; }
public string PrimaryMethod { get; set; }
public string SecondaryMethod { get; set; }
public int RemainingValidCodes { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Streetwriters.Common.Models
{
public class MFAConfig
{
public bool IsEnabled { get; set; }
public string PrimaryMethod { get; set; }
public string SecondaryMethod { get; set; }
public int RemainingValidCodes { get; set; }
}
}

View File

@@ -1,55 +1,53 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Streetwriters.Common.Models.Offer
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Data.Attributes;
namespace Streetwriters.Common.Models
{
[BsonCollection("subscriptions", "offers")]
public class Offer : IOffer
{
public Offer()
{
Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("promoCode")]
public string PromoCode { get; set; }
[JsonPropertyName("codes")]
public PromoCode[] Codes { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Streetwriters.Common.Models.Offer
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Offer : IOffer
{
public Offer()
{
Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("promoCode")]
public string PromoCode { get; set; }
[JsonPropertyName("codes")]
public PromoCode[] Codes { get; set; }
}
}

View File

@@ -1,40 +1,40 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Streetwriters.Common.Models.Offer
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class PromoCode
{
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[JsonPropertyName("code")]
public string Code { get; set; }
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Streetwriters.Common.Models.Offer
using System.Collections.Generic;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class PromoCode
{
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[JsonPropertyName("code")]
public string Code { get; set; }
}
}

View File

@@ -1,32 +1,32 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Response : IResponse
{
[JsonIgnore]
public bool Success { get; set; }
public int StatusCode { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Response : IResponse
{
[JsonIgnore]
public bool Success { get; set; }
public int StatusCode { get; set; }
}
}

View File

@@ -1,143 +1,141 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using AspNetCore.Identity.Mongo.Model;
using Streetwriters.Data.Attributes;
namespace Streetwriters.Common.Models
{
[BsonCollection("identity", "roles")]
public class Role : MongoRole
{
// [DataMember(Name = "email")]
// [BsonElement("email")]
// public string Email
// {
// get; set;
// }
// [DataMember(Name = "isEmailConfirmed")]
// [BsonElement("isEmailConfirmed")]
// public bool IsEmailConfirmed { get; set; }
// [DataMember(Name = "username")]
// [BsonElement("username")]
// public string Username
// {
// get; set;
// }
// [BsonId]
// [BsonRepresentation(BsonType.ObjectId)]
// public string Id
// {
// get; set;
// }
// [IgnoreDataMember]
// [BsonElement("passwordHash")]
// public string PasswordHash
// {
// get; set;
// }
// [DataMember(Name = "salt")]
// public string Salt
// {
// get; set;
// }
}
/*
public class Picture
{
[DataMember(Name = "thumbnail")]
public string Thumbnail
{
get; set;
}
[DataMember(Name = "full")]
public string Full
{
get; set;
}
}
public class Streetwriters
{
[DataMember(Name = "fullName")]
public string FullName
{
get; set;
}
[DataMember(Name = "biography")]
[StringLength(240)]
public string Biography
{
get; set;
}
[DataMember(Name = "favoriteWords")]
public string FavoriteWords
{
get; set;
}
[DataMember(Name = "profilePicture")]
public Picture ProfilePicture
{
get; set;
}
[DataMember(Name = "followers")]
public string[] Followers
{
get; set;
}
[DataMember(Name = "following")]
public string[] Following
{
get; set;
}
[DataMember(Name = "website")]
[Url]
public string Website
{
get; set;
}
[DataMember(Name = "instagram")]
public string Instagram
{
get; set;
}
[DataMember(Name = "twitter")]
public string Twitter
{
get; set;
}
} */
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using AspNetCore.Identity.Mongo.Model;
namespace Streetwriters.Common.Models
{
public class Role : MongoRole
{
// [DataMember(Name = "email")]
// [BsonElement("email")]
// public string Email
// {
// get; set;
// }
// [DataMember(Name = "isEmailConfirmed")]
// [BsonElement("isEmailConfirmed")]
// public bool IsEmailConfirmed { get; set; }
// [DataMember(Name = "username")]
// [BsonElement("username")]
// public string Username
// {
// get; set;
// }
// [BsonId]
// [BsonRepresentation(BsonType.ObjectId)]
// public string Id
// {
// get; set;
// }
// [IgnoreDataMember]
// [BsonElement("passwordHash")]
// public string PasswordHash
// {
// get; set;
// }
// [DataMember(Name = "salt")]
// public string Salt
// {
// get; set;
// }
}
/*
public class Picture
{
[DataMember(Name = "thumbnail")]
public string Thumbnail
{
get; set;
}
[DataMember(Name = "full")]
public string Full
{
get; set;
}
}
public class Streetwriters
{
[DataMember(Name = "fullName")]
public string FullName
{
get; set;
}
[DataMember(Name = "biography")]
[StringLength(240)]
public string Biography
{
get; set;
}
[DataMember(Name = "favoriteWords")]
public string FavoriteWords
{
get; set;
}
[DataMember(Name = "profilePicture")]
public Picture ProfilePicture
{
get; set;
}
[DataMember(Name = "followers")]
public string[] Followers
{
get; set;
}
[DataMember(Name = "following")]
public string[] Following
{
get; set;
}
[DataMember(Name = "website")]
[Url]
public string Website
{
get; set;
}
[DataMember(Name = "instagram")]
public string Instagram
{
get; set;
}
[DataMember(Name = "twitter")]
public string Twitter
{
get; set;
}
} */
}

View File

@@ -1,82 +1,80 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
using Streetwriters.Data.Attributes;
namespace Streetwriters.Common.Models
{
[BsonCollection("subscriptions", "subscriptions")]
public class Subscription : ISubscription
{
public Subscription()
{
Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonIgnore]
public string OrderId { get; set; }
[JsonIgnore]
public string SubscriptionId { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("start")]
public long StartDate { get; set; }
[JsonPropertyName("expiry")]
public long ExpiryDate { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("type")]
public SubscriptionType Type { get; set; }
[JsonPropertyName("cancelURL")]
public string CancelURL { get; set; }
[JsonPropertyName("updateURL")]
public string UpdateURL { get; set; }
[JsonPropertyName("productId")]
public string ProductId { get; set; }
[JsonIgnore]
public int TrialExtensionCount { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
namespace Streetwriters.Common.Models
{
public class Subscription : ISubscription
{
public Subscription()
{
Id = ObjectId.GenerateNewId().ToString();
}
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("userId")]
public string UserId { get; set; }
[JsonIgnore]
public string OrderId { get; set; }
[JsonIgnore]
public string SubscriptionId { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("appId")]
public ApplicationType AppId { get; set; }
[JsonPropertyName("start")]
public long StartDate { get; set; }
[JsonPropertyName("expiry")]
public long ExpiryDate { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("provider")]
public SubscriptionProvider Provider { get; set; }
[BsonRepresentation(BsonType.Int32)]
[JsonPropertyName("type")]
public SubscriptionType Type { get; set; }
[JsonPropertyName("cancelURL")]
public string CancelURL { get; set; }
[JsonPropertyName("updateURL")]
public string UpdateURL { get; set; }
[JsonPropertyName("productId")]
public string ProductId { get; set; }
[JsonIgnore]
public int TrialExtensionCount { get; set; }
}
}

View File

@@ -1,31 +1,29 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using AspNetCore.Identity.Mongo.Model;
using Streetwriters.Data.Attributes;
namespace Streetwriters.Common.Models
{
[BsonCollection("identity", "users")]
public class User : MongoUser
{
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using AspNetCore.Identity.Mongo.Model;
namespace Streetwriters.Common.Models
{
public class User : MongoUser
{
}
}

View File

@@ -1,42 +1,45 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Models
{
public class UserModel
{
[JsonPropertyName("id")]
public string UserId { get; set; }
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("phoneNumber")]
public string PhoneNumber { get; set; }
[JsonPropertyName("isEmailConfirmed")]
public bool IsEmailConfirmed { get; set; }
[JsonPropertyName("mfa")]
public MFAConfig MFA { get; set; }
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Text.Json.Serialization;
namespace Streetwriters.Common.Models
{
public class UserModel
{
[JsonPropertyName("id")]
public string UserId { get; set; }
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("phoneNumber")]
public string PhoneNumber { get; set; }
[JsonPropertyName("isEmailConfirmed")]
public bool IsEmailConfirmed { get; set; }
[JsonPropertyName("marketingConsent")]
public bool MarketingConsent { get; set; }
[JsonPropertyName("mfa")]
public MFAConfig MFA { get; set; }
}
}

View File

@@ -1,121 +1,122 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
#if !DEBUG
using System;
using System.Security.Cryptography.X509Certificates;
#endif
namespace Streetwriters.Common
{
public class Server
{
public Server(string originCertPath = null, string originCertKeyPath = null)
{
if (!string.IsNullOrEmpty(originCertPath) && !string.IsNullOrEmpty(originCertKeyPath))
this.SSLCertificate = X509Certificate2.CreateFromPemFile(originCertPath, originCertKeyPath);
}
public int Port { get; set; }
public string Hostname { get; set; }
public string Domain { get; set; }
public X509Certificate2 SSLCertificate { get; }
public bool IsSecure { get => this.SSLCertificate != null; }
public override string ToString()
{
var url = "";
url += "http";
url += $"://{Hostname}";
url += Port == 80 || Port == 443 ? "" : $":{Port}";
return url;
}
public string WS()
{
var url = "";
url += IsSecure ? "ws" : "ws";
url += $"://{Hostname}";
url += Port == 80 ? "" : $":{Port}";
return url;
}
}
public class Servers
{
#if DEBUG
public static string GetLocalIPv4(NetworkInterfaceType _type)
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
string output = "";
foreach (NetworkInterface item in interfaces)
{
if (item.NetworkInterfaceType == _type && item.OperationalStatus == OperationalStatus.Up)
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
public readonly static string HOST = GetLocalIPv4(NetworkInterfaceType.Ethernet);
public static Server S3Server { get; } = new()
{
Port = 4568,
Hostname = HOST,
Domain = HOST
};
#endif
public static Server NotesnookAPI { get; } = new(Constants.NOTESNOOK_CERT_PATH, Constants.NOTESNOOK_CERT_KEY_PATH)
{
Domain = Constants.NOTESNOOK_SERVER_DOMAIN,
Port = Constants.NOTESNOOK_SERVER_PORT,
Hostname = Constants.NOTESNOOK_SERVER_HOST,
};
public static Server MessengerServer { get; } = new(Constants.SSE_CERT_PATH, Constants.SSE_CERT_KEY_PATH)
{
Domain = Constants.SSE_SERVER_DOMAIN,
Port = Constants.SSE_SERVER_PORT,
Hostname = Constants.SSE_SERVER_HOST,
};
public static Server IdentityServer { get; } = new(Constants.IDENTITY_CERT_PATH, Constants.IDENTITY_CERT_KEY_PATH)
{
Domain = Constants.IDENTITY_SERVER_DOMAIN,
Port = Constants.IDENTITY_SERVER_PORT,
Hostname = Constants.IDENTITY_SERVER_HOST,
};
public static Server SubscriptionServer { get; } = new(Constants.SUBSCRIPTIONS_CERT_PATH, Constants.SUBSCRIPTIONS_CERT_KEY_PATH)
{
Domain = Constants.SUBSCRIPTIONS_SERVER_DOMAIN,
Port = Constants.SUBSCRIPTIONS_SERVER_PORT,
Hostname = Constants.SUBSCRIPTIONS_SERVER_HOST,
};
}
}
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
#if !DEBUG
using System;
using System.Security.Cryptography.X509Certificates;
#endif
namespace Streetwriters.Common
{
public class Server
{
public Server(string originCertPath = null, string originCertKeyPath = null)
{
if (!string.IsNullOrEmpty(originCertPath) && !string.IsNullOrEmpty(originCertKeyPath))
this.SSLCertificate = X509Certificate2.CreateFromPemFile(originCertPath, originCertKeyPath);
}
public string Id { get; set; }
public int Port { get; set; }
public string Hostname { get; set; }
public Uri PublicURL { get; set; }
public X509Certificate2 SSLCertificate { get; }
public bool IsSecure { get => this.SSLCertificate != null; }
public override string ToString()
{
var url = "";
url += "http";
url += $"://{Hostname}";
url += Port == 80 || Port == 443 ? "" : $":{Port}";
return url;
}
public string WS()
{
var url = "";
url += IsSecure ? "ws" : "ws";
url += $"://{Hostname}";
url += Port == 80 ? "" : $":{Port}";
return url;
}
}
public class Servers
{
#if (DEBUG || STAGING)
public static string GetLocalIPv4()
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
string output = "";
foreach (NetworkInterface item in interfaces)
{
if ((item.NetworkInterfaceType == NetworkInterfaceType.Ethernet || item.NetworkInterfaceType == NetworkInterfaceType.Wireless80211) && item.OperationalStatus == OperationalStatus.Up)
{
foreach (UnicastIPAddressInformation ip in item.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
{
output = ip.Address.ToString();
}
}
}
}
return output;
}
public readonly static string HOST = GetLocalIPv4();
public static Server S3Server { get; } = new()
{
Port = 4568,
Hostname = HOST
};
#endif
public static Server NotesnookAPI { get; } = new(Constants.NOTESNOOK_CERT_PATH, Constants.NOTESNOOK_CERT_KEY_PATH)
{
Port = Constants.NOTESNOOK_SERVER_PORT,
Hostname = Constants.NOTESNOOK_SERVER_HOST,
Id = "notesnook-sync"
};
public static Server MessengerServer { get; } = new(Constants.SSE_CERT_PATH, Constants.SSE_CERT_KEY_PATH)
{
Port = Constants.SSE_SERVER_PORT,
Hostname = Constants.SSE_SERVER_HOST,
Id = "sse"
};
public static Server IdentityServer { get; } = new(Constants.IDENTITY_CERT_PATH, Constants.IDENTITY_CERT_KEY_PATH)
{
PublicURL = Constants.IDENTITY_SERVER_URL,
Port = Constants.IDENTITY_SERVER_PORT,
Hostname = Constants.IDENTITY_SERVER_HOST,
Id = "auth"
};
public static Server SubscriptionServer { get; } = new(Constants.SUBSCRIPTIONS_CERT_PATH, Constants.SUBSCRIPTIONS_CERT_KEY_PATH)
{
Port = Constants.SUBSCRIPTIONS_SERVER_PORT,
Hostname = Constants.SUBSCRIPTIONS_SERVER_HOST,
Id = "subscription"
};
}
}

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