-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Objective
Create the AgentBox Portal — an ASP.NET Core 10 application that serves as the dashboard, REST API, and YARP reverse proxy. All in a single deployment.
Scope
Files to create (ALL in src/portal/ — do NOT touch any other directory):
Project Structure
src/portal/
├── AgentBox.Portal.sln
├── AgentBox.Portal/
│ ├── AgentBox.Portal.csproj
│ ├── Program.cs
│ ├── appsettings.json
│ ├── appsettings.Development.json
│ ├── Dockerfile
│ ├── Controllers/
│ │ ├── BoxesController.cs — CRUD for AgentBox containers
│ │ ├── AuthController.cs — OAuth callbacks (GitHub, ADO, Atlassian)
│ │ └── TokenController.cs — Token status, refresh, shared Copilot token
│ ├── Services/
│ │ ├── AciService.cs — Azure SDK: create/list/delete ACI containers
│ │ ├── BoxMetadataService.cs — Azure Table Storage: track ownership, tokens, grants
│ │ ├── GitHubOAuthService.cs — GitHub App OAuth flow + Copilot license check
│ │ ├── AdoOAuthService.cs — Azure DevOps OAuth flow
│ │ ├── AtlassianOAuthService.cs — Atlassian 3LO OAuth flow
│ │ ├── SharePointService.cs — Site discovery + Sites.Selected grant/revoke
│ │ ├── DataverseService.cs — MI app user provisioning per environment
│ │ ├── SshCertificateService.cs — Key Vault SSH cert signing
│ │ └── YarpConfigService.cs — Dynamic YARP route management
│ ├── Middleware/
│ │ └── BoxAuthorizationMiddleware.cs — Owner/shared/admin access checks
│ ├── Models/
│ │ ├── AgentBoxInstance.cs — Container state model
│ │ ├── SpawnRequest.cs — Spawn form input model
│ │ └── BoxMetadata.cs — Table Storage entity
│ └── ClientApp/ — PLACEHOLDER ONLY (React created by Agent 5)
│ └── .gitkeep
└── AgentBox.Portal.Tests/
└── AgentBox.Portal.Tests.csproj
Key NuGet Packages
Yarp.ReverseProxy— Microsoft's reverse proxyMicrosoft.Identity.Web— Entra ID authAzure.ResourceManager.ContainerInstance— ACI managementAzure.Data.Tables— Table Storage for BoxMetadataAzure.Security.KeyVault.Secrets+Azure.Security.KeyVault.Certificates— Key Vault accessMicrosoft.Graph— SharePoint Sites.Selected + site discoveryAzure.Identity— DefaultAzureCredential for managed identity
Program.cs Key Configuration
builder.Services.AddReverseProxy()with in-memory config provider (YarpConfigService)builder.Services.AddMicrosoftIdentityWebApiAuthentication()— Entra ID bearer token validation- Table Storage client DI registration
- All 9 services registered as scoped/singleton as appropriate
app.UseStaticFiles()— serves React build from wwwroot/app.MapReverseProxy()— YARP catches wildcard subdomainsapp.MapControllers()— API endpoints- Fallback to index.html for SPA routing
API Endpoints
POST /api/boxes— spawn (accepts SpawnRequest with name, selected services, environments, sites)GET /api/boxes— list user's boxesGET /api/boxes/{name}— get box detailsDELETE /api/boxes/{name}— destroy box + cleanup (Dataverse app users, SharePoint grants)GET /api/boxes/{name}/status— health checkPOST /api/boxes/{name}/ssh-certificate— sign SSH public keyGET /auth/github/callback— GitHub OAuth callbackGET /auth/ado/callback— ADO OAuth callbackGET /auth/atlassian/callback— Atlassian OAuth callback
YARP Dynamic Routing
YarpConfigServicemaintains in-memory route config- When box is spawned: add route
{boxName}.agentbox.networg.com→http://{aciIp}:80 - When box is destroyed: remove route
- On startup: load active boxes from Table Storage and rebuild routes
BoxMetadata (Azure Table Storage)
- PartitionKey: userId (Entra ID object ID)
- RowKey: boxName
- Fields: aciResourceId, fqdn, createdAt, expiresAt (5-day TTL), ghToken (encrypted), adoToken, jsmToken, spSites (JSON), dvEnvironments (JSON), sshCertPermissions
Copilot License Check (in GitHubOAuthService)
- After GitHub OAuth: resolve username from token
GET /orgs/NETWORG/members/{username}/copilot→ 200 (has seat) or 404 (no seat)- If no seat: fetch shared Copilot token from Key Vault (secret:
shared-copilot-refresh-token) - Mint fresh 8hr access token from refresh token, inject as
COPILOT_GITHUB_TOKEN - If has seat: use user's own
ghu_*for bothGH_TOKENandCOPILOT_GITHUB_TOKEN
Key Design Decisions
- React SPA served as static files from
wwwroot/(built by Agent 5, copied during Docker build) - Azure Table Storage for BoxMetadata (~$0.01/mo)
- Cloudflare handles TLS at edge — portal runs on HTTP internally
- Short-lived OAuth tokens (8hr) for shared Copilot account — portal refreshes server-side
- Per-container MI preferred for SharePoint — test if Site Permissions API accepts MI appId
Conflict Prevention
This agent works ONLY in src/portal/. Creates ClientApp/.gitkeep as placeholder — Agent 5 fills it.
No other agent touches src/portal/.
Acceptance Criteria
-
dotnet buildsucceeds -
dotnet testsucceeds (at least one placeholder test) - All 9 services have reasonable implementations (can use TODOs for external API calls)
- YARP configured with dynamic in-memory provider
- Portal Dockerfile builds successfully
- appsettings.json has all required configuration sections
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels