diff --git a/packages/app-server/src/modules/app/config/config.ts b/packages/app-server/src/modules/app/config/config.ts index 63da50c3..33276dcd 100644 --- a/packages/app-server/src/modules/app/config/config.ts +++ b/packages/app-server/src/modules/app/config/config.ts @@ -19,7 +19,7 @@ export const configDefinition = { routeTimeoutMs: { doc: 'The maximum time in milliseconds for a route to complete before timing out', schema: z.coerce.number().int().positive(), - default: 5_000, + default: 30_000, // Increased from 5s to 30s to accommodate larger file uploads env: 'SERVER_API_ROUTES_TIMEOUT_MS', }, corsOrigins: { diff --git a/packages/docs/src/index.md b/packages/docs/src/index.md index 22c42dec..ae8b195d 100644 --- a/packages/docs/src/index.md +++ b/packages/docs/src/index.md @@ -33,3 +33,11 @@ By leveraging client-side encryption and a zero-knowledge server, Enclosed guara ## Get Started Ready to start using Enclosed? You can [try it out online](https://enclosed.cc) or [self-host](./self-hosting/docker) your instance for maximum control. Dive into our documentation to learn more about how Enclosed works and how you can take full advantage of its features. + +## Disclaimer + +**Enclosed is provided "as is", without warranty of any kind, express or implied.** The creators and contributors of Enclosed are not responsible for the content of any notes created or shared using the service, or for any actions taken by users based on such content. Users are solely responsible for their use of the service and any content they create, share, or access. + +If you choose to self-host an instance of Enclosed, you do so at your own risk. The creators and contributors are not responsible for any issues, security breaches, or other problems that may arise from self-hosting. + +For more detailed information, please review our [Privacy Policy](./legal/privacy-policy.md) and [Terms of Use](./legal/terms-of-use.md). diff --git a/packages/docs/src/integrations/npm-package.md b/packages/docs/src/integrations/npm-package.md index 0ce73efd..a61c9780 100644 --- a/packages/docs/src/integrations/npm-package.md +++ b/packages/docs/src/integrations/npm-package.md @@ -1,29 +1,517 @@ # NPM Package -::: warning -This page is a work in progress. -::: - -The Enclosed lib is available as an npm package. You can use it to create and manage Enclosed notes. +The Enclosed library is available as an npm package. It provides a simple and secure way to create, manage, and access encrypted notes with optional password protection, expiration times, and file attachments. ## Installation -You can install the Enclosed Lib using npm, yarn, or pnpm. +You can install the Enclosed library using npm, yarn, or pnpm. ### Using npm ```bash -npm install -g @enclosed/lib +npm install @enclosed/lib ``` ### Using yarn ```bash -yarn global add @enclosed/lib +yarn add @enclosed/lib ``` ### Using pnpm ```bash -pnpm add -g @enclosed/lib +pnpm add @enclosed/lib +``` + +## Basic Usage + +### Creating a Note + +```typescript +import { createNote } from '@enclosed/lib'; + +async function createSimpleNote() { + const { noteUrl } = await createNote({ + content: 'This is a secret note', + }); + + console.log(`Your note is available at: ${noteUrl}`); +} +``` + +### Reading a Note + +```typescript +import { decryptNote } from '@enclosed/lib'; + +async function readNote(noteUrl, password) { + // Parse the URL to extract noteId and encryptionKey + const { noteId, encryptionKey } = parseNoteUrl({ noteUrl }); + + // Fetch the encrypted note from the server + const { payload } = await fetchNote({ noteId }); + + // Decrypt the note + const { note } = await decryptNote({ + encryptedPayload: payload, + encryptionKey, + password, // Optional, only needed if the note is password-protected + }); + + console.log('Note content:', note.content); + + // If the note has file attachments + if (note.assets.length > 0) { + console.log('Note has file attachments:', note.assets.length); + } +} +``` + +## API Reference + +### createNote + +Creates an encrypted note and stores it on the server. + +```typescript +async function createNote({ + content, + password, + ttlInSeconds, + deleteAfterReading = false, + clientBaseUrl = 'https://enclosed.cc', + apiBaseUrl = clientBaseUrl, + storeNote = params => storeNoteImpl({ ...params, apiBaseUrl }), + assets = [], + encryptionAlgorithm = 'aes-256-gcm', + serializationFormat = 'cbor-array', + isPublic = true, + pathPrefix, +}: { + content: string; + password?: string; + ttlInSeconds?: number; + deleteAfterReading?: boolean; + clientBaseUrl?: string; + apiBaseUrl?: string; + assets?: NoteAsset[]; + encryptionAlgorithm?: EncryptionAlgorithm; + serializationFormat?: SerializationFormat; + isPublic?: boolean; + pathPrefix?: string; + storeNote?: (params: { + payload: string; + ttlInSeconds?: number; + deleteAfterReading: boolean; + encryptionAlgorithm: EncryptionAlgorithm; + serializationFormat: SerializationFormat; + isPublic?: boolean; + }) => Promise<{ noteId: string }>; +}): Promise<{ + encryptedPayload: string; + encryptionKey: string; + noteId: string; + noteUrl: string; +}> +``` + +#### Parameters + +- `content` (required): The content of the note as a string. +- `password` (optional): A password to protect the note. If provided, the password will be required to decrypt the note. +- `ttlInSeconds` (optional): Time-to-live in seconds. The note will be automatically deleted after this time. +- `deleteAfterReading` (optional, default: `false`): If `true`, the note will be deleted after it's read once. +- `clientBaseUrl` (optional, default: `'https://enclosed.cc'`): The base URL for the client. +- `apiBaseUrl` (optional, default: `clientBaseUrl`): The base URL for the API. +- `assets` (optional, default: `[]`): An array of file assets to attach to the note. +- `encryptionAlgorithm` (optional, default: `'aes-256-gcm'`): The encryption algorithm to use. +- `serializationFormat` (optional, default: `'cbor-array'`): The serialization format to use. +- `isPublic` (optional, default: `true`): If `true`, the note is publicly accessible with the correct URL. +- `pathPrefix` (optional): A prefix for the note URL path. +- `storeNote` (optional): A custom function to store the note. By default, it uses the built-in `storeNote` function. + +#### Returns + +- `encryptedPayload`: The encrypted note payload. +- `encryptionKey`: The encryption key used to encrypt the note. +- `noteId`: The ID of the created note. +- `noteUrl`: The URL to access the note. + +### decryptNote + +Decrypts an encrypted note. + +```typescript +async function decryptNote({ + encryptedPayload, + password, + encryptionKey, + serializationFormat = 'cbor-array', + encryptionAlgorithm = 'aes-256-gcm', +}: { + encryptedPayload: string; + password?: string; + encryptionKey: string; + serializationFormat?: SerializationFormat; + encryptionAlgorithm?: EncryptionAlgorithm; +}): Promise<{ + note: Note; +}> +``` + +#### Parameters + +- `encryptedPayload` (required): The encrypted note payload. +- `password` (optional): The password used to encrypt the note, if any. +- `encryptionKey` (required): The encryption key used to encrypt the note. +- `serializationFormat` (optional, default: `'cbor-array'`): The serialization format used. +- `encryptionAlgorithm` (optional, default: `'aes-256-gcm'`): The encryption algorithm used. + +#### Returns + +- `note`: The decrypted note object containing the content and any attached assets. + +### fetchNote + +Fetches an encrypted note from the server. + +```typescript +async function fetchNote({ + noteId, + apiBaseUrl, +}: { + noteId: string; + apiBaseUrl?: string; +}): Promise<{ + payload: string; +}> +``` + +#### Parameters + +- `noteId` (required): The ID of the note to fetch. +- `apiBaseUrl` (optional): The base URL for the API. + +#### Returns + +- An object containing the encrypted note payload. + +### URL Handling Functions + +#### createNoteUrl + +Creates a URL for accessing a note. + +```typescript +function createNoteUrl({ + noteId, + encryptionKey, + clientBaseUrl, + isPasswordProtected, + isDeletedAfterReading, + pathPrefix, +}: { + noteId: string; + encryptionKey: string; + clientBaseUrl: string; + isPasswordProtected?: boolean; + isDeletedAfterReading?: boolean; + pathPrefix?: string; +}): { noteUrl: string } +``` + +#### parseNoteUrl + +Parses a note URL to extract the noteId, encryptionKey, and other information. + +```typescript +function parseNoteUrl({ noteUrl }: { noteUrl: string }): { + noteId: string; + encryptionKey: string; + isPasswordProtected: boolean; + isDeletedAfterReading: boolean; +} +``` + +### File Handling Functions + +#### fileToNoteAsset + +Converts a File object to a NoteAsset. + +```typescript +async function fileToNoteAsset({ file }: { file: File }): Promise +``` + +#### filesToNoteAssets + +Converts an array of File objects to an array of NoteAssets. + +```typescript +async function filesToNoteAssets({ files }: { files: File[] }): Promise +``` + +#### noteAssetToFile + +Converts a NoteAsset to a File object. + +```typescript +async function noteAssetToFile({ noteAsset }: { noteAsset: NoteAsset }): Promise +``` + +#### noteAssetsToFiles + +Converts an array of NoteAssets to an array of File objects. + +```typescript +async function noteAssetsToFiles({ noteAssets }: { noteAssets: NoteAsset[] }): Promise +``` + +## TypeScript Types and Interfaces + +The library exports several TypeScript types and interfaces that you can use in your code. + +### Note Types + +```typescript +// Represents a file or other binary asset attached to a note +type NoteAsset = { + metadata: { + type: string; + [key: string]: unknown; + }; + content: Uint8Array; +}; + +// Represents a decrypted note +type Note = { + content: string; + assets: NoteAsset[]; +}; + +// Represents an encrypted note +type EncryptedNote = { + version: number; + payload: string; + encryptionAlgorithm: EncryptionAlgorithm; + serializationFormat: SerializationFormat; + keyDerivationAlgorithm: KeyDerivationAlgorithm; + compressionAlgorithm: CompressionAlgorithm; + ttlInSeconds: number; + deleteAfterReading: boolean; +}; +``` + +### Encryption and Serialization Types + +```typescript +// Available encryption algorithms +type EncryptionAlgorithm = 'aes-256-gcm'; + +// Available key derivation algorithms +type KeyDerivationAlgorithm = 'pbkdf2-base-key-salted'; + +// Available compression algorithms +type CompressionAlgorithm = 'brotli' | 'none'; + +// Available serialization formats +type SerializationFormat = 'cbor-array'; +``` + +## Advanced Usage + +### Creating a Password-Protected Note + +```typescript +import { createNote } from '@enclosed/lib'; + +async function createPasswordProtectedNote() { + const { noteUrl } = await createNote({ + content: 'This is a password-protected note', + password: 'my-secure-password', + }); + + console.log(`Your password-protected note is available at: ${noteUrl}`); +} +``` + +### Creating a Note with Expiration + +```typescript +import { createNote } from '@enclosed/lib'; + +async function createExpiringNote() { + // Create a note that expires after 1 hour (3600 seconds) + const { noteUrl } = await createNote({ + content: 'This note will expire after 1 hour', + ttlInSeconds: 3600, + }); + + console.log(`Your expiring note is available at: ${noteUrl}`); +} +``` + +### Creating a Note that Deletes After Reading + +```typescript +import { createNote } from '@enclosed/lib'; + +async function createSelfDestructingNote() { + const { noteUrl } = await createNote({ + content: 'This note will self-destruct after reading', + deleteAfterReading: true, + }); + + console.log(`Your self-destructing note is available at: ${noteUrl}`); +} +``` + +### Creating a Note with File Attachments + +```typescript +import { createNote, filesToNoteAssets } from '@enclosed/lib'; + +async function createNoteWithAttachments() { + // Assuming you have File objects from a file input or other source + const files = [ + new File(['file content'], 'document.txt', { type: 'text/plain' }), + // Add more files as needed + ]; + + // Convert files to note assets + const assets = await filesToNoteAssets({ files }); + + const { noteUrl } = await createNote({ + content: 'This note has file attachments', + assets, + }); + + console.log(`Your note with attachments is available at: ${noteUrl}`); +} +``` + +### Using a Custom API Endpoint + +```typescript +import { createNote } from '@enclosed/lib'; + +async function createNoteWithCustomEndpoint() { + const { noteUrl } = await createNote({ + content: 'This note uses a custom API endpoint', + apiBaseUrl: 'https://my-custom-enclosed-instance.com', + clientBaseUrl: 'https://my-custom-enclosed-instance.com', + }); + + console.log(`Your note is available at: ${noteUrl}`); +} ``` + +## Error Handling + +The library throws errors in various situations. Here's how to handle them: + +```typescript +import { createNote, isApiClientErrorWithCode, isApiClientErrorWithStatusCode } from '@enclosed/lib'; + +async function createNoteWithErrorHandling() { + try { + const { noteUrl } = await createNote({ + content: 'This is a note with error handling', + }); + + console.log(`Your note is available at: ${noteUrl}`); + } catch (error) { + // Check if it's a rate limit error + if (isApiClientErrorWithStatusCode({ error, statusCode: 429 })) { + console.error('Rate limit exceeded. Try again later.'); + return; + } + + // Check if it's a specific API error + if (isApiClientErrorWithCode({ error, code: 'NOTE_TOO_LARGE' })) { + console.error('The note is too large. Please reduce its size.'); + return; + } + + // Handle other errors + console.error('An error occurred:', error); + } +} +``` + +## Complete Examples + +### Creating and Reading a Note + +```typescript +import { createNote, decryptNote, fetchNote, parseNoteUrl } from '@enclosed/lib'; + +async function createAndReadNote() { + // Create a note + const { noteUrl } = await createNote({ + content: 'This is a complete example note', + }); + + console.log(`Note created at: ${noteUrl}`); + + // Parse the URL to extract noteId and encryptionKey + const { noteId, encryptionKey } = parseNoteUrl({ noteUrl }); + + // Fetch the encrypted note from the server + const note = await fetchNote({ noteId }); + + // Decrypt the note + const { note: decryptedNote } = await decryptNote({ + encryptedPayload: note.payload, + encryptionKey, + }); + + console.log('Decrypted note content:', decryptedNote.content); +} +``` + +### Creating and Reading a Password-Protected Note with File Attachments + +```typescript +import { createNote, decryptNote, fetchNote, parseNoteUrl, filesToNoteAssets, noteAssetsToFiles } from '@enclosed/lib'; + +async function createAndReadComplexNote() { + // Prepare file attachments + const files = [ + new File(['file content'], 'document.txt', { type: 'text/plain' }), + ]; + + const assets = await filesToNoteAssets({ files }); + + // Create a password-protected note with attachments that expires after 1 day + const { noteUrl } = await createNote({ + content: 'This is a complex note example', + password: 'secure-password', + assets, + ttlInSeconds: 86400, // 1 day + }); + + console.log(`Complex note created at: ${noteUrl}`); + + // Parse the URL to extract noteId and encryptionKey + const { noteId, encryptionKey, isPasswordProtected } = parseNoteUrl({ noteUrl }); + + // Fetch the encrypted note from the server + const note = await fetchNote({ noteId }); + + // Decrypt the note with password + const { note: decryptedNote } = await decryptNote({ + encryptedPayload: note.payload, + encryptionKey, + password: isPasswordProtected ? 'secure-password' : undefined, + }); + + console.log('Decrypted note content:', decryptedNote.content); + + // Handle file attachments + if (decryptedNote.assets.length > 0) { + const attachedFiles = await noteAssetsToFiles({ noteAssets: decryptedNote.assets }); + console.log('Attached files:', attachedFiles.map(file => file.name)); + } +} diff --git a/packages/docs/src/legal/privacy-policy.md b/packages/docs/src/legal/privacy-policy.md new file mode 100644 index 00000000..05687ff2 --- /dev/null +++ b/packages/docs/src/legal/privacy-policy.md @@ -0,0 +1,60 @@ +# Privacy Policy + +Last Updated: May 1, 2025 + +## Introduction + +Welcome to Enclosed. We respect your privacy and are committed to protecting your personal data. This Privacy Policy explains how we collect, use, and safeguard your information when you use our service. + +Enclosed is designed with privacy as a core principle. Our application uses end-to-end encryption, meaning that the content of your notes is encrypted on your device before being sent to our servers, and can only be decrypted by someone with the correct link and password (if set). + +## Information We Collect + +### Information You Provide + +- **Note Content**: The content of your notes is encrypted on your device before being sent to our servers. We cannot access the unencrypted content of your notes. +- **File Attachments**: Any files you attach to notes are also encrypted on your device before being sent to our servers. +- **Authentication Information**: If you choose to create an account, we collect your email address and a hashed version of your password. + +### Information Collected Automatically + +- **Usage Data**: We collect information about how you interact with our service, such as access dates and times, pages viewed, and the features you use. +- **Device Information**: We collect information about the device you use to access our service, including IP address, browser type, and operating system. + +## How We Use Your Information + +We use the information we collect to: + +- Provide, maintain, and improve our service +- Detect, prevent, and address technical issues +- Monitor the usage of our service +- Protect against unauthorized access to our servers + +## Data Storage and Security + +- **Note Content and Attachments**: These are stored in an encrypted format on our servers and are automatically deleted after the expiration period you set, or after being read (if you selected that option). +- **Authentication Information**: If you create an account, your email and password hash are stored securely. + +We implement appropriate technical and organizational measures to protect your data against unauthorized access, alteration, disclosure, or destruction. + +## Your Rights + +Depending on your location, you may have certain rights regarding your personal information, such as: + +- The right to access the personal information we hold about you +- The right to request correction or deletion of your personal information +- The right to restrict or object to our processing of your personal information +- The right to data portability + +To exercise these rights, please contact us using the information provided below. + +## Changes to This Privacy Policy + +We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last Updated" date. + +## Contact Us + +If you have any questions about this Privacy Policy, please contact us: + +- By email: [contact@enclosed.cc](mailto:contact@enclosed.cc) +- By visiting our GitHub repository: [https://github.com/CorentinTh/enclosed](https://github.com/CorentinTh/enclosed) \ No newline at end of file diff --git a/packages/docs/src/legal/terms-of-use.md b/packages/docs/src/legal/terms-of-use.md new file mode 100644 index 00000000..e69de29b