-
-
Notifications
You must be signed in to change notification settings - Fork 144
feat(server): helpers #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
03a8ec6
setCookie & getCookie
dinwwwh cc1bee8
base64url + encryption + signing
dinwwwh 71b9fe4
improve
dinwwwh dba767b
docs
dinwwwh f2a55b2
Merge branch 'main' into feat/server/cookie-helpers
dinwwwh 9617eb2
docs
dinwwwh 283313b
tests
dinwwwh 1987236
improve security
dinwwwh 0d98757
improve
dinwwwh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| --- | ||
| title: Base64Url Helpers | ||
| description: Functions to encode and decode base64url strings, a URL-safe variant of base64 encoding. | ||
| --- | ||
|
|
||
| # Base64Url Helpers | ||
|
|
||
| Base64Url helpers provide functions to encode and decode base64url strings, a URL-safe variant of base64 encoding used in web tokens, data serialization, and APIs. | ||
|
|
||
| ```ts twoslash | ||
| import { decodeBase64url, encodeBase64url } from '@orpc/server/helpers' | ||
|
|
||
| const originalText = 'Hello World' | ||
| const textBytes = new TextEncoder().encode(originalText) | ||
| const encodedData = encodeBase64url(textBytes) | ||
| const decodedBytes = decodeBase64url(encodedData) | ||
| const decodedText = new TextDecoder().decode(decodedBytes) // 'Hello World' | ||
| ``` | ||
|
|
||
| ::: info | ||
| The `decodeBase64url` accepts `undefined` or `null` as encoded value and returns `undefined` for invalid inputs, enabling seamless handling of optional data. | ||
| ::: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| --- | ||
| title: Cookie Helpers | ||
| description: Functions for managing HTTP cookies in web applications. | ||
| --- | ||
|
|
||
| # Cookie Helpers | ||
|
|
||
| The Cookie helpers provide functions to set and get HTTP cookies. | ||
|
|
||
| ```ts twoslash | ||
| import { getCookie, setCookie } from '@orpc/server/helpers' | ||
|
|
||
| const headers = new Headers() | ||
|
|
||
| setCookie(headers, 'sessionId', 'abc123', { | ||
| secure: true, | ||
| maxAge: 3600 | ||
| }) | ||
|
|
||
| const sessionId = getCookie(headers, 'sessionId') // 'abc123' | ||
| ``` | ||
|
|
||
| ::: info | ||
| Both helpers accept `undefined` as headers for seamless integration with plugins like [Request Headers](/docs/plugins/request-headers) or [Response Headers](/docs/plugins/response-headers). | ||
| ::: | ||
|
|
||
| ## Security with Signing and Encryption | ||
|
|
||
| Combine cookies with [signing](/docs/helpers/signing) or [encryption](/docs/helpers/encryption) for enhanced security: | ||
|
|
||
| ```ts twoslash | ||
| import { getCookie, setCookie, sign, unsign } from '@orpc/server/helpers' | ||
|
|
||
| const secret = 'your-secret-key' | ||
|
|
||
| const headers = new Headers() | ||
|
|
||
| setCookie(headers, 'sessionId', await sign('abc123', secret), { | ||
| httpOnly: true, | ||
| secure: true, | ||
| maxAge: 3600 | ||
| }) | ||
|
|
||
| const signedSessionId = await unsign(getCookie(headers, 'sessionId'), secret) | ||
| // 'abc123' | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| --- | ||
| title: Encryption Helpers | ||
| description: Functions to encrypt and decrypt sensitive data using AES-GCM. | ||
| --- | ||
|
|
||
| # Encryption Helpers | ||
|
|
||
| Encryption helpers provide functions to encrypt and decrypt sensitive data using AES-GCM with PBKDF2 key derivation. | ||
|
|
||
| ::: info | ||
| Encryption prevents users from reading data content but is slower than [signing](/docs/helpers/signing). | ||
| ::: | ||
|
|
||
| ```ts twoslash | ||
| import { decrypt, encrypt } from '@orpc/server/helpers' | ||
|
|
||
| const secret = 'your-encryption-key' | ||
| const sensitiveData = 'user-email@example.com' | ||
|
|
||
| const encryptedData = await encrypt(sensitiveData, secret) | ||
| // 'Rq7wF8...' (base64url encoded, unreadable) | ||
|
|
||
| const decryptedData = await decrypt(encryptedData, secret) | ||
| // 'user-email@example.com' | ||
| ``` | ||
|
|
||
| ::: info | ||
| The `decrypt` helper accepts `undefined` or `null` as encrypted value and returns `undefined` for invalid inputs, enabling seamless handling of optional data. | ||
| ::: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| --- | ||
| title: Signing Helpers | ||
| description: Functions to cryptographically sign and verify data using HMAC-SHA256. | ||
| --- | ||
|
|
||
| # Signing Helpers | ||
|
|
||
| Signing helpers provide functions to cryptographically sign and verify data using HMAC-SHA256. | ||
|
|
||
| ::: info | ||
| Signing is faster than [encryption](/docs/helpers/encryption) but users can view the original data. | ||
| ::: | ||
|
|
||
| ```ts twoslash | ||
| import { sign, unsign } from '@orpc/server/helpers' | ||
|
|
||
| const secret = 'your-secret-key' | ||
| const userData = 'user123' | ||
|
|
||
| const signedValue = await sign(userData, secret) | ||
| // 'user123.oneQsU0r5dvwQFHFEjjV1uOI_IR3gZfkYHij3TRauVA' | ||
| // ↑ Original data is visible to users | ||
|
|
||
| const verifiedValue = await unsign(signedValue, secret) // 'user123' | ||
| ``` | ||
|
|
||
| ::: info | ||
| The `unsign` helper accepts `undefined` or `null` as signed value and returns `undefined` for invalid inputs, enabling seamless handling of optional data. | ||
| ::: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import { decodeBase64url, encodeBase64url } from './base64url' | ||
|
|
||
| describe('encodeBase64url / decodeBase64url', () => { | ||
| it('should encode and decode Uint8Array correctly', () => { | ||
| const original = new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]) | ||
| const encoded = encodeBase64url(original) | ||
| const decoded = decodeBase64url(encoded) | ||
|
|
||
| expect(decoded).toEqual(original) | ||
| }) | ||
|
|
||
| it('should produce URL-safe output without padding', () => { | ||
| const data = new Uint8Array([255, 254, 253]) // Will produce +/= characters in regular base64 | ||
| const encoded = encodeBase64url(data) | ||
|
|
||
| expect(encoded).not.toMatch(/[+/=]/) | ||
| expect(encoded).toMatch(/^[\w-]+$/) | ||
| }) | ||
|
|
||
| it('should handle empty data', () => { | ||
| const empty = new Uint8Array([]) | ||
| const encoded = encodeBase64url(empty) | ||
| const decoded = decodeBase64url(encoded) | ||
|
|
||
| expect(encoded).toBe('') | ||
| expect(decoded).toEqual(empty) | ||
| }) | ||
|
|
||
| it('should work with TextEncoder/TextDecoder', () => { | ||
| const text = 'Hello World' | ||
| const encoded = encodeBase64url(new TextEncoder().encode(text)) | ||
| const decoded = decodeBase64url(encoded) | ||
|
|
||
| expect(new TextDecoder().decode(decoded)).toEqual(text) | ||
| }) | ||
|
|
||
| it('should handle large data without call stack overflow', () => { | ||
| // Create a large Uint8Array (100KB) | ||
| const largeData = new Uint8Array(100 * 1024) | ||
| for (let i = 0; i < largeData.length; i++) { | ||
| largeData[i] = i % 256 | ||
| } | ||
|
|
||
| const encoded = encodeBase64url(largeData) | ||
| const decoded = decodeBase64url(encoded) | ||
|
|
||
| expect(decoded).toEqual(largeData) | ||
| expect(encoded).not.toMatch(/[+/=]/) // Should still be URL-safe | ||
| }) | ||
|
|
||
| it('should handle invalid input gracefully', () => { | ||
| expect(decodeBase64url(null)).toBeUndefined() | ||
| expect(decodeBase64url(undefined)).toBeUndefined() | ||
| expect(decodeBase64url('invalid base64!')).toBeUndefined() | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /** | ||
| * Encodes a Uint8Array to base64url format | ||
| * Base64url is URL-safe and doesn't use padding | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const text = "Hello World" | ||
| * const encoded = encodeBase64url(new TextEncoder().encode(text)) | ||
| * const decoded = decodeBase64url(encoded) | ||
| * expect(new TextDecoder().decode(decoded)).toEqual(text) | ||
| * ``` | ||
| */ | ||
| export function encodeBase64url(data: Uint8Array): string { | ||
| const chunkSize = 8192 // 8KB chunks to stay well below call stack limits | ||
| let binaryString = '' | ||
|
|
||
| for (let i = 0; i < data.length; i += chunkSize) { | ||
| const chunk = data.subarray(i, i + chunkSize) | ||
| binaryString += String.fromCharCode(...chunk) | ||
| } | ||
|
|
||
| const base64 = btoa(binaryString) | ||
| return base64 | ||
| .replace(/\+/g, '-') | ||
| .replace(/\//g, '_') | ||
| .replace(/=/g, '') | ||
| } | ||
|
|
||
| /** | ||
| * Decodes a base64url string to Uint8Array | ||
| * Returns undefined if the input is invalid | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const text = "Hello World" | ||
| * const encoded = encodeBase64url(new TextEncoder().encode(text)) | ||
| * const decoded = decodeBase64url(encoded) | ||
| * expect(new TextDecoder().decode(decoded)).toEqual(text) | ||
| * ``` | ||
| */ | ||
| export function decodeBase64url(base64url: string | undefined | null): Uint8Array | undefined { | ||
| try { | ||
| if (typeof base64url !== 'string') { | ||
| return undefined | ||
| } | ||
|
|
||
| let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/') | ||
|
|
||
| while (base64.length % 4) { | ||
| base64 += '=' | ||
| } | ||
|
|
||
| const binaryString = atob(base64) | ||
|
|
||
| const bytes = new Uint8Array(binaryString.length) | ||
| for (let i = 0; i < binaryString.length; i++) { | ||
| bytes[i] = binaryString.charCodeAt(i) | ||
| } | ||
|
|
||
| return bytes | ||
| } | ||
| catch { | ||
| return undefined | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.