fix: allow custom cookie tld via env (COOKIE_TLDS)

This commit is contained in:
Carl-Gerhard Lindesvärd
2026-01-20 06:13:45 +01:00
parent 470ddbe8e7
commit 7cd5f84c58
3 changed files with 139 additions and 2 deletions

View File

@@ -175,6 +175,27 @@ COOKIE_SECRET=your-random-secret-here
Never use the default value in production! Always generate a unique secret.
</Callout>
### COOKIE_TLDS
**Type**: `string` (comma-separated)
**Required**: No
**Default**: None
Custom multi-part TLDs for cookie domain handling. Use this when deploying on domains with public suffixes that aren't recognized by default (e.g., `.my.id`, `.web.id`, `.co.id`).
**Example**:
```bash
# For domains like abc.my.id
COOKIE_TLDS=my.id
# Multiple TLDs
COOKIE_TLDS=my.id,web.id,co.id
```
<Callout>
This is required when using domain suffixes that are public suffixes (like `.co.uk`). Without this, the browser will reject authentication cookies. Common examples include Indonesian domains (`.my.id`, `.web.id`, `.co.id`).
</Callout>
### DEMO_USER_ID
**Type**: `string`

View File

@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { parseCookieDomain } from './parse-cookie-domain';
describe('parseCookieDomain', () => {
@@ -399,4 +399,100 @@ describe('parseCookieDomain', () => {
});
});
});
describe('custom multi-part TLDs via COOKIE_TLDS', () => {
const originalEnv = process.env.COOKIE_TLDS;
beforeEach(() => {
// Reset the environment variable before each test
delete process.env.COOKIE_TLDS;
});
afterEach(() => {
// Restore original value
if (originalEnv !== undefined) {
process.env.COOKIE_TLDS = originalEnv;
} else {
delete process.env.COOKIE_TLDS;
}
});
it('should handle my.id domains when COOKIE_TLDS includes my.id', () => {
process.env.COOKIE_TLDS = 'my.id';
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.abc.my.id',
secure: true,
});
});
it('should handle subdomains of my.id domains correctly', () => {
process.env.COOKIE_TLDS = 'my.id';
expect(parseCookieDomain('https://api.abc.my.id')).toEqual({
domain: '.abc.my.id',
secure: true,
});
});
it('should handle multiple custom TLDs', () => {
process.env.COOKIE_TLDS = 'my.id,web.id,co.id';
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.abc.my.id',
secure: true,
});
expect(parseCookieDomain('https://abc.web.id')).toEqual({
domain: '.abc.web.id',
secure: true,
});
expect(parseCookieDomain('https://abc.co.id')).toEqual({
domain: '.abc.co.id',
secure: true,
});
});
it('should handle custom TLDs with extra whitespace', () => {
process.env.COOKIE_TLDS = ' my.id , web.id ';
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.abc.my.id',
secure: true,
});
});
it('should handle case-insensitive custom TLDs', () => {
process.env.COOKIE_TLDS = 'MY.ID';
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.abc.my.id',
secure: true,
});
});
it('should not affect domains when env variable is empty', () => {
process.env.COOKIE_TLDS = '';
// Without the custom TLD, my.id is treated as a regular TLD
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.my.id',
secure: true,
});
});
it('should not affect domains when env variable is not set', () => {
delete process.env.COOKIE_TLDS;
// Without the custom TLD, my.id is treated as a regular TLD
expect(parseCookieDomain('https://abc.my.id')).toEqual({
domain: '.my.id',
secure: true,
});
});
it('should still work with built-in multi-part TLDs when custom TLDs are set', () => {
process.env.COOKIE_TLDS = 'my.id';
// Built-in TLDs should still work
expect(parseCookieDomain('https://example.co.uk')).toEqual({
domain: '.example.co.uk',
secure: true,
});
});
});
});

View File

@@ -12,6 +12,26 @@ const MULTI_PART_TLDS = [
/go\.\w{2}$/,
];
function getCustomMultiPartTLDs(): string[] {
const envValue = process.env.COOKIE_TLDS || '';
if (!envValue.trim()) {
return [];
}
return envValue
.split(',')
.map((tld) => tld.trim().toLowerCase())
.filter((tld) => tld.length > 0);
}
function isMultiPartTLD(potentialTLD: string): boolean {
if (MULTI_PART_TLDS.some((pattern) => pattern.test(potentialTLD))) {
return true;
}
const customTLDs = getCustomMultiPartTLDs();
return customTLDs.includes(potentialTLD.toLowerCase());
}
export const parseCookieDomain = (url: string) => {
if (!url) {
return {
@@ -36,7 +56,7 @@ export const parseCookieDomain = (url: string) => {
// Handle multi-part TLDs like co.uk, com.au, etc.
if (parts.length >= 3) {
const potentialTLD = parts.slice(-2).join('.');
if (MULTI_PART_TLDS.some((tld) => tld.test(potentialTLD))) {
if (isMultiPartTLD(potentialTLD)) {
// For domains like example.co.uk or subdomain.example.co.uk
// Use the last 3 parts: .example.co.uk
return {