Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 263 additions & 0 deletions spec/HeaderAliasesValidation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
'use strict';

const Config = require('../lib/Config');

describe('Config.validateHeaderAliases', () => {
it('should accept null and undefined', () => {
expect(() => Config.validateHeaderAliases(null)).not.toThrow();
expect(() => Config.validateHeaderAliases(undefined)).not.toThrow();
});

it('should accept a valid headerAliases object', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X-App-Id'],
'X-Parse-Session-Token': ['X-Session-Token-Alias'],
})
).not.toThrow();
});

it('should accept an empty aliases array for an allowlisted canonical header', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [],
})
).not.toThrow();
});

it('should accept aliases with trimable surrounding whitespace', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [' X-App-Id '],
})
).not.toThrow();
});

it('should accept differently cased allowlisted canonical headers', () => {
expect(() =>
Config.validateHeaderAliases({
'x-parse-application-id': ['X-App-Id'],
})
).not.toThrow();
});

it('should reject a non-object headerAliases value', () => {
expect(() => Config.validateHeaderAliases([])).toThrow('Header aliases must be an object');
expect(() => Config.validateHeaderAliases('bad')).toThrow('Header aliases must be an object');
expect(() => Config.validateHeaderAliases(1)).toThrow('Header aliases must be an object');
});

it('should reject empty or whitespace-only canonical keys', () => {
expect(() => Config.validateHeaderAliases({ '': ['X-App-Id'] })).toThrow(
'Header aliases must contain non-empty string keys'
);
expect(() => Config.validateHeaderAliases({ ' ': ['X-App-Id'] })).toThrow(
'Header aliases must contain non-empty string keys'
);
});

it('should reject non-array aliases', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': 'X-App-Id',
})
).toThrow("Header aliases for 'X-Parse-Application-Id' must be an array");
});

it('should reject non-string aliases', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [1],
})
).toThrow("Header aliases for 'X-Parse-Application-Id' must only contain strings");
});

it('should reject empty string aliases', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [' '],
})
).toThrow("Header aliases for 'X-Parse-Application-Id' must not contain empty strings");
});

it('should reject an alias that contains CRLF characters', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X-Foo\r\nSet-Cookie: evil'],
})
).toThrowError(/contains invalid characters/);
});

it('should reject an alias that contains a colon', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X-Foo: bar'],
})
).toThrowError(/contains invalid characters/);
});

it('should reject an alias that contains an internal space', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X Foo'],
})
).toThrowError(/contains invalid characters/);
});

it('should reject an alias that contains characters outside A-Za-z0-9-', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X-App/Id'],
})
).toThrowError(/contains invalid characters/);
});

it('should reject CORS-safelisted request-header names and Range as aliases', () => {
for (const alias of [
'Accept',
'accept-language',
'Content-Language',
'Content-Type',
'Range',
]) {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [alias],
})
).toThrowError(/CORS-safelisted request header/);
}
});

it('should reject browser-generated headers as aliases', () => {
for (const alias of ['Origin', 'Cookie', 'Referer', 'User-Agent']) {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [alias],
})
).toThrowError(/browser-controlled, or CORS-safelisted request header/);
}
});

it('should reject reserved sec- and proxy- prefix aliases', () => {
for (const alias of ['Sec-Fetch-Site', 'Proxy-Authorization']) {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [alias],
})
).toThrowError(/reserved, browser-controlled, or CORS-safelisted request header/);
}
});

it('should reject a canonical header that contains invalid characters', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id:': ['X-App-Id'],
})
).toThrowError(/contains invalid characters/);
});

it('should reject canonical headers that are not allowlisted Parse headers', () => {
expect(() =>
Config.validateHeaderAliases({
Authorization: ['X-Auth-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
expect(() =>
Config.validateHeaderAliases({
Host: ['X-Host-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
expect(() =>
Config.validateHeaderAliases({
Cookie: ['X-Cookie-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
expect(() =>
Config.validateHeaderAliases({
'X-Custom': ['X-Custom-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
});

it('should reject credential-bearing master and maintenance key aliases', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Master-Key': ['X-Master-Key-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Maintenance-Key': ['X-Maintenance-Key-Alias'],
})
).toThrowError(/is not an allowed Parse header/);
});

it('should reject an alias that normalizes to the same value as its canonical header', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['x-parse-application-id'],
})
).toThrowError(/must not normalize to the same value as the canonical header name/);
});

it('should reject duplicate normalized aliases within the same aliases array', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['foo', 'FOO'],
})
).toThrowError(/Duplicate normalized header alias/);
});

it('should reject two canonical keys that normalize to the same value', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': [],
'x-parse-application-id': [],
})
).toThrowError(/collides with.*after trim and lowercasing/);
});

it('should reject the same normalized alias used for two different canonical headers', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['shared-alias'],
'X-Parse-Session-Token': ['Shared-Alias'],
})
).toThrowError(/collides with alias/);
});

it('should reject an alias that normalizes to another canonical header key', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Session-Token': [],
'X-Parse-Application-Id': ['x-parse-session-token'],
})
).toThrowError(/collides with canonical header/);
});

it('should reject an alias that is another allowlisted Parse header even if that header is not in the mapping', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['x-parse-session-token'],
})
).toThrowError(/collides with canonical header/);
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Session-Token': ['X-Parse-Installation-Id'],
})
).toThrowError(/collides with canonical header/);
});

it('should reject an alias that is a credential-bearing Parse header', () => {
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Application-Id': ['X-Parse-Master-Key'],
})
).toThrowError(/collides with canonical header/);
expect(() =>
Config.validateHeaderAliases({
'X-Parse-Session-Token': ['x-parse-maintenance-key'],
})
).toThrowError(/collides with canonical header/);
});
});
Loading
Loading