Skip to content

CICD and Infra#1

Merged
skyarkitekten merged 9 commits into
mainfrom
test/login
Apr 22, 2026
Merged

CICD and Infra#1
skyarkitekten merged 9 commits into
mainfrom
test/login

Conversation

@skyarkitekten
Copy link
Copy Markdown
Member

This pull request introduces a comprehensive CI/CD pipeline for the project, including new GitHub Actions workflows for continuous integration, deployment, and infrastructure validation. It also adds thorough authentication endpoint tests for the backend API and improves test infrastructure setup. Additionally, the agent definitions for coding and DevOps tasks are enhanced for better tool access and usability.

CI/CD Pipeline and Workflow Automation

  • Added a complete continuous integration workflow (ci.yml) that builds and tests both backend and frontend code, and validates Bicep infrastructure templates.
  • Introduced a reusable deployment workflow (_deploy.yml) to automate environment-specific deployments, including infrastructure provisioning, secrets management, SQL access configuration, and separate backend/frontend deployments.
  • Created a continuous delivery workflow (cd.yml) that builds artifacts and triggers deployments to development and production environments using the reusable deployment workflow.
  • Added an infrastructure validation workflow (infra-validate.yml) that runs Bicep "what-if" analysis on pull requests affecting infrastructure, summarizing changes directly in the workflow output.

Backend Testing Improvements

  • Added comprehensive integration tests for authentication endpoints in AuthEndpointsTests.cs, covering registration, login, duplicate handling, input validation, and authenticated user retrieval.
  • Enhanced the test factory (NotesApiFactory.cs) to support authenticated test clients and properly configure JWT settings for testing scenarios. [1] [2]

Agent Configuration Enhancements

  • Expanded tool access for the "Clean Coder" and "DevOps" agents, and made the DevOps agent user-invocable for more flexible usage in workflows. [1] [2]

Backend:
- Add ASP.NET Identity with ApplicationUser and EF Core migrations
- Add AuthController with register, login, and me endpoints
- Add JwtTokenService for issuing signed access tokens
- Scope NotesController to authenticated user's notes via OwnerId
- Add AuthEndpointsTests, NotesAuthorizationTests, and update
  NotesEndpointsTests to use authenticated clients

Frontend:
- Add AuthProvider, useAuth hook, ProtectedRoute, and token storage
- Add LoginPage and RegisterPage with client-side validation
- Extract shared HTTP client with automatic Bearer header injection
- Update Layout with sign-out button and user email display
- Update MSW handlers and test helpers with auth support
Bicep build output (ARM JSON) should not be committed; it's regenerated on demand by az bicep build and the CI workflow.
Provisions the full stack on cheapest-tier Azure resources:

- Resource group per environment (dev, prod)
- Linux App Service on F1 (Free) with .NET 10, system-assigned MI
- Azure SQL serverless with auto-pause and useFreeLimit, Entra-only auth
- Static Web Apps on Free tier
- Key Vault (RBAC) holding the JWT signing key, referenced via @Microsoft.KeyVault
- Log Analytics + Application Insights

App Service MI is granted Key Vault Secrets User; the SQL contained
user is created by the CD workflow. No secrets are present in source
or in .bicepparam files (values come from env vars at deploy time).

Includes bootstrap/setup-oidc.sh for one-time GitHub OIDC app
registration and subscription role assignments.
- ci.yml: backend (dotnet test), frontend (lint/typecheck/test/build), and bicep build on PR and push to main
- infra-validate.yml: az deployment group what-if on PRs touching infra/**, posted to the PR summary
- cd.yml: orchestrates build-backend + build-frontend, then dev deploy, then prod deploy behind the prod environment's reviewer gate
- _deploy.yml: reusable deploy job that runs bicep, seeds jwt-signing-key in Key Vault if missing, grants the App Service MI access to SQL via sqlcmd + Entra access token, deploys the API zip, rebuilds the SPA with VITE_API_BASE_URL, and uploads to SWA

All Azure auth is OIDC federated; no long-lived client secrets.
Covers architecture, cost breakdown, security model (OIDC + managed identity + Key Vault), repo layout, first-time OIDC bootstrap, GitHub Environment setup, day-to-day operations (what-if, JWT rotation, teardown), the app-code env-var contract, outstanding app-code work, and troubleshooting.
Comment thread backend/Notes.Api/Auth/AuthController.cs Fixed
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds end-to-end CI/CD and Azure infrastructure provisioning for the Notes app, while introducing JWT-based authentication + user-scoped notes in the backend and corresponding auth UX/testing updates in the frontend.

Changes:

  • Added subscription/resource-group scoped Bicep templates + bootstrap scripts to provision App Service, SWA, SQL, Key Vault, and observability resources.
  • Introduced GitHub Actions workflows for CI, infra what-if validation on PRs, and reusable CD deployments (dev → prod).
  • Implemented JWT authentication + ASP.NET Identity in the backend (including migrations and integration tests) and added frontend auth context, routes, and MSW support.

Reviewed changes

Copilot reviewed 63 out of 66 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
infra/modules/workload.bicep Orchestrates workload resource modules and wires outputs between them.
infra/modules/static-web-app.bicep Defines the Static Web App resource (unlinked, deployed via token).
infra/modules/sql.bicep Provisions Azure SQL server/database with Entra admin + firewall settings.
infra/modules/log-analytics.bicep Creates Log Analytics workspace used by App Insights.
infra/modules/key-vault.bicep Creates Key Vault with RBAC enabled and role assignments for deployers.
infra/modules/app-service.bicep Creates Linux App Service + settings for DB/JWT/CORS and KV access.
infra/modules/app-insights.bicep Creates App Insights linked to Log Analytics workspace.
infra/main.prod.bicepparam Prod parameterization via environment variables (no literals).
infra/main.dev.bicepparam Dev parameterization via environment variables (no literals).
infra/main.bicep Subscription-scope entrypoint creating RG + workload module.
infra/bootstrap/setup-oidc.sh Bash bootstrap for GitHub OIDC app/SP + federated creds + role assignments.
infra/bootstrap/setup-oidc.ps1 PowerShell bootstrap equivalent for GitHub OIDC setup.
infra/abbreviations.json Centralized naming prefixes used by Bicep modules.
frontend/src/test/setup.ts Resets auth + notes stores and clears token between tests.
frontend/src/test/server.ts Adds auth store reset/seeding helpers for MSW test server.
frontend/src/test/renderWithProviders.tsx Wraps tests with QueryClient/Router/AuthProvider and seeds auth state.
frontend/src/test/handlers.ts Extends MSW handlers with auth endpoints and per-user note scoping.
frontend/src/pages/RegisterPage.tsx Adds registration page with client-side validation and navigation.
frontend/src/pages/RegisterPage.test.tsx Tests register flow, validation, and server duplicate-email errors.
frontend/src/pages/LoginPage.tsx Adds login page with validation and UnauthorizedError handling.
frontend/src/pages/LoginPage.test.tsx Tests login flows and invalid-credentials behavior.
frontend/src/main.tsx Wraps app in AuthProvider at the application root.
frontend/src/components/Layout.tsx Displays signed-in email and adds sign-out action in header.
frontend/src/components/Layout.test.tsx Tests header email rendering and sign-out behavior.
frontend/src/auth/useAuth.ts Adds useAuth() hook for consuming the auth context.
frontend/src/auth/tokenStorage.ts Adds safe localStorage helpers for token persistence.
frontend/src/auth/authContextInternal.ts Defines auth context/state types and creates context.
frontend/src/auth/ProtectedRoute.tsx Adds route guard that redirects unauthenticated users to /login.
frontend/src/auth/ProtectedRoute.test.tsx Tests redirect vs authenticated rendering behavior.
frontend/src/auth/AuthContext.tsx Implements AuthProvider bootstrap/login/register/logout + HTTP hooks wiring.
frontend/src/api/notes.ts Switches notes API functions to use shared HTTP request helper.
frontend/src/api/http.ts Introduces shared request wrapper with auth header injection + 401 handling.
frontend/src/api/auth.ts Adds typed auth API client (register/login/me).
frontend/src/App.tsx Adds login/register routes and protects notes routes behind ProtectedRoute.
docs/deployment.md Documents Azure architecture, CI/CD, bootstrap steps, and operational runbooks.
backend/Notes.Api/appsettings.json Adds Jwt config section for issuer/audience/signing key/lifetime.
backend/Notes.Api/appsettings.Development.json Adds dev-only JWT signing key override.
backend/Notes.Api/Program.cs Wires authentication middleware and auth service registration.
backend/Notes.Api/Notes.Api.csproj Adds JWT bearer auth + ASP.NET Identity EF Core packages.
backend/Notes.Api/Migrations/Sqlite/NotesDbContextModelSnapshot.cs Updates SQLite snapshot for Identity + Note owner relationship.
backend/Notes.Api/Migrations/Sqlite/20260418001323_AddIdentityAndNoteOwner.cs Adds Identity tables + Note.OwnerId for SQLite provider.
backend/Notes.Api/Migrations/Sqlite/20260418001323_AddIdentityAndNoteOwner.Designer.cs EF designer for the SQLite migration.
backend/Notes.Api/Migrations/SqlServer/20260418000922_AddIdentityAndNoteOwner.cs Adds Identity tables + Note.OwnerId for SQL Server provider.
backend/Notes.Api/Migrations/SqlServer/20260418000922_AddIdentityAndNoteOwner.Designer.cs EF designer for the SQL Server migration.
backend/Notes.Api/Extensions/ServiceCollectionExtensions.cs Adds AddAppAuthentication() and JWT/Identity service configuration.
backend/Notes.Api/Domain/Note.cs Adds OwnerId to Note domain model.
backend/Notes.Api/Domain/ApplicationUser.cs Adds ApplicationUser Identity model.
backend/Notes.Api/Data/NotesDbContext.cs Switches DbContext to IdentityDbContext and calls base.OnModelCreating().
backend/Notes.Api/Data/Configurations/NoteConfiguration.cs Enforces OwnerId required, indexed, and FK to ApplicationUser.
backend/Notes.Api/Controllers/NotesController.cs Requires auth and scopes all note operations to the current user.
backend/Notes.Api/Auth/JwtTokenService.cs Issues JWT access tokens with standard claims.
backend/Notes.Api/Auth/JwtOptions.cs Adds strongly-typed JWT options binding target.
backend/Notes.Api/Auth/IJwtTokenService.cs Defines token service interface.
backend/Notes.Api/Auth/AuthController.cs Adds register/login/me endpoints backed by Identity + JWT issuance.
backend/Notes.Api/Auth/AuthContracts.cs Adds request/response DTOs for auth endpoints.
backend/Notes.Api.Tests/NotesEndpointsTests.cs Updates notes tests to use authenticated clients.
backend/Notes.Api.Tests/NotesAuthorizationTests.cs Adds tests for 401 without token and per-owner data isolation.
backend/Notes.Api.Tests/NotesApiFactory.cs Adds JWT config + helpers to create authenticated test clients.
backend/Notes.Api.Tests/AuthEndpointsTests.cs Adds integration tests for register/login/me endpoints.
.gitignore Ignores compiled Bicep JSON artifacts under infra/.
.github/workflows/infra-validate.yml Adds PR what-if workflow for infra changes with step summary output.
.github/workflows/ci.yml Adds CI jobs for backend, frontend, and Bicep build validation.
.github/workflows/cd.yml Adds CD pipeline that builds artifacts and deploys dev/prod via reusable workflow.
.github/workflows/_deploy.yml Adds reusable deploy job: infra, KV seeding, SQL grants, API deploy, SWA deploy.
.github/agents/devops.agent.md Updates DevOps agent tool access and makes it user-invocable.
.github/agents/coder.agent.md Updates Clean Coder agent tool access list.
Files not reviewed (2)
  • backend/Notes.Api/Migrations/SqlServer/20260418000922_AddIdentityAndNoteOwner.Designer.cs: Language not supported
  • backend/Notes.Api/Migrations/Sqlite/20260418001323_AddIdentityAndNoteOwner.Designer.cs: Language not supported
Comments suppressed due to low confidence (1)

backend/Notes.Api/Program.cs:17

  • CORS is still hardcoded to http://localhost:5173, but the infra sets Cors__AllowedOrigins__0 to the SWA hostname. In Azure this will block browser calls. Read allowed origins from configuration (e.g., Cors:AllowedOrigins) and use those in the CORS policy.
builder.Services.AddCors(options =>
{
    options.AddPolicy("Frontend", policy => policy
        .WithOrigins("http://localhost:5173")
        .AllowAnyHeader()
        .AllowAnyMethod());

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +73
ftpsState: 'Disabled'
healthCheckPath: '/health'
cors: {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

healthCheckPath is set to /health, but the API project doesn’t currently map a health endpoint (no MapHealthChecks / controller route). App Service health probes will get 404 and can mark instances unhealthy. Either add a real /health endpoint in the backend or remove/parameterize healthCheckPath.

Copilot uses AI. Check for mistakes.
Comment on lines +128 to +135
resource kvRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, site.id, secretsUserRoleId)
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', secretsUserRoleId)
principalId: site.identity.principalId
principalType: 'ServicePrincipal'
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The role assignment name is derived from site.id, but the assigned principal is site.identity.principalId. If the system-assigned identity is ever re-created (principalId changes) while the site resource ID stays the same, the role assignment name will stay constant and the deployment can fail because role assignments are immutable. Use site.identity.principalId in the guid(...) input to keep the name tied to the principal.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +111
# Allow this runner's public IP temporarily so sqlcmd can connect.
runner_ip=$(curl -s https://api.ipify.org)
az sql server firewall-rule create \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--server "$SQL_SERVER" \
--name "gh-runner-${{ github.run_id }}" \
--start-ip-address "$runner_ip" \
--end-ip-address "$runner_ip" >/dev/null

# Install sqlcmd (go edition).
curl -fsSL https://github.com/microsoft/go-sqlcmd/releases/download/v1.8.0/sqlcmd-linux-amd64.tar.bz2 \
| tar -xj -C /tmp
sudo mv /tmp/sqlcmd /usr/local/bin/sqlcmd

# Acquire access token as the deployer SP (already logged in via OIDC).
token=$(az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv)

cat <<SQL > /tmp/grant.sql
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'${APP_NAME}')
BEGIN
CREATE USER [${APP_NAME}] FROM EXTERNAL PROVIDER;
END
ALTER ROLE db_datareader ADD MEMBER [${APP_NAME}];
ALTER ROLE db_datawriter ADD MEMBER [${APP_NAME}];
ALTER ROLE db_ddladmin ADD MEMBER [${APP_NAME}];
SQL

sqlcmd -S "$fqdn" -d "$SQL_DB" --access-token "$token" -i /tmp/grant.sql

# Clean up firewall rule.
az sql server firewall-rule delete \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--server "$SQL_SERVER" \
--name "gh-runner-${{ github.run_id }}" >/dev/null
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SQL firewall rule is created and deleted inline, but if sqlcmd (or any earlier command) fails, the cleanup step won’t run and the runner IP rule will be left behind. Add a trap (or finally-style cleanup) to ensure the firewall rule is removed on all exit paths.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +57
az deployment group what-if \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--template-file infra/main.bicep \
--parameters infra/main.dev.bicepparam \
--no-pretty-print \
> whatif.txt || true
{
echo '### Bicep what-if (dev)';
echo '```';
cat whatif.txt;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The what-if command is suffixed with || true, so an auth/configuration failure in az deployment group what-if won’t fail the workflow and the step summary will likely be empty (errors go to stderr). For infra validation, it’s better to fail the job on what-if errors (or at least capture stderr and surface it explicitly).

Suggested change
az deployment group what-if \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--template-file infra/main.bicep \
--parameters infra/main.dev.bicepparam \
--no-pretty-print \
> whatif.txt || true
{
echo '### Bicep what-if (dev)';
echo '```';
cat whatif.txt;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
set +e
az deployment group what-if \
--resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \
--template-file infra/main.bicep \
--parameters infra/main.dev.bicepparam \
--no-pretty-print \
> whatif.txt 2>&1
whatif_exit_code=$?
set -e
{
echo '### Bicep what-if (dev)';
echo '```';
cat whatif.txt;
echo '```';
} >> "$GITHUB_STEP_SUMMARY"
if [ "$whatif_exit_code" -ne 0 ]; then
exit "$whatif_exit_code"
fi

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +20
nullable: false,
defaultValue: "");
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OwnerId is added as non-nullable with a default empty string, and later a foreign key is added to AspNetUsers(Id). If the Notes table already contains rows, those existing rows will have OwnerId = '' and the FK addition will fail. Consider a two-step migration (nullable column + backfill + make non-null), or explicitly clean/assign existing rows before adding the FK constraint.

Suggested change
nullable: false,
defaultValue: "");
nullable: true);

Copilot uses AI. Check for mistakes.
Comment thread frontend/src/api/http.ts
Comment on lines +36 to +47
export async function request<T>(url: string, init?: RequestInit): Promise<T> {
const headers = new Headers(init?.headers)
if (!headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json')
}
const token = getToken()
if (token && !headers.has('Authorization')) {
headers.set('Authorization', `Bearer ${token}`)
}

const response = await fetch(url, { ...init, headers })

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request() uses fetch(url, ...) with whatever relative path the caller passes. In deployed SWA (unlinked) the frontend needs to call the App Service hostname, so relative /api/... requests will hit the SWA origin and fail. Prefix requests with import.meta.env.VITE_API_BASE_URL (while still allowing absolute URLs) before calling fetch.

Copilot uses AI. Check for mistakes.
Comment thread infra/modules/sql.bicep
Comment on lines +35 to +70
resource server 'Microsoft.Sql/servers@2023-08-01-preview' = {
name: serverName
location: location
tags: tags
identity: {
type: 'SystemAssigned'
}
properties: {
// Entra-only authentication. No SQL auth, no passwords.
administrators: {
administratorType: 'ActiveDirectory'
azureADOnlyAuthentication: true
login: adminPrincipalName
sid: adminPrincipalId
principalType: adminPrincipalType
tenantId: tenant().tenantId
}
minimalTlsVersion: '1.2'
publicNetworkAccess: 'Enabled'
restrictOutboundNetworkAccess: 'Disabled'
version: '12.0'
}
}

// Allow Azure services (App Service outbound IPs) to reach this server.
resource allowAzure 'Microsoft.Sql/servers/firewallRules@2023-08-01-preview' = {
name: 'AllowAllWindowsAzureIps'
parent: server
properties: {
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
}

resource database 'Microsoft.Sql/servers/databases@2023-08-01-preview' = {
name: databaseName
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module uses the Microsoft.Sql/*@2023-08-01-preview API versions. Preview API versions can introduce breaking schema changes and are risky for long-lived infra (especially prod). Prefer a stable (non--preview) API version for servers, firewallRules, and databases if one is available.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
"github/*",
"github/*",
todo,
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frontmatter tools: value is written as a flow sequence with a trailing comma (e.g., todo, before ]). Many YAML parsers reject trailing commas, which would make this agent definition invalid. Remove trailing commas (and the duplicated "github/*" entries) to ensure the frontmatter parses reliably.

Suggested change
"github/*",
"github/*",
todo,
todo

Copilot uses AI. Check for mistakes.
com.microsoft/azure/search,
"playwright/*",
"azure-mcp/*",
todo,
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The frontmatter tools: list uses a bracketed flow sequence with a trailing comma (todo, before the closing ]). If the agent config loader uses a strict YAML parser, this can fail to parse. Remove trailing commas in the flow sequence to avoid breaking agent loading.

Suggested change
todo,
todo

Copilot uses AI. Check for mistakes.
Comment on lines 30 to 34

app.UseHttpsRedirection();
app.UseCors("Frontend");
app.UseAuthentication();
app.UseAuthorization();
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pipeline/infra expects migrations to run in App Service (it sets RUN_MIGRATIONS_ON_START=true while ASPNETCORE_ENVIRONMENT=Production), but this file currently runs db.Database.Migrate() only under IsDevelopment(). That means deployed environments won’t migrate and can fail at runtime. Consider gating migrations on RUN_MIGRATIONS_ON_START (or moving migration execution into the deploy workflow).

Copilot uses AI. Check for mistakes.
@skyarkitekten skyarkitekten merged commit 298446a into main Apr 22, 2026
4 of 6 checks passed
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