diff --git a/.pnp.cjs b/.pnp.cjs index 57b64b29e..1448be776 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6817,6 +6817,7 @@ const RAW_RUNTIME_STATE = ["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\ ["@aws-sdk/client-sns", "npm:3.484.0"],\ ["@aws-sdk/client-sqs", "npm:3.484.0"],\ + ["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ diff --git a/.yarn/cache/@cbor-extract-cbor-extract-darwin-arm64-npm-2.1.1-7f6025512f-10.zip b/.yarn/cache/@cbor-extract-cbor-extract-darwin-arm64-npm-2.1.1-7f6025512f-10.zip deleted file mode 100644 index ed30bfc94..000000000 Binary files a/.yarn/cache/@cbor-extract-cbor-extract-darwin-arm64-npm-2.1.1-7f6025512f-10.zip and /dev/null differ diff --git a/.yarn/cache/fsevents-patch-19706e7e35-10.zip b/.yarn/cache/fsevents-patch-19706e7e35-10.zip deleted file mode 100644 index aff1ab12c..000000000 Binary files a/.yarn/cache/fsevents-patch-19706e7e35-10.zip and /dev/null differ diff --git a/packages/analytics/src/Domain/Email/daily-analytics-report.html.ts b/packages/analytics/src/Domain/Email/daily-analytics-report.html.ts index 1f63dd30a..3cedc9675 100644 --- a/packages/analytics/src/Domain/Email/daily-analytics-report.html.ts +++ b/packages/analytics/src/Domain/Email/daily-analytics-report.html.ts @@ -4,6 +4,7 @@ import { TimerInterface } from '@standardnotes/time' import { AnalyticsActivity } from '../Analytics/AnalyticsActivity' import { StatisticMeasureName } from '../Statistics/StatisticMeasureName' import { Period } from '../Time/Period' +import { safeHtml } from '@standardnotes/common' const countActiveUsers = (measureName: string, data: any): { yesterday: number; last30Days: number } => { const totalActiveUsersLast30DaysIncludingToday = data.statisticsOverTime.find( @@ -567,7 +568,7 @@ export const html = (data: any, timer: TimerInterface) => { const totalActivePlusUsers = countActiveUsers(StatisticMeasureName.NAMES.ActivePlusUsers, data) const totalActiveProUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveProUsers, data) - return `
+ return safeHtml`

Hello,

Here are some statistics from yesterday: diff --git a/packages/auth/src/Domain/Email/offline-subscription-token-created.html.ts b/packages/auth/src/Domain/Email/offline-subscription-token-created.html.ts index 6e7497ba4..99a250461 100644 --- a/packages/auth/src/Domain/Email/offline-subscription-token-created.html.ts +++ b/packages/auth/src/Domain/Email/offline-subscription-token-created.html.ts @@ -1,4 +1,6 @@ -export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => `

+import { safeHtml } from '@standardnotes/common' + +export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => safeHtml`
diff --git a/packages/auth/src/Domain/Email/shared-subscription-invitation-created.html.ts b/packages/auth/src/Domain/Email/shared-subscription-invitation-created.html.ts index 2ed1bacb8..422a0a5e8 100644 --- a/packages/auth/src/Domain/Email/shared-subscription-invitation-created.html.ts +++ b/packages/auth/src/Domain/Email/shared-subscription-invitation-created.html.ts @@ -1,4 +1,6 @@ -export const html = (inviterIdentifier: string, inviteUuid: string) => `

Hello,

+import { safeHtml } from '@standardnotes/common' + +export const html = (inviterIdentifier: string, inviteUuid: string) => safeHtml`

Hello,

You've been invited to join a Standard Notes premium subscription at no cost. ${inviterIdentifier} has invited you to share the benefits of their subscription plan.

Accept Invite diff --git a/packages/auth/src/Domain/Email/user-email-changed.html.ts b/packages/auth/src/Domain/Email/user-email-changed.html.ts index c8f7d2d35..c275bc780 100644 --- a/packages/auth/src/Domain/Email/user-email-changed.html.ts +++ b/packages/auth/src/Domain/Email/user-email-changed.html.ts @@ -1,4 +1,6 @@ -export const html = (newEmail: string) => ` +import { safeHtml } from '@standardnotes/common' + +export const html = (newEmail: string) => safeHtml`

Hello,

We are writing to inform you that your request to update your email address has been successfully processed. The email address associated with your Standard Notes account has now been changed to the following:

diff --git a/packages/auth/src/Domain/Email/user-invited-to-shared-vault.html.ts b/packages/auth/src/Domain/Email/user-invited-to-shared-vault.html.ts index bcd1be005..8bca278bc 100644 --- a/packages/auth/src/Domain/Email/user-invited-to-shared-vault.html.ts +++ b/packages/auth/src/Domain/Email/user-invited-to-shared-vault.html.ts @@ -1,4 +1,6 @@ -export const html = () => ` +import { safeHtml } from '@standardnotes/common' + +export const html = () => safeHtml`

Hello,

You've been invited to join a shared vault. This shared workspace will help you collaborate and securely manage notes and files.

diff --git a/packages/auth/src/Domain/Email/user-signed-in.html.ts b/packages/auth/src/Domain/Email/user-signed-in.html.ts index c72b9210e..a2be181d7 100644 --- a/packages/auth/src/Domain/Email/user-signed-in.html.ts +++ b/packages/auth/src/Domain/Email/user-signed-in.html.ts @@ -1,4 +1,6 @@ -export const html = (email: string, device: string, browser: string, timeAndDate: string) => ` +import { safeHtml } from '@standardnotes/common' + +export const html = (email: string, device: string, browser: string, timeAndDate: string) => safeHtml`

Hello,

We've detected a new sign-in to your account ${email}

diff --git a/packages/common/src/Domain/Html/SafeHtml.spec.ts b/packages/common/src/Domain/Html/SafeHtml.spec.ts new file mode 100644 index 000000000..5d65a56aa --- /dev/null +++ b/packages/common/src/Domain/Html/SafeHtml.spec.ts @@ -0,0 +1,16 @@ +import { safeHtml } from './SafeHtml' + +describe('html', () => { + test('Should escape html from user input', () => { + const basicStringInput = '

User

' + const numberValue = 10 + expect(safeHtml`

Hello world, ${basicStringInput} ${numberValue}

`).toBe( + '

Hello world, <h1>User</h1> 10

', + ) + }) + + test('Should join arrays and escape', () => { + const arrayOfStrings = ['

User

', '

Test

'] + expect(safeHtml`

${arrayOfStrings}

`).toBe('

<h1>User</h1><p>Test</p>

') + }) +}) diff --git a/packages/common/src/Domain/Html/SafeHtml.ts b/packages/common/src/Domain/Html/SafeHtml.ts new file mode 100644 index 000000000..e49bd13c8 --- /dev/null +++ b/packages/common/src/Domain/Html/SafeHtml.ts @@ -0,0 +1,32 @@ +function escapeHTML(str: string) { + return str + .replace(/&/g, '&') + .replace(/>/g, '>') + .replace(/) { + const raw = literals.raw + + let result = raw[0] + + for (let index = 1; index < raw.length; index++) { + const literal = raw[index] + let substitution = substitutions[index - 1] + if (Array.isArray(substitution)) { + substitution = substitution.join('') + } else if (typeof substitution === 'number') { + substitution = substitution.toString() + } + substitution = escapeHTML(substitution) + result += substitution + literal + } + + return result +} diff --git a/packages/common/src/Domain/index.ts b/packages/common/src/Domain/index.ts index d788e6484..c6fe229d0 100644 --- a/packages/common/src/Domain/index.ts +++ b/packages/common/src/Domain/index.ts @@ -18,3 +18,4 @@ export * from './Subscription/SubscriptionName' export * from './Type/Either' export * from './Type/Only' export * from './User/UserRequestType' +export * from './Html/SafeHtml' diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json index 462173b31..3de9e7e50 100644 --- a/packages/scheduler/package.json +++ b/packages/scheduler/package.json @@ -29,6 +29,7 @@ "dependencies": { "@aws-sdk/client-sns": "^3.484.0", "@aws-sdk/client-sqs": "^3.484.0", + "@standardnotes/common": "workspace:^", "@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*", diff --git a/packages/scheduler/src/Domain/Email/encourage-email-backups.html.ts b/packages/scheduler/src/Domain/Email/encourage-email-backups.html.ts index 9285790aa..f33e77b94 100644 --- a/packages/scheduler/src/Domain/Email/encourage-email-backups.html.ts +++ b/packages/scheduler/src/Domain/Email/encourage-email-backups.html.ts @@ -1,4 +1,6 @@ -export const html = `
+import { safeHtml } from '@standardnotes/common' + +export const html = safeHtml`

Did you know you can enable daily email backups for your account? This free feature sends an email to your inbox with an encrypted backup file including all your notes and tags. diff --git a/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html.ts b/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html.ts index 07473f08b..ed148ac82 100644 --- a/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html.ts +++ b/packages/scheduler/src/Domain/Email/encourage-subscription-purchasing.html.ts @@ -1,4 +1,6 @@ -export const html = (registrationDate: string, annualPlusPrice: number, annualProPrice: number) => `

+import { safeHtml } from '@standardnotes/common' + +export const html = (registrationDate: string, annualPlusPrice: number, annualProPrice: number) => safeHtml`

Hi there,

We hope you've been finding great use out of Standard Notes. We built Standard Notes to be a secure place for diff --git a/packages/scheduler/src/Domain/Email/exit-interview.html.ts b/packages/scheduler/src/Domain/Email/exit-interview.html.ts index e5420a50b..231a611f3 100644 --- a/packages/scheduler/src/Domain/Email/exit-interview.html.ts +++ b/packages/scheduler/src/Domain/Email/exit-interview.html.ts @@ -1,4 +1,6 @@ -export const html = `

+import { safeHtml } from '@standardnotes/common' + +export const html = safeHtml`

We're truly sad to see you leave. Our mission is simple: build the best, most private, and most secure note-taking app available. It's clear we've fallen short of your expectations somewhere along the way. diff --git a/packages/syncing-server/src/Domain/Email/dropbox-backup-failed.html.ts b/packages/syncing-server/src/Domain/Email/dropbox-backup-failed.html.ts index da8a061f5..98cff02da 100644 --- a/packages/syncing-server/src/Domain/Email/dropbox-backup-failed.html.ts +++ b/packages/syncing-server/src/Domain/Email/dropbox-backup-failed.html.ts @@ -1,4 +1,6 @@ -export const html = `

Hello,

+import { safeHtml } from '@standardnotes/common' + +export const html = safeHtml`

Hello,

We recently tried backing up your data to Dropbox, but an issue prevented us from doing so.

The usual cause is an expired or revoked token from your sync provider. Please follow diff --git a/packages/syncing-server/src/Domain/Email/email-backup-attachment-created.html.ts b/packages/syncing-server/src/Domain/Email/email-backup-attachment-created.html.ts index 8f3215bd8..606ae77ba 100644 --- a/packages/syncing-server/src/Domain/Email/email-backup-attachment-created.html.ts +++ b/packages/syncing-server/src/Domain/Email/email-backup-attachment-created.html.ts @@ -1,4 +1,6 @@ -export const html = (email: string) => ` +import { safeHtml } from '@standardnotes/common' + +export const html = (email: string) => safeHtml`

Your encrypted data backup is attached for ${email}. You can import this file using the Standard Notes web or desktop app, or by using the offline decryption script available at diff --git a/packages/syncing-server/src/Domain/Email/google-drive-backup-failed.html.ts b/packages/syncing-server/src/Domain/Email/google-drive-backup-failed.html.ts index 4654d40e9..2771f883b 100644 --- a/packages/syncing-server/src/Domain/Email/google-drive-backup-failed.html.ts +++ b/packages/syncing-server/src/Domain/Email/google-drive-backup-failed.html.ts @@ -1,4 +1,6 @@ -export const html = `

Hello,

+import { safeHtml } from '@standardnotes/common' + +export const html = safeHtml`

Hello,

We recently tried backing up your data to Google Drive Sync, but an issue prevented us from doing so.

diff --git a/packages/syncing-server/src/Domain/Email/one-drive-backup-failed.html.ts b/packages/syncing-server/src/Domain/Email/one-drive-backup-failed.html.ts index 90fe94975..0172111de 100644 --- a/packages/syncing-server/src/Domain/Email/one-drive-backup-failed.html.ts +++ b/packages/syncing-server/src/Domain/Email/one-drive-backup-failed.html.ts @@ -1,4 +1,6 @@ -export const html = `

Hello,

+import { safeHtml } from '@standardnotes/common' + +export const html = safeHtml`

Hello,

We recently tried backing up your data to OneDrive Sync, but an issue prevented us from doing so.

diff --git a/yarn.lock b/yarn.lock index 69d4fd6f5..9505ade9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6164,6 +6164,7 @@ __metadata: dependencies: "@aws-sdk/client-sns": "npm:^3.484.0" "@aws-sdk/client-sqs": "npm:^3.484.0" + "@standardnotes/common": "workspace:^" "@standardnotes/domain-core": "workspace:^" "@standardnotes/domain-events": "workspace:*" "@standardnotes/domain-events-infra": "workspace:*"