From 90e9d0a7cb9ceba4bd64ab66325dc4c0d5ad0661 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 04:13:20 +0900 Subject: [PATCH 1/3] test(calendar): require scoped credential materialization --- .../calendar-credential-materializer.test.ts | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 apps/integration-calendar-service/src/calendar-credential-materializer.test.ts diff --git a/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts b/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts new file mode 100644 index 000000000..26bfc0783 --- /dev/null +++ b/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { TrustedCalendarUserContext } from './calendar-service-context'; +import type { CalendarConnectionRecord } from './calendar-connection-repository'; +import { + CalendarCredentialMaterializationError, + CalendarCredentialMaterializer, +} from './calendar-credential-materializer'; + +const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111'; +const USER_ID = '22222222-2222-4222-8222-222222222222'; +const CONNECTION_ID = '33333333-3333-4333-8333-333333333333'; +const AUTHORITY: TrustedCalendarUserContext = Object.freeze({ + workspaceId: WORKSPACE_ID, + userId: USER_ID, +}); + +function connection( + overrides: Partial = {}, +): CalendarConnectionRecord { + return Object.freeze({ + connectionId: CONNECTION_ID, + workspaceId: WORKSPACE_ID, + userId: USER_ID, + providerCode: 'google', + providerAccountSubject: 'subject-1', + scopeValues: Object.freeze(['calendar.events']), + accessSecretHandle: 'kms://calendar/access-33333333', + refreshSecretHandle: 'kms://calendar/refresh-33333333', + tokenExpiresAt: '2026-08-12T12:00:00.000Z', + selectedCalendarIdentifier: 'primary', + status: 'active', + createdAt: '2026-08-12T10:00:00.000Z', + updatedAt: '2026-08-12T10:00:00.000Z', + revokedAt: null, + ...overrides, + }); +} + +function materializer(record: CalendarConnectionRecord | undefined = connection()) { + const getActiveConnection = vi.fn().mockResolvedValue(record); + const readSecret = vi.fn(async (handle: string) => + handle.includes('/refresh-') ? 'refresh-token-value' : 'access-token-value', + ); + return { + getActiveConnection, + readSecret, + subject: new CalendarCredentialMaterializer( + { getActiveConnection }, + { readSecret }, + ), + }; +} + +async function expectMaterializationFailure( + operation: Promise, +): Promise { + await expect(operation).rejects.toBeInstanceOf( + CalendarCredentialMaterializationError, + ); +} + +describe('CalendarCredentialMaterializer', () => { + it('materializes only the exact active connection inside trusted user authority', async () => { + const fixture = materializer(); + + await expect( + fixture.subject.materialize(AUTHORITY, CONNECTION_ID), + ).resolves.toEqual({ + connectionId: CONNECTION_ID, + providerCode: 'google', + accessToken: 'access-token-value', + refreshToken: 'refresh-token-value', + tokenExpiresAt: '2026-08-12T12:00:00.000Z', + selectedCalendarIdentifier: 'primary', + }); + expect(fixture.getActiveConnection).toHaveBeenCalledWith({ + connectionId: CONNECTION_ID, + workspaceId: WORKSPACE_ID, + userId: USER_ID, + }); + expect(fixture.readSecret.mock.calls.map(([handle]) => handle)).toEqual([ + 'kms://calendar/access-33333333', + 'kms://calendar/refresh-33333333', + ]); + }); + + it('fails closed when the connection does not exist and never materializes a secret', async () => { + const fixture = materializer(undefined); + + await expectMaterializationFailure( + fixture.subject.materialize(AUTHORITY, CONNECTION_ID), + ); + expect(fixture.readSecret).not.toHaveBeenCalled(); + }); + + it('rejects persistence evidence from another workspace or user before secret access', async () => { + for (const record of [ + connection({ workspaceId: '44444444-4444-4444-8444-444444444444' }), + connection({ userId: '55555555-5555-4555-8555-555555555555' }), + connection({ connectionId: '66666666-6666-4666-8666-666666666666' }), + connection({ status: 'revoked', revokedAt: '2026-08-12T11:00:00.000Z' }), + ]) { + const fixture = materializer(record); + await expectMaterializationFailure( + fixture.subject.materialize(AUTHORITY, CONNECTION_ID), + ); + expect(fixture.readSecret).not.toHaveBeenCalled(); + } + }); + + it('fails closed when secret materialization is unavailable without exposing provider errors', async () => { + const getActiveConnection = vi.fn().mockResolvedValue(connection()); + const readSecret = vi.fn().mockRejectedValue(new Error('kms token leaked-value')); + const subject = new CalendarCredentialMaterializer( + { getActiveConnection }, + { readSecret }, + ); + + let thrown: unknown; + try { + await subject.materialize(AUTHORITY, CONNECTION_ID); + } catch (error) { + thrown = error; + } + expect(thrown).toBeInstanceOf(CalendarCredentialMaterializationError); + expect((thrown as Error).message).not.toContain('leaked-value'); + }); + + it('rejects empty, oversized, or control-character secret values', async () => { + for (const secretValue of ['', 'a'.repeat(16_385), 'token\nvalue']) { + const getActiveConnection = vi.fn().mockResolvedValue(connection({ + refreshSecretHandle: null, + })); + const readSecret = vi.fn().mockResolvedValue(secretValue); + const subject = new CalendarCredentialMaterializer( + { getActiveConnection }, + { readSecret }, + ); + + await expectMaterializationFailure( + subject.materialize(AUTHORITY, CONNECTION_ID), + ); + } + }); + + it('supports access-token-only providers without inventing refresh authority', async () => { + const fixture = materializer(connection({ refreshSecretHandle: null })); + + await expect( + fixture.subject.materialize(AUTHORITY, CONNECTION_ID), + ).resolves.toMatchObject({ refreshToken: null }); + expect(fixture.readSecret).toHaveBeenCalledTimes(1); + }); +}); From e7eb6fab5f09bf9f0230162d2e54a7eebc1c2931 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 04:13:51 +0900 Subject: [PATCH 2/3] feat(calendar): add scoped credential materialization port --- .../src/calendar-credential-materializer.ts | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 apps/integration-calendar-service/src/calendar-credential-materializer.ts diff --git a/apps/integration-calendar-service/src/calendar-credential-materializer.ts b/apps/integration-calendar-service/src/calendar-credential-materializer.ts new file mode 100644 index 000000000..e10555aab --- /dev/null +++ b/apps/integration-calendar-service/src/calendar-credential-materializer.ts @@ -0,0 +1,153 @@ +import type { TrustedCalendarUserContext } from './calendar-service-context'; +import type { + CalendarConnectionProvider, + CalendarConnectionRecord, + GetActiveCalendarConnection, +} from './calendar-connection-repository'; + +const UUID_V4_PATTERN = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu; +const SECRET_HANDLE_PATTERN = + /^[A-Za-z][A-Za-z0-9+.-]{0,31}:\/\/[^\s\u0000-\u001f\u007f]{1,1024}$/u; +const CONTROL_CHARACTER_PATTERN = /[\u0000-\u001f\u007f]/u; +const MAXIMUM_SECRET_LENGTH = 16_384; + +/** Least-authority lookup needed before calendar credential materialization. */ +export interface CalendarCredentialConnectionPort { + getActiveConnection( + input: GetActiveCalendarConnection, + ): Promise; +} + +/** External encrypted secret-store/KMS port; opaque handles are the only lookup key. */ +export interface CalendarCredentialSecretStore { + readSecret(secretHandle: string): Promise; +} + +/** Internal-only provider material. This type must never be returned by a public controller. */ +export interface CalendarCredentialMaterial { + readonly connectionId: string; + readonly providerCode: CalendarConnectionProvider; + readonly accessToken: string; + readonly refreshToken: string | null; + readonly tokenExpiresAt: string; + readonly selectedCalendarIdentifier: string; +} + +/** Fixed fail-closed error that never retains provider credential material. */ +export class CalendarCredentialMaterializationError extends Error { + /** Creates a credential-free failure safe for application-level sanitization. */ + constructor() { + super('Calendar credential materialization is unavailable'); + this.name = 'CalendarCredentialMaterializationError'; + } +} + +function invalid(): never { + throw new CalendarCredentialMaterializationError(); +} + +function requireUuidV4(value: unknown): string { + if (typeof value !== 'string' || !UUID_V4_PATTERN.test(value)) { + return invalid(); + } + return value.toLowerCase(); +} + +function requireSecretHandle(value: unknown): string { + if (typeof value !== 'string' || !SECRET_HANDLE_PATTERN.test(value)) { + return invalid(); + } + return value; +} + +function requireSecretMaterial(value: unknown): string { + if ( + typeof value !== 'string' || + value.length === 0 || + value.length > MAXIMUM_SECRET_LENGTH || + CONTROL_CHARACTER_PATTERN.test(value) + ) { + return invalid(); + } + return value; +} + +function requireActiveEvidence( + record: CalendarConnectionRecord, + expected: GetActiveCalendarConnection, +): CalendarConnectionRecord { + if ( + record.status !== 'active' || + record.revokedAt !== null || + record.connectionId !== expected.connectionId || + record.workspaceId !== expected.workspaceId || + record.userId !== expected.userId + ) { + return invalid(); + } + return record; +} + +/** + * Resolves provider credentials only after exact tenant/user/connection authority. + * Durable LifeOS state supplies opaque handles; plaintext exists only in the + * returned internal value and must be passed directly to the selected provider. + */ +export class CalendarCredentialMaterializer { + /** Creates the materializer over scoped connection evidence and encrypted secret storage. */ + constructor( + private readonly connections: CalendarCredentialConnectionPort, + private readonly secrets: CalendarCredentialSecretStore, + ) {} + + /** Materializes one exact active connection without accepting ownership from caller data. */ + async materialize( + authority: TrustedCalendarUserContext, + connectionId: string, + ): Promise { + const expected = Object.freeze({ + connectionId: requireUuidV4(connectionId), + workspaceId: requireUuidV4(authority.workspaceId), + userId: requireUuidV4(authority.userId), + }); + + let persisted: CalendarConnectionRecord | undefined; + try { + persisted = await this.connections.getActiveConnection(expected); + } catch { + return invalid(); + } + if (!persisted) { + return invalid(); + } + const record = requireActiveEvidence(persisted, expected); + const accessSecretHandle = requireSecretHandle(record.accessSecretHandle); + const refreshSecretHandle = + record.refreshSecretHandle === null + ? null + : requireSecretHandle(record.refreshSecretHandle); + + let accessToken: string; + let refreshToken: string | null; + try { + accessToken = requireSecretMaterial( + await this.secrets.readSecret(accessSecretHandle), + ); + refreshToken = refreshSecretHandle + ? requireSecretMaterial(await this.secrets.readSecret(refreshSecretHandle)) + : null; + } catch { + return invalid(); + } + + return Object.freeze({ + connectionId: record.connectionId, + providerCode: record.providerCode, + accessToken, + refreshToken, + tokenExpiresAt: record.tokenExpiresAt, + selectedCalendarIdentifier: record.selectedCalendarIdentifier, + }); + } +} From e2892e556ec2e000009362c21ba1b589517f41a7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 05:57:28 +0900 Subject: [PATCH 3/3] test(calendar): fix missing-connection fixture and secret literals --- .../calendar-credential-materializer.test.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts b/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts index 26bfc0783..a8604fbee 100644 --- a/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts +++ b/apps/integration-calendar-service/src/calendar-credential-materializer.test.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import { describe, expect, it, vi } from 'vitest'; import type { TrustedCalendarUserContext } from './calendar-service-context'; import type { CalendarConnectionRecord } from './calendar-connection-repository'; @@ -9,6 +10,8 @@ import { const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111'; const USER_ID = '22222222-2222-4222-8222-222222222222'; const CONNECTION_ID = '33333333-3333-4333-8333-333333333333'; +const ACCESS_MATERIAL = randomBytes(24).toString('base64url'); +const REFRESH_MATERIAL = randomBytes(24).toString('base64url'); const AUTHORITY: TrustedCalendarUserContext = Object.freeze({ workspaceId: WORKSPACE_ID, userId: USER_ID, @@ -36,10 +39,10 @@ function connection( }); } -function materializer(record: CalendarConnectionRecord | undefined = connection()) { +function materializer(record: CalendarConnectionRecord | undefined) { const getActiveConnection = vi.fn().mockResolvedValue(record); const readSecret = vi.fn(async (handle: string) => - handle.includes('/refresh-') ? 'refresh-token-value' : 'access-token-value', + handle.includes('/refresh-') ? REFRESH_MATERIAL : ACCESS_MATERIAL, ); return { getActiveConnection, @@ -61,15 +64,15 @@ async function expectMaterializationFailure( describe('CalendarCredentialMaterializer', () => { it('materializes only the exact active connection inside trusted user authority', async () => { - const fixture = materializer(); + const fixture = materializer(connection()); await expect( fixture.subject.materialize(AUTHORITY, CONNECTION_ID), ).resolves.toEqual({ connectionId: CONNECTION_ID, providerCode: 'google', - accessToken: 'access-token-value', - refreshToken: 'refresh-token-value', + accessToken: ACCESS_MATERIAL, + refreshToken: REFRESH_MATERIAL, tokenExpiresAt: '2026-08-12T12:00:00.000Z', selectedCalendarIdentifier: 'primary', }); @@ -110,7 +113,9 @@ describe('CalendarCredentialMaterializer', () => { it('fails closed when secret materialization is unavailable without exposing provider errors', async () => { const getActiveConnection = vi.fn().mockResolvedValue(connection()); - const readSecret = vi.fn().mockRejectedValue(new Error('kms token leaked-value')); + const readSecret = vi + .fn() + .mockRejectedValue(new Error('provider sensitive-material marker')); const subject = new CalendarCredentialMaterializer( { getActiveConnection }, { readSecret }, @@ -123,14 +128,16 @@ describe('CalendarCredentialMaterializer', () => { thrown = error; } expect(thrown).toBeInstanceOf(CalendarCredentialMaterializationError); - expect((thrown as Error).message).not.toContain('leaked-value'); + expect((thrown as Error).message).not.toContain('sensitive-material'); }); it('rejects empty, oversized, or control-character secret values', async () => { - for (const secretValue of ['', 'a'.repeat(16_385), 'token\nvalue']) { - const getActiveConnection = vi.fn().mockResolvedValue(connection({ - refreshSecretHandle: null, - })); + for (const secretValue of ['', 'a'.repeat(16_385), 'material\nvalue']) { + const getActiveConnection = vi.fn().mockResolvedValue( + connection({ + refreshSecretHandle: null, + }), + ); const readSecret = vi.fn().mockResolvedValue(secretValue); const subject = new CalendarCredentialMaterializer( { getActiveConnection },