Skip to content

Commit

Permalink
Add cookie chunker code (#653)
Browse files Browse the repository at this point in the history
* Add chunker code

* Remove unused constant

* Add changeset

* Store the regex in a constant

* Always return an array of objects from createChunks

* Remove array check as chunker always returns array

* Added vitest and tests for chunker

* Add chunkSize property to createChunks
  • Loading branch information
silentworks authored Oct 26, 2023
1 parent a2c0abc commit 9fa8f2b
Show file tree
Hide file tree
Showing 12 changed files with 460 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-dancers-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@supabase/auth-helpers-shared': minor
---

Add cookie chunking for large JWTs
2 changes: 1 addition & 1 deletion examples/sveltekit/src/routes/(app)/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const actions = {
}
});

if (!error) {
if (error) {
return fail(500, {
createMagicLink: {
values: {
Expand Down
2 changes: 1 addition & 1 deletion examples/sveltekit/src/routes/(app)/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
{/if}

{#if $page.url.searchParams.get('auth-type') === 'magiclink'}
<form action="/?/create-magiclink" method="post" use:enhance={handleSubmit}>
<form action="/?/send-magiclink" method="post" use:enhance={handleSubmit}>
<div class="field">
<label for="email" class="label">Email</label>
<p class="control">
Expand Down
13 changes: 2 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"build:ssr": "turbo run build --filter=@supabase/ssr",
"dev": "turbo run dev --parallel",
"lint": "turbo run lint --filter=!@example/*",
"test": "turbo run test",
"test:watch": "turbo run test:watch",
"check": "prettier --check .",
"format": "prettier --write .",
"docs": "typedoc",
Expand All @@ -40,17 +42,6 @@
"peerDependencies": {
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x"
},
"pnpm": {
"overrides": {
"@changesets/assemble-release-plan": "5.2.3"
},
"overrides-notes": {
"@changesets/assemble-release-plan": "patched until https://github.com/changesets/changesets/issues/835 is resolved"
},
"patchedDependencies": {
"@changesets/[email protected]": "patches/@[email protected]"
}
},
"lint-staged": {
"**/*.{ts,tsx,md}": [
"prettier --check"
Expand Down
7 changes: 5 additions & 2 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
],
"scripts": {
"lint": "tsc",
"build": "tsup"
"build": "tsup",
"test": "vitest run",
"test:watch": "vitest"
},
"repository": {
"type": "git",
Expand All @@ -38,7 +40,8 @@
"react": ">=17.0.2 <18.0.0 || >=18.0.0-0 <19.0.0",
"react-dom": "^17.0.2 || ^18.0.0-0",
"tsconfig": "workspace:*",
"tsup": "^6.7.0"
"tsup": "^6.7.0",
"vitest": "^0.34.6"
},
"dependencies": {
"jose": "^4.14.4"
Expand Down
57 changes: 57 additions & 0 deletions packages/shared/src/chunker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
interface Chunk {
name: string;
value: string;
}

function createChunkRegExp(chunkSize: number) {
return new RegExp('.{1,' + chunkSize + '}', 'g');
}

const MAX_CHUNK_SIZE = 3600;
const MAX_CHUNK_REGEXP = createChunkRegExp(MAX_CHUNK_SIZE);

/**
* create chunks from a string and return an array of object
*/
export function createChunks(key: string, value: string, chunkSize?: number): Chunk[] {
const re = chunkSize !== undefined ? createChunkRegExp(chunkSize) : MAX_CHUNK_REGEXP;

// check the length of the string to work out if it should be returned or chunked
const chunkCount = Math.ceil(value.length / MAX_CHUNK_SIZE);

if (chunkCount === 1) {
return [{ name: key, value }];
}

const chunks: Chunk[] = [];
// split string into a array based on the regex
const values = value.match(re);
values?.forEach((value, i) => {
const name: string = `${key}.${i}`;
chunks.push({ name, value });
});

return chunks;
}

// Get fully constructed chunks
export function combineChunk(
key: string,
retrieveChunk: (name: string) => string | null | undefined = () => {
return null;
}
) {
let values: string[] = [];
for (let i = 0; ; i++) {
const chunkName = `${key}.${i}`;
const chunk = retrieveChunk(chunkName);

if (!chunk) {
break;
}

values.push(chunk);
}

return values.length ? values.join('') : null;
}
43 changes: 37 additions & 6 deletions packages/shared/src/cookieAuthStorageAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { GoTrueClientOptions, Session } from '@supabase/supabase-js';
import { DEFAULT_COOKIE_OPTIONS, parseSupabaseCookie, stringifySupabaseSession } from './utils';
import { CookieOptions, DefaultCookieOptions } from './types';
import { combineChunk, createChunks } from './chunker';

export interface StorageAdapter extends Exclude<GoTrueClientOptions['storage'], undefined> {}

Expand All @@ -22,14 +23,20 @@ export abstract class CookieAuthStorageAdapter implements StorageAdapter {
getItem(key: string): string | Promise<string | null> | null {
const value = this.getCookie(key);

if (!value) return null;

// pkce code verifier
if (key.endsWith('-code-verifier')) {
if (key.endsWith('-code-verifier') && value) {
return value;
}

return JSON.stringify(parseSupabaseCookie(value));
if (value) {
return JSON.stringify(parseSupabaseCookie(value));
}

const chunks = combineChunk(key, (chunkName) => {
return this.getCookie(chunkName);
});

return chunks !== null ? JSON.stringify(parseSupabaseCookie(chunks)) : null;
}

setItem(key: string, value: string): void | Promise<void> {
Expand All @@ -42,10 +49,34 @@ export abstract class CookieAuthStorageAdapter implements StorageAdapter {
let session: Session = JSON.parse(value);
const sessionStr = stringifySupabaseSession(session);

this.setCookie(key, sessionStr);
// split session string before setting cookie
const sessionChunks = createChunks(key, sessionStr);

sessionChunks.forEach((sess) => {
this.setCookie(sess.name, sess.value);
});
}

removeItem(key: string): void | Promise<void> {
this.deleteCookie(key);
this._deleteSingleCookie(key);
this._deleteChunkedCookies(key);
}

private _deleteSingleCookie(key: string) {
if (this.getCookie(key)) {
this.deleteCookie(key);
}
}

private _deleteChunkedCookies(key: string, from = 0) {
for (let i = from; ; i++) {
const cookieName = `${key}.${i}`;
const value = this.getCookie(cookieName);

if (value === undefined) {
break;
}
this.deleteCookie(cookieName);
}
}
}
1 change: 1 addition & 0 deletions packages/shared/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { DefaultCookieOptions } from '../types';

export const DEFAULT_COOKIE_OPTIONS: DefaultCookieOptions = {
path: '/',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 365 * 1000
};
46 changes: 46 additions & 0 deletions packages/shared/tests/chunker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest';
import { combineChunk, createChunks } from '../src/chunker';
import { CHUNK_STRING } from './helper';

describe('chunker', () => {
it('should not chunk and return one item', () => {
const chunked = createChunks('my-chunks', 'hello-world');
expect(chunked.length).toBe(1);
});

it('should chunk and return two chunks', () => {
const chunked = createChunks('my-chunks', CHUNK_STRING, 2000);
const combined = combineChunk('my-chunks', (name) => {
let chunk = chunked.find((chunk) => {
return chunk.name === name;
});
return chunk?.value;
});
expect(chunked.length).toBe(2);
expect(combined).toBe(CHUNK_STRING);
});

it('should chunk and return twelve chunks', () => {
const chunked = createChunks('my-chunks', CHUNK_STRING, 320);
const combined = combineChunk('my-chunks', (name) => {
let chunk = chunked.find((chunk) => {
return chunk.name === name;
});
return chunk?.value;
});
expect(chunked.length).toBe(12);
expect(combined).toBe(CHUNK_STRING);
});

it('should chunk and return one hundred and one chunks', () => {
const chunked = createChunks('my-chunks', CHUNK_STRING, 36);
const combined = combineChunk('my-chunks', (name) => {
let chunk = chunked.find((chunk) => {
return chunk.name === name;
});
return chunk?.value;
});
expect(chunked.length).toBe(101);
expect(combined).toBe(CHUNK_STRING);
});
});
2 changes: 2 additions & 0 deletions packages/shared/tests/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CHUNK_STRING =
'zDq8KDAdv4PwF3UOp3mnEyx1xY71CaY4ZJdPTG8HpLHy3bCYs1x3vwPXdUqY75d0LYHL8KhxgrKqBEK531igiQNk1KqUKmMsabNlwcaF5E2gXA79vpwlxvi1wecwmKGVig4mJ0dzEEXKNsLgyQCsjKOpI7Nw2gnGAKKFdHle1SJeuFj9PyAHx9stMvQFpRQhoLRt3iI0uFA.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.lww9VHv5CCETYEFj9Q1emgiZI2nXZuvHbyJmIbYgMF9w533oG8ZEsdiKo81LAflQeUHYbbxOXTtroR2bdax0VYgjZ2qg5YeOnekqbgyDXwvmsOMktvg4x7JBJiHjzKK2kSPX0cCAhcU17ydRx19xq1gOUBup1j4kqEBbvKFKt67cfqZhT4kiERgbOt6mIyurSzUPgAhBNiqUJPM3872jVtweoum6mAfSgnG4H1qs2oNZjrbGi6Xv1H5WPALAdNzDwca0evrbbufUCKDX0XO27UTAFh9k4UFf0Dk1pPgKhomuWsfsJAJDvo2ZimmkXrlUo8OfihbpGCLMbEDRpxyIcIGTUQ30WeCyaHo2ds2hs2sh';
Loading

0 comments on commit 9fa8f2b

Please sign in to comment.