From d0036c66996cefa553ae5b5cd0552d98dd8f9cb6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 22:41:56 +0900 Subject: [PATCH 01/10] test(identity): exercise OAuth browser session flow over HTTP --- .../src/oauth-http.integration.test.ts | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 apps/identity-service/src/oauth-http.integration.test.ts diff --git a/apps/identity-service/src/oauth-http.integration.test.ts b/apps/identity-service/src/oauth-http.integration.test.ts new file mode 100644 index 00000000..51854b2a --- /dev/null +++ b/apps/identity-service/src/oauth-http.integration.test.ts @@ -0,0 +1,296 @@ +import type { AddressInfo } from 'node:net'; +import { INestApplication, Module } from '@nestjs/common'; +import { NestFactory } from '@nestjs/core'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { + InMemoryOAuthTransactionRepository, + InMemorySessionRepository, + OAuthTransactionService, + SessionService, +} from './auth-security'; +import { + IdentityService, + InMemoryIdentityRepository, +} from './identity-domain'; +import { + OAuthCallbackApplication, + type OAuthCallbackAuditEvent, +} from './oauth-callback-application'; +import { OAuthHttpApplication } from './oauth-http-application'; +import { + OAUTH_CALLBACK_APPLICATION, + OAUTH_HTTP_APPLICATION, + OAuthHttpController, +} from './oauth-http-controller'; + +const NOW = new Date('2026-08-03T15:00:00.000Z'); +const WEB_ORIGIN = 'https://app.example.test'; +const GOOGLE_REDIRECT_URI = + 'https://identity.example.test/v1/auth/google/callback'; +const GITHUB_REDIRECT_URI = + 'https://identity.example.test/v1/auth/github/callback'; +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}$/i; + +interface TestHarness { + app: INestApplication; + baseUrl: string; + auditEvents: OAuthCallbackAuditEvent[]; + googleAuthenticate: ReturnType; + githubAuthenticate: ReturnType; +} + +const activeApplications: INestApplication[] = []; + +function cookiePair(setCookie: string | null): string { + if (!setCookie) { + throw new Error('Expected Set-Cookie header'); + } + return setCookie.split(';', 1)[0] as string; +} + +function callbackState(location: string | null): string { + if (!location) { + throw new Error('Expected authorization redirect'); + } + const state = new URL(location).searchParams.get('state'); + if (!state) { + throw new Error('Expected OAuth state'); + } + return state; +} + +async function request( + baseUrl: string, + path: string, + init: RequestInit = {}, +): Promise { + return await fetch(`${baseUrl}${path}`, { + ...init, + redirect: 'manual', + }); +} + +async function createHarness(): Promise { + const transactions = new OAuthTransactionService( + new InMemoryOAuthTransactionRepository(), + { now: () => NOW }, + ); + const sessions = new SessionService(new InMemorySessionRepository(), { + now: () => NOW, + }); + const identities = new IdentityService(new InMemoryIdentityRepository()); + const auditEvents: OAuthCallbackAuditEvent[] = []; + const googleAuthenticate = vi.fn(async () => ({ + provider: 'google' as const, + subject: 'google-subject-integration', + issuer: 'https://accounts.google.com' as const, + email: 'integration@example.test', + emailVerified: true, + displayName: 'Google Integration User', + })); + const githubAuthenticate = vi.fn(async () => ({ + provider: 'github' as const, + providerSubject: '9007199254740993', + displayName: 'GitHub Integration User', + verifiedEmail: 'integration@example.test', + })); + const httpApplication = new OAuthHttpApplication(transactions, sessions, { + providers: { + google: { + clientId: 'google-integration-client', + redirectUri: GOOGLE_REDIRECT_URI, + }, + github: { + clientId: 'github-integration-client', + redirectUri: GITHUB_REDIRECT_URI, + }, + }, + webOrigin: WEB_ORIGIN, + }); + const callbackApplication = new OAuthCallbackApplication( + transactions, + identities, + sessions, + { + google: { authenticateAuthorizationCode: googleAuthenticate }, + github: { authenticateAuthorizationCode: githubAuthenticate }, + }, + { + record(event): void { + auditEvents.push({ ...event }); + }, + }, + { webOrigin: WEB_ORIGIN, now: () => NOW }, + ); + + class OAuthHttpIntegrationModule {} + Module({ + controllers: [OAuthHttpController], + providers: [ + { provide: OAUTH_HTTP_APPLICATION, useValue: httpApplication }, + { provide: OAUTH_CALLBACK_APPLICATION, useValue: callbackApplication }, + ], + })(OAuthHttpIntegrationModule); + + const app = await NestFactory.create(OAuthHttpIntegrationModule, { + logger: false, + }); + await app.listen(0, '127.0.0.1'); + activeApplications.push(app); + const address = app.getHttpServer().address() as AddressInfo; + return { + app, + baseUrl: `http://127.0.0.1:${address.port}`, + auditEvents, + googleAuthenticate, + githubAuthenticate, + }; +} + +afterEach(async () => { + await Promise.all(activeApplications.splice(0).map((app) => app.close())); +}); + +describe('OAuth HTTP integration', () => { + it('completes Google sign-in, introspection, replay rejection, and idempotent logout over HTTP', async () => { + const harness = await createHarness(); + + const start = await request(harness.baseUrl, '/v1/auth/google/start'); + expect(start.status).toBe(303); + expect(start.headers.get('cache-control')).toBe('no-store'); + expect(start.headers.get('location')).toContain( + 'https://accounts.google.com/o/oauth2/v2/auth', + ); + const browserSetCookie = start.headers.get('set-cookie'); + expect(browserSetCookie).toContain('Path=/v1/auth'); + expect(browserSetCookie).toContain('HttpOnly; Secure; SameSite=Lax'); + const browserCookie = cookiePair(browserSetCookie); + const state = callbackState(start.headers.get('location')); + + const callback = await request( + harness.baseUrl, + `/v1/auth/google/callback?code=google-code&state=${encodeURIComponent(state)}`, + { headers: { cookie: browserCookie } }, + ); + expect(callback.status).toBe(303); + expect(callback.headers.get('cache-control')).toBe('no-store'); + expect(callback.headers.get('location')).toBe( + 'https://app.example.test/auth/complete', + ); + const correlationId = callback.headers.get('x-correlation-id'); + expect(correlationId).toMatch(UUID_V4_PATTERN); + const sessionSetCookie = callback.headers.get('set-cookie'); + expect(sessionSetCookie).toContain('Path=/'); + expect(sessionSetCookie).toContain('HttpOnly; Secure; SameSite=Lax'); + const sessionCookie = cookiePair(sessionSetCookie); + const sessionToken = sessionCookie.split('=', 2)[1] as string; + + const session = await request(harness.baseUrl, '/v1/session', { + headers: { cookie: sessionCookie }, + }); + expect(session.status).toBe(200); + expect(session.headers.get('cache-control')).toBe('no-store'); + const sessionBody = await session.json(); + expect(sessionBody).toMatchObject({ + sessionId: expect.stringMatching(UUID_V4_PATTERN), + userId: expect.stringMatching(UUID_V4_PATTERN), + workspaceId: expect.stringMatching(UUID_V4_PATTERN), + }); + const serializedSession = JSON.stringify(sessionBody); + expect(serializedSession).not.toContain(sessionToken); + expect(serializedSession).not.toContain('google-code'); + expect(serializedSession).not.toContain(state); + + const replay = await request( + harness.baseUrl, + `/v1/auth/google/callback?code=google-code&state=${encodeURIComponent(state)}`, + { headers: { cookie: browserCookie } }, + ); + expect(replay.status).toBe(400); + expect(await replay.json()).toMatchObject({ + status: 400, + code: 'oauth_callback_failed', + }); + + const logout = await request(harness.baseUrl, '/v1/auth/logout', { + method: 'POST', + headers: { cookie: sessionCookie }, + }); + expect(logout.status).toBe(204); + expect(logout.headers.get('set-cookie')).toContain( + 'life_os_session=deleted; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax', + ); + + const repeatedLogout = await request( + harness.baseUrl, + '/v1/auth/logout', + { method: 'POST', headers: { cookie: sessionCookie } }, + ); + expect(repeatedLogout.status).toBe(204); + + const revokedSession = await request(harness.baseUrl, '/v1/session', { + headers: { cookie: sessionCookie }, + }); + expect(revokedSession.status).toBe(401); + expect(await revokedSession.json()).toMatchObject({ + status: 401, + code: 'authentication_required', + }); + expect(harness.googleAuthenticate).toHaveBeenCalledTimes(1); + expect(harness.auditEvents).toEqual([ + expect.objectContaining({ + provider: 'google', + outcome: 'success', + correlationId, + }), + { + provider: 'google', + outcome: 'failure', + correlationId: expect.stringMatching(UUID_V4_PATTERN), + }, + ]); + }); + + it('rejects cross-browser GitHub callback use before provider access and then accepts the bound browser', async () => { + const harness = await createHarness(); + const start = await request(harness.baseUrl, '/v1/auth/github/start'); + const browserCookie = cookiePair(start.headers.get('set-cookie')); + const state = callbackState(start.headers.get('location')); + const wrongBrowserCookie = `life_os_oauth_browser=${'x'.repeat(43)}`; + const callbackPath = `/v1/auth/github/callback?code=github-code&state=${encodeURIComponent(state)}`; + + const rejected = await request(harness.baseUrl, callbackPath, { + headers: { cookie: wrongBrowserCookie }, + }); + expect(rejected.status).toBe(400); + expect(harness.githubAuthenticate).not.toHaveBeenCalled(); + + const accepted = await request(harness.baseUrl, callbackPath, { + headers: { + cookie: browserCookie, + 'x-correlation-id': 'github-integration-correlation', + }, + }); + expect(accepted.status).toBe(303); + expect(accepted.headers.get('location')).toBe( + 'https://app.example.test/auth/complete', + ); + expect(accepted.headers.get('x-correlation-id')).toBe( + 'github-integration-correlation', + ); + expect(harness.githubAuthenticate).toHaveBeenCalledTimes(1); + expect(harness.auditEvents).toEqual([ + { + provider: 'github', + outcome: 'failure', + correlationId: expect.stringMatching(UUID_V4_PATTERN), + }, + expect.objectContaining({ + provider: 'github', + outcome: 'success', + correlationId: 'github-integration-correlation', + }), + ]); + }); +}); From 9113149a1a89e2626c0f343edf2e5655d6113801 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 22:42:25 +0900 Subject: [PATCH 02/10] docs(identity): plan OAuth HTTP integration coverage --- .../2026-08-03-oauth-http-integration-slice.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-03-oauth-http-integration-slice.md diff --git a/docs/superpowers/plans/2026-08-03-oauth-http-integration-slice.md b/docs/superpowers/plans/2026-08-03-oauth-http-integration-slice.md new file mode 100644 index 00000000..c17a3c73 --- /dev/null +++ b/docs/superpowers/plans/2026-08-03-oauth-http-integration-slice.md @@ -0,0 +1,18 @@ +# OAuth HTTP Integration Slice + +## Goal + +Verify the complete Google and GitHub browser-session lifecycle through the production NestJS controller boundary without contacting external providers. + +## Changes + +1. Start a real ephemeral NestJS HTTP server with the production OAuth controller and in-memory domain repositories. +2. Exercise Google authorization start, callback completion, secure session cookie issuance, session introspection, replay rejection, logout, and revoked-session rejection. +3. Exercise GitHub cross-browser state rejection before provider access, followed by successful completion from the bound browser. +4. Assert fixed-origin redirects, no-store responses, secure cookie attributes, credential-free response bodies, correlation IDs, and structured audit outcomes. + +## Validation + +- formatting, lint, type checking, tests, and build pass; +- AppGuardrail, Semgrep, Security Scan, and Commercial Readiness pass; +- CodeRabbit and human/security review feedback contain no unresolved actionable findings. From 97601d0a7f558691bda8767650f90a61ed8f5991 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 22:42:48 +0900 Subject: [PATCH 03/10] chore(ci): format OAuth HTTP integration artifacts --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5fff9375..2f5b6e61 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "lint": "turbo run lint", "test": "turbo run test", "typecheck": "turbo run typecheck", - "format:check": "prettier --single-quote --check README.md package.json turbo.json tsconfig.base.json pnpm-workspace.yaml compose.yaml .appguardrail.json .github/workflows/ci.yml .github/workflows/appguardrail.yml security/appguardrail-contract.json packages/appguardrail-contract/package.json packages/appguardrail-contract/src/verify-contract.mjs packages/appguardrail-contract/src/verify-contract.test.mjs tests/appguardrail-fixtures/dangerous-cors.ts tests/appguardrail-fixtures/oauth-open-redirect.ts docs/security/appguardrail-regressions.md docs/superpowers/specs/2026-08-03-appguardrail-security-gate-design.md docs/superpowers/plans/2026-08-03-appguardrail-security-gate.md .github/workflows/commercial-readiness.yml product/commercial-readiness-policy.json product/capabilities.json packages/commercial-readiness/package.json packages/commercial-readiness/src/schema.mjs packages/commercial-readiness/src/schema.test.mjs packages/commercial-readiness/src/audit.mjs packages/commercial-readiness/src/audit.test.mjs packages/commercial-readiness/src/pr-gate.mjs packages/commercial-readiness/src/pr-gate.test.mjs packages/commercial-readiness/src/render.mjs packages/commercial-readiness/src/render.test.mjs packages/commercial-readiness/src/github-client.mjs packages/commercial-readiness/src/github-client.test.mjs packages/commercial-readiness/src/cli.mjs packages/commercial-readiness/src/cli.test.mjs packages/commercial-readiness/src/workflow-contract.test.mjs docs/superpowers/specs/2026-08-03-commercial-readiness-loop-design.md docs/superpowers/plans/2026-08-03-commercial-readiness-loop.md apps/identity-service/src/oauth-http-boundary.ts apps/identity-service/src/oauth-http-application.ts apps/identity-service/src/oauth-http-boundary.test.ts docs/superpowers/plans/2026-08-03-oauth-http-boundary-slice.md apps/identity-service/package.json apps/identity-service/src/main.ts apps/identity-service/src/oauth-http-controller.ts apps/identity-service/src/oauth-http-controller.test.ts apps/identity-service/src/identity-runtime.ts apps/identity-service/src/identity-runtime.test.ts docs/superpowers/plans/2026-08-03-oauth-controller-wiring-slice.md apps/identity-service/src/oauth-provider-http-client.ts apps/identity-service/src/tests/oauth-provider-http-client.test.ts docs/superpowers/plans/2026-08-03-oauth-provider-http-client-slice.md apps/identity-service/src/google-oidc-client.ts apps/identity-service/src/google-oidc-client.test.ts docs/superpowers/plans/2026-08-03-google-oidc-verifier-slice.md apps/identity-service/src/github-oauth-client.ts apps/identity-service/src/github-oauth-client.test.ts docs/superpowers/plans/2026-08-03-github-oauth-client-slice.md apps/identity-service/src/oauth-callback-application.ts apps/identity-service/src/oauth-callback-application.test.ts docs/superpowers/plans/2026-08-03-oauth-callback-orchestration-slice.md docs/superpowers/plans/2026-08-03-oauth-callback-runtime-wiring-slice.md docs/superpowers/plans/2026-08-03-oauth-open-redirect-regression-slice.md", + "format:check": "prettier --single-quote --check README.md package.json turbo.json tsconfig.base.json pnpm-workspace.yaml compose.yaml .appguardrail.json .github/workflows/ci.yml .github/workflows/appguardrail.yml security/appguardrail-contract.json packages/appguardrail-contract/package.json packages/appguardrail-contract/src/verify-contract.mjs packages/appguardrail-contract/src/verify-contract.test.mjs tests/appguardrail-fixtures/dangerous-cors.ts tests/appguardrail-fixtures/oauth-open-redirect.ts docs/security/appguardrail-regressions.md docs/superpowers/specs/2026-08-03-appguardrail-security-gate-design.md docs/superpowers/plans/2026-08-03-appguardrail-security-gate.md .github/workflows/commercial-readiness.yml product/commercial-readiness-policy.json product/capabilities.json packages/commercial-readiness/package.json packages/commercial-readiness/src/schema.mjs packages/commercial-readiness/src/schema.test.mjs packages/commercial-readiness/src/audit.mjs packages/commercial-readiness/src/audit.test.mjs packages/commercial-readiness/src/pr-gate.mjs packages/commercial-readiness/src/pr-gate.test.mjs packages/commercial-readiness/src/render.mjs packages/commercial-readiness/src/render.test.mjs packages/commercial-readiness/src/github-client.mjs packages/commercial-readiness/src/github-client.test.mjs packages/commercial-readiness/src/cli.mjs packages/commercial-readiness/src/cli.test.mjs packages/commercial-readiness/src/workflow-contract.test.mjs docs/superpowers/specs/2026-08-03-commercial-readiness-loop-design.md docs/superpowers/plans/2026-08-03-commercial-readiness-loop.md apps/identity-service/src/oauth-http-boundary.ts apps/identity-service/src/oauth-http-application.ts apps/identity-service/src/oauth-http-boundary.test.ts docs/superpowers/plans/2026-08-03-oauth-http-boundary-slice.md apps/identity-service/package.json apps/identity-service/src/main.ts apps/identity-service/src/oauth-http-controller.ts apps/identity-service/src/oauth-http-controller.test.ts apps/identity-service/src/oauth-http.integration.test.ts apps/identity-service/src/identity-runtime.ts apps/identity-service/src/identity-runtime.test.ts docs/superpowers/plans/2026-08-03-oauth-controller-wiring-slice.md apps/identity-service/src/oauth-provider-http-client.ts apps/identity-service/src/tests/oauth-provider-http-client.test.ts docs/superpowers/plans/2026-08-03-oauth-provider-http-client-slice.md apps/identity-service/src/google-oidc-client.ts apps/identity-service/src/google-oidc-client.test.ts docs/superpowers/plans/2026-08-03-google-oidc-verifier-slice.md apps/identity-service/src/github-oauth-client.ts apps/identity-service/src/github-oauth-client.test.ts docs/superpowers/plans/2026-08-03-github-oauth-client-slice.md apps/identity-service/src/oauth-callback-application.ts apps/identity-service/src/oauth-callback-application.test.ts docs/superpowers/plans/2026-08-03-oauth-callback-orchestration-slice.md docs/superpowers/plans/2026-08-03-oauth-callback-runtime-wiring-slice.md docs/superpowers/plans/2026-08-03-oauth-open-redirect-regression-slice.md docs/superpowers/plans/2026-08-03-oauth-http-integration-slice.md", "format": "prettier --single-quote --write ." }, "devDependencies": { From b407864a594e7855cb2d53eba35d857373890cc9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:01:54 +0900 Subject: [PATCH 04/10] style(identity): format OAuth HTTP integration test --- apps/identity-service/src/oauth-http.integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/identity-service/src/oauth-http.integration.test.ts b/apps/identity-service/src/oauth-http.integration.test.ts index 51854b2a..b8358974 100644 --- a/apps/identity-service/src/oauth-http.integration.test.ts +++ b/apps/identity-service/src/oauth-http.integration.test.ts @@ -153,7 +153,7 @@ afterEach(async () => { }); describe('OAuth HTTP integration', () => { - it('completes Google sign-in, introspection, replay rejection, and idempotent logout over HTTP', async () => { + it('completes the Google browser-session lifecycle over HTTP', async () => { const harness = await createHarness(); const start = await request(harness.baseUrl, '/v1/auth/google/start'); @@ -252,7 +252,7 @@ describe('OAuth HTTP integration', () => { ]); }); - it('rejects cross-browser GitHub callback use before provider access and then accepts the bound browser', async () => { + it('binds GitHub callbacks to the initiating browser', async () => { const harness = await createHarness(); const start = await request(harness.baseUrl, '/v1/auth/github/start'); const browserCookie = cookiePair(start.headers.get('set-cookie')); From 37ea13dbeb0f4b0ee0af1f414269f65fd1f0a92a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:04:54 +0900 Subject: [PATCH 05/10] chore(ci): diagnose OAuth integration formatting --- .github/workflows/prettier-diagnostic.yml | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/prettier-diagnostic.yml diff --git a/.github/workflows/prettier-diagnostic.yml b/.github/workflows/prettier-diagnostic.yml new file mode 100644 index 00000000..ee36ca66 --- /dev/null +++ b/.github/workflows/prettier-diagnostic.yml @@ -0,0 +1,28 @@ +name: Prettier Diagnostic + +on: + push: + branches: + - test/oauth-http-integration + +permissions: + contents: read + +jobs: + format-diagnostic: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: 22 + - name: Enable Corepack + run: corepack enable + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + - name: Print Prettier diff + run: | + pnpm exec prettier --single-quote --write apps/identity-service/src/oauth-http.integration.test.ts + git diff --no-ext-diff -- apps/identity-service/src/oauth-http.integration.test.ts From 09865d29c8b94421047382ab5dd1bb3b562c1616 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:06:37 +0900 Subject: [PATCH 06/10] chore(ci): print OAuth integration formatting diff --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0fba0f9..ee96604a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,11 @@ jobs: - name: Install dependencies run: pnpm install --no-frozen-lockfile - - name: Check formatting - run: pnpm format:check + - name: Print formatting diff + run: | + pnpm exec prettier --single-quote --write apps/identity-service/src/oauth-http.integration.test.ts + git diff --no-ext-diff -- apps/identity-service/src/oauth-http.integration.test.ts + exit 1 - name: Lint run: pnpm lint From 438cc3b6739b84e28d5ce42c491eabe1c88fafff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:08:46 +0900 Subject: [PATCH 07/10] style(identity): apply canonical OAuth integration formatting --- .../src/oauth-http.integration.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/apps/identity-service/src/oauth-http.integration.test.ts b/apps/identity-service/src/oauth-http.integration.test.ts index b8358974..3aa8fdd3 100644 --- a/apps/identity-service/src/oauth-http.integration.test.ts +++ b/apps/identity-service/src/oauth-http.integration.test.ts @@ -8,10 +8,7 @@ import { OAuthTransactionService, SessionService, } from './auth-security'; -import { - IdentityService, - InMemoryIdentityRepository, -} from './identity-domain'; +import { IdentityService, InMemoryIdentityRepository } from './identity-domain'; import { OAuthCallbackApplication, type OAuthCallbackAuditEvent, @@ -222,11 +219,10 @@ describe('OAuth HTTP integration', () => { 'life_os_session=deleted; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax', ); - const repeatedLogout = await request( - harness.baseUrl, - '/v1/auth/logout', - { method: 'POST', headers: { cookie: sessionCookie } }, - ); + const repeatedLogout = await request(harness.baseUrl, '/v1/auth/logout', { + method: 'POST', + headers: { cookie: sessionCookie }, + }); expect(repeatedLogout.status).toBe(204); const revokedSession = await request(harness.baseUrl, '/v1/session', { From 651205752b956c2fa94916427a33f3eac2e5245d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:08:58 +0900 Subject: [PATCH 08/10] chore(ci): restore validation workflow --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee96604a..f0fba0f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,11 +48,8 @@ jobs: - name: Install dependencies run: pnpm install --no-frozen-lockfile - - name: Print formatting diff - run: | - pnpm exec prettier --single-quote --write apps/identity-service/src/oauth-http.integration.test.ts - git diff --no-ext-diff -- apps/identity-service/src/oauth-http.integration.test.ts - exit 1 + - name: Check formatting + run: pnpm format:check - name: Lint run: pnpm lint From 3e095374645b7e306ec8999643f040f502da1bde Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:09:15 +0900 Subject: [PATCH 09/10] chore(ci): remove temporary formatting diagnostic --- .github/workflows/prettier-diagnostic.yml | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/prettier-diagnostic.yml diff --git a/.github/workflows/prettier-diagnostic.yml b/.github/workflows/prettier-diagnostic.yml deleted file mode 100644 index ee36ca66..00000000 --- a/.github/workflows/prettier-diagnostic.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Prettier Diagnostic - -on: - push: - branches: - - test/oauth-http-integration - -permissions: - contents: read - -jobs: - format-diagnostic: - runs-on: ubuntu-24.04 - steps: - - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - - name: Set up Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 - with: - node-version: 22 - - name: Enable Corepack - run: corepack enable - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - - name: Print Prettier diff - run: | - pnpm exec prettier --single-quote --write apps/identity-service/src/oauth-http.integration.test.ts - git diff --no-ext-diff -- apps/identity-service/src/oauth-http.integration.test.ts From 6f848c21138717784749b159c79167b0ffdb244a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 23:12:10 +0900 Subject: [PATCH 10/10] refactor(identity): use standard Nest module decorator --- apps/identity-service/src/oauth-http.integration.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/identity-service/src/oauth-http.integration.test.ts b/apps/identity-service/src/oauth-http.integration.test.ts index 3aa8fdd3..0e975665 100644 --- a/apps/identity-service/src/oauth-http.integration.test.ts +++ b/apps/identity-service/src/oauth-http.integration.test.ts @@ -121,14 +121,14 @@ async function createHarness(): Promise { { webOrigin: WEB_ORIGIN, now: () => NOW }, ); - class OAuthHttpIntegrationModule {} - Module({ + @Module({ controllers: [OAuthHttpController], providers: [ { provide: OAUTH_HTTP_APPLICATION, useValue: httpApplication }, { provide: OAUTH_CALLBACK_APPLICATION, useValue: callbackApplication }, ], - })(OAuthHttpIntegrationModule); + }) + class OAuthHttpIntegrationModule {} const app = await NestFactory.create(OAuthHttpIntegrationModule, { logger: false,