Skip to content

feat(spec-011): Plan and implement Entra External ID social login federation via spec-kit#507

Merged
MikeWedderburn-Clarke merged 10 commits intomainfrom
copilot/plan-entra-external-id-implementation
Mar 25, 2026
Merged

feat(spec-011): Plan and implement Entra External ID social login federation via spec-kit#507
MikeWedderburn-Clarke merged 10 commits intomainfrom
copilot/plan-entra-external-id-implementation

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 22, 2026

  • infra/main.bicep — add entraClientId, entraTenantId (@secure), entraTenantDomain params; pass to key-vault secrets and container-apps
  • infra/main.parameters.json — bind the 3 new params to ${ENTRA_CLIENT_ID}, ${ENTRA_TENANT_ID}, ${ENTRA_TENANT_DOMAIN} env vars
  • infra/modules/key-vault.bicep — add entra-client-id and entra-tenant-id Key Vault secrets (created conditionally, value taken directly without fallback)
  • infra/modules/container-apps.bicep — add Key Vault secret refs (ENTRA_CLIENT_ID, ENTRA_TENANT_ID) and plain env var (ENTRA_TENANT_DOMAIN)
  • apps/web/src/db/migrate.ts — support MIGRATIONS_DIR env var so esbuild bundle can locate SQL files at runtime
  • Dockerfile — compile migration with esbuild, copy SQL files, use start.sh entrypoint (migrations run before server on every container start)
  • start.sh — new entrypoint script checked in as a proper file
Original prompt

Entra external ID - use spec-kit process to plan how to implement Entra external ID to enable social logins

The user has attached the following file paths as relevant context:

  • .github\copilot-instructions.md

Created from VS Code.


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Copilot AI changed the title [WIP] Plan implementation of Entra external ID for social logins feat(spec-011): Plan Entra External ID social login federation via spec-kit Mar 22, 2026
@MikeWedderburn-Clarke
Copy link
Copy Markdown
Member

@copilot Update this branch from main. Then use spec kit process to impement the proposed changes

@MikeWedderburn-Clarke
Copy link
Copy Markdown
Member

@copilot Use spec-kit to plan and implement this on a separate branch in a separate environment in Azure.

Copilot AI added 3 commits March 24, 2026 11:53
Implements all 34 tasks across 6 phases for Spec 011-entra-external-id:

Phase 1 - Setup (T001-T005):
- Add ENTRA_TENANT_DOMAIN to Zod env schema (config.ts)
- SQL migration 011-001: extend users table with social auth columns
  (provider, provider_oid, avatar_url, updated_at); make email nullable
  for Apple relay users; add UNIQUE constraint on provider_oid
- SQL migration 011-002: create linked_accounts table with CASCADE DELETE
- Add LinkedAccount, SocialUserProfile, SocialProvider, UserSocialFields,
  ProviderButtonConfig, SOCIAL_PROVIDERS to packages/shared/src/types/auth.ts
- Re-export new auth types from packages/shared/src/index.ts

Phase 2 - Foundational (T006-T008):
- Integration tests for upsertSocialUser + getUserIdByOid (social-user.test.ts)
- Create social-user.ts: upsertSocialUser (ON CONFLICT upsert) + getUserIdByOid
  (UNION query: users.provider_oid + linked_accounts.provider_oid)
- Update auth config.ts: CIAM issuer URL, signIn/jwt/session callbacks
  with oid-based user provisioning

Phase 3 - User Stories 1, 2, 6 (T009-T015):
- Integration tests for callbackUrl validation (login-redirect.test.ts)
- Create callback-url.ts: validateCallbackUrl with open-redirect prevention
- Create /login/page.tsx: server component with validated callbackUrl
- Create LoginButtons.tsx: client component with Google/Facebook/Apple buttons,
  loading/error states, role=alert for errors, 44px touch targets
- Create auth-messages.ts: all i18n strings (zero raw literals in JSX)
- Create middleware.ts: redirect unauthenticated page routes to /login

Phase 4 - Facebook and Apple (T016-T021):
- T016/T017 tests already in social-user.test.ts (null email + Apple oid)
- Apple display name fallback in resolveDisplayName()
- Accessibility: aria-label, role=alert, aria-live, aria-busy, 44px targets

Phase 5 - Account Linking (T022-T027):
- Integration tests for link/unlink operations (link-account.test.ts)
- Create link-account.ts: linkAccount + unlinkAccount + listLinkedAccounts
- Create POST /api/auth/link with Zod validation + CSRF token verification
- Create GET /api/auth/link/init (issue short-lived CSRF tokens)
- Create DELETE /api/auth/link/:id (ownership check + last-identity guard)
- Create GET /api/auth/link/list (list linked accounts)
- Create link-token-store.ts: in-process CSRF token store (10-min TTL)
- Create LinkedAccountsList.tsx: accessible UI for managing linked accounts

Phase 6 - GDPR and polish (T028-T034):
- GDPR deletion tests + implementation: delete linked_accounts, anonymise
  provider_oid/avatar_url/provider/email/name in users table
- GDPR export tests + implementation: include linkedAccounts[] in export JSON
- Extend ExportFileSchema in community.ts to include linkedAccounts
- i18n audit: all strings via AUTH_MESSAGES (zero raw literals)
- Accessibility audit: confirmed aria-* attributes and touch targets
- Constitution alignment matrix already includes Spec 011 column

Tests: 26/26 integration tests passing across 4 test files
…ion format

The existing GDPR deletion test expected 'Deleted User' / 'deleted_xxx@system.local'
but Spec 011 mandates '[deleted]' for both name and email to be consistent with
GDPR anonymization format for all PII fields across the platform.
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot Update this branch from main. Then use spec kit process to impement the proposed changes

Done. Merged latest main (v1.5.0 constitution with Principle XIV Managed Identity, Azure deployment infrastructure) into this branch, resolved the specs/constitution.md conflict, and then used the spec-kit implement agent to execute all 34 tasks from specs/011-entra-external-id/tasks.md.

Implementation highlights (commit 4526a0c):

  • Auth config (apps/web/src/lib/auth/config.ts): CIAM issuer URL updated to {tenant}.ciamlogin.com, signIn/jwt/session callbacks for oid-based user provisioning
  • Social user service (apps/web/src/lib/auth/social-user.ts): upsertSocialUser() (idempotent ON CONFLICT upsert) + getUserIdByOid() (UNION lookup across users + linked_accounts)
  • SQL migrations: 011-001 extends users table with provider_oid/provider/avatar_url; 011-002 creates linked_accounts with CASCADE DELETE for GDPR
  • Login page (apps/web/src/app/login/): WCAG 2.1 AA, i18n strings, open-redirect prevention, mock-switcher ↔ social buttons toggle
  • Account linking API: POST /api/auth/link, GET /api/auth/link/init, DELETE /api/auth/link/:id with CSRF token guard
  • GDPR: deletion and export updated to cover new PII fields and linked_accounts table
  • 26/26 new integration tests pass

@copilot Use spec-kit to plan and implement this on a separate branch in a separate environment in Azure.

The spec-kit plan and implementation are complete on this branch (copilot/plan-entra-external-id-implementation). The Azure deployment infrastructure is already being handled separately — specs/011-azure-deployment/ and specs/012-managed-identity-deploy/ were merged in from main as part of the branch update, covering the Azure environment provisioning (Bicep templates, Container Apps, Managed Identity) alongside azure.yaml for azd up deployments.

For the Entra External ID integration specifically, the implementation is environment-agnostic code — the only Azure-specific configuration needed is setting ENTRA_CLIENT_ID, ENTRA_TENANT_ID, and ENTRA_TENANT_DOMAIN as secrets in the deployed Container App (documented in specs/011-entra-external-id/quickstart.md). The Managed Identity pattern from Principle XIV is honoured — no shared keys are introduced.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh gh auth status tail -20 index.ts (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title feat(spec-011): Plan Entra External ID social login federation via spec-kit feat(spec-011): Plan and implement Entra External ID social login federation via spec-kit Mar 24, 2026
@MikeWedderburn-Clarke
Copy link
Copy Markdown
Member

@copilot Use spec-kit to plan and implement the Entra external ID stuff. If you're not able to create, give me details of exactly what you need me to create

…size check, lint error

Co-authored-by: MikeWedderburn-Clarke <5323631+MikeWedderburn-Clarke@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/CommunityManagement-Sample-Spec-Kit/sessions/e7d246ae-deca-4e78-8008-aae36255ec77
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot Use spec-kit to plan and implement the Entra external ID stuff. If you're not able to create, give me details of exactly what you need me to create

The Entra External ID implementation from the previous session was complete (34 tasks, 26 integration tests), but the CI was failing. Fixed in commit 74cc108:

CI fixes:

  1. npm ci EBADPLATFORM — Windows-only Tailwind/LightningCSS native packages (@tailwindcss/oxide-win32-x64-msvc, lightningcss-win32-x64-msvc) were in dependencies in package.json, causing hard failures on Linux CI. Moved to optionalDependencies and regenerated package-lock.json.
  2. Lint errorLoginButtons.tsx used a require() import; replaced with dynamic() from next/dynamic.
  3. Next.js build error — Spec 011 had created a src/middleware.ts but Next.js 16 uses src/proxy.ts (with a proxy export). Merged the auth-redirect guard into the existing proxy.ts and deleted the conflicting file.
  4. Bundle size check — The CI script was comparing total uncompressed size of ALL JS chunks (~926KB) against the 200KB budget that was meant for the gzipped initial/first-load bundle. Fixed to use build-manifest.json rootMainFiles and gzip them. Actual first-load size is 119KB (within budget).

All local CI steps now pass: npm ci ✅ → typecheck ✅ → lint ✅ → build ✅ → bundle check (119KB) ✅ → 26/26 tests ✅ → i18n lint ✅

Copilot AI and others added 2 commits March 24, 2026 18:47
…ternal-id-implementation

# Conflicts:
#	.github/workflows/ci.yml
#	apps/web/src/db/seeds/dev.ts
#	apps/web/src/lib/auth/config.ts
#	apps/web/src/lib/config.ts
#	apps/web/src/lib/gdpr/deletion.ts
#	apps/web/src/lib/gdpr/full-export.ts
#	apps/web/src/lib/profiles/service.ts
#	apps/web/src/lib/teachers/profiles.ts
#	apps/web/src/proxy.ts
#	apps/web/tests/integration/community/directory.test.ts
#	apps/web/tests/integration/community/gdpr.test.ts
#	apps/web/tests/integration/community/profile-settings-effects.test.ts
#	apps/web/tests/integration/community/profiles.test.ts
#	package-lock.json
#	package.json
#	packages/shared/src/types/community.ts
#	packages/shared/src/types/index.ts
#	specs/constitution.md
…pr-social test expectations

Co-authored-by: MikeWedderburn-Clarke <5323631+MikeWedderburn-Clarke@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/CommunityManagement-Sample-Spec-Kit/sessions/1154b6f0-f12a-43ed-a60d-06a677ae2343
@MikeWedderburn-Clarke
Copy link
Copy Markdown
Member

@copilot CI issues should be fixed now on main. Merge those changes into this branch, fix any issues, and then continue

…ternal-id-implementation

# Conflicts:
#	.github/workflows/ci.yml
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 24, 2026

@copilot CI issues should be fixed now on main. Merge those changes into this branch, fix any issues, and then continue

Merged in babc9e0. The new main commits included:

  • CI bundle size fix — now reads initial chunks from apps/web/.next/server/app/index.html and gzips them (180KB < 200KB ✅)
  • Storybook a11y fix — installs Playwright Chromium before the audit and passes --config-dir apps/web/.storybook to test-storybook
  • Directory URL sync (T026) — filter params now sync to URL; useSearchParams wrapped in Suspense boundary

All 684/684 tests still pass.

…tions

- infra/main.bicep: add entraClientId, entraTenantId (@secure), entraTenantDomain params
- infra/main.parameters.json: bind new params to ENTRA_CLIENT_ID/TENANT_ID/TENANT_DOMAIN env vars
- infra/modules/key-vault.bicep: add entra-client-id and entra-tenant-id Key Vault secrets
- infra/modules/container-apps.bicep: add Key Vault secret refs (ENTRA_CLIENT_ID, ENTRA_TENANT_ID) and plain env var (ENTRA_TENANT_DOMAIN)
- apps/web/src/db/migrate.ts: support MIGRATIONS_DIR env var for bundled runtime path
- Dockerfile: compile migrate.ts with esbuild in build stage, add start.sh to run migrations then server
- start.sh: new entrypoint script

Co-authored-by: MikeWedderburn-Clarke <5323631+MikeWedderburn-Clarke@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/CommunityManagement-Sample-Spec-Kit/sessions/e46b2c7f-7fc7-4498-a63b-ccca2b519eee
@MikeWedderburn-Clarke MikeWedderburn-Clarke marked this pull request as ready for review March 25, 2026 17:22
@MikeWedderburn-Clarke MikeWedderburn-Clarke merged commit 50623a1 into main Mar 25, 2026
5 checks passed
@MikeWedderburn-Clarke MikeWedderburn-Clarke deleted the copilot/plan-entra-external-id-implementation branch March 25, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants