This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import { GOOGLE_OAUTH_SCOPES } from '../src/services/google-sheets';
|
||||
import {
|
||||
buildGoogleAuthorizationUrl,
|
||||
googleConnectionMetadata,
|
||||
normaliseGoogleValues,
|
||||
oauthFlowMatches,
|
||||
oauthStateHash,
|
||||
parseBoundedGoogleRange,
|
||||
} from '../src/services/google-sheets';
|
||||
|
||||
describe('Google OAuth proof and redaction', () => {
|
||||
it('binds state and PKCE without putting the verifier in the authorization URL', () => {
|
||||
const state = 'state-secret';
|
||||
const verifier = 'verifier-secret';
|
||||
const url = new URL(buildGoogleAuthorizationUrl({
|
||||
clientId: 'client-id',
|
||||
redirectUri: 'https://pig.example/oauth/google/callback',
|
||||
state,
|
||||
challenge: 'challenge',
|
||||
}));
|
||||
assert.equal(url.searchParams.get('state'), state);
|
||||
assert.equal(url.searchParams.get('code_challenge'), 'challenge');
|
||||
assert.equal(url.searchParams.get('code_challenge_method'), 'S256');
|
||||
assert.equal(url.searchParams.get('scope'), GOOGLE_OAUTH_SCOPES.join(' '));
|
||||
assert.equal(url.searchParams.get('access_type'), 'offline');
|
||||
assert.equal(url.toString().includes(verifier), false);
|
||||
});
|
||||
|
||||
it('accepts only the matching, unexpired, just-consumed state', () => {
|
||||
const now = new Date('2026-08-13T12:00:00.000Z');
|
||||
const flow = {
|
||||
stateHash: oauthStateHash('expected'),
|
||||
browserBindingHash: oauthStateHash('browser'),
|
||||
expiresAt: new Date('2026-08-13T12:01:00.000Z'),
|
||||
consumedAt: now,
|
||||
};
|
||||
assert.equal(oauthFlowMatches(flow, 'expected', 'browser', now), true);
|
||||
assert.equal(oauthFlowMatches(flow, 'attacker', 'browser', now), false);
|
||||
assert.equal(oauthFlowMatches(flow, 'expected', 'other-browser', now), false);
|
||||
assert.equal(oauthFlowMatches({ ...flow, consumedAt: null }, 'expected', 'browser', now), false);
|
||||
assert.equal(oauthFlowMatches({ ...flow, expiresAt: now }, 'expected', 'browser', now), false);
|
||||
});
|
||||
|
||||
it('never serializes encrypted tokens in connection metadata', () => {
|
||||
const metadata = googleConnectionMetadata(true, {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
refreshTokenEncrypted: 'v1.refresh.secret',
|
||||
accessTokenEncrypted: 'v1.access.secret',
|
||||
accessTokenExpiresAt: new Date(),
|
||||
scopes: [...GOOGLE_OAUTH_SCOPES],
|
||||
connectedAt: new Date('2026-08-13T12:00:00.000Z'),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
const serialized = JSON.stringify(metadata);
|
||||
assert.equal(serialized.includes('v1.refresh.secret'), false);
|
||||
assert.equal(serialized.includes('v1.access.secret'), false);
|
||||
assert.deepEqual(Object.keys(metadata), ['configured', 'connected', 'connectedAt', 'scopes']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Google Sheets range and value boundaries', () => {
|
||||
it('requires an explicit rectangular range within A14 limits and the selected grid', () => {
|
||||
assert.deepEqual(parseBoundedGoogleRange('a1:CV2001', { rowCount: 3_000, columnCount: 100 }), {
|
||||
a1: 'A1:CV2001',
|
||||
rows: 2_001,
|
||||
columns: 100,
|
||||
});
|
||||
assert.throws(() => parseBoundedGoogleRange('A:Z'));
|
||||
assert.throws(() => parseBoundedGoogleRange('A1:C2002'));
|
||||
assert.throws(() => parseBoundedGoogleRange('A1:C10', { rowCount: 9, columnCount: 3 }));
|
||||
});
|
||||
|
||||
it('normalizes formatted values into A14 rows while keeping text inert', () => {
|
||||
const table = normaliseGoogleValues([
|
||||
['external_id', 'name', 'active', 'score'],
|
||||
[7, '=IMPORTDATA("https://example.test")', true, 2.5],
|
||||
], 4);
|
||||
assert.deepEqual(table.headers, ['external_id', 'name', 'active', 'score']);
|
||||
assert.deepEqual(table.rows, [['7', '=IMPORTDATA("https://example.test")', 'true', '2.5']]);
|
||||
assert.match(table.warnings.join(' '), /formula source was not imported/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user