M3: BYOK — encrypted owner-only API configs + model adapters
SharedKernel: Autonomy dial enum; IModelClient (ModelRequest/ModelCompletion);
IApiConfigResolver (+ ApiConfigSummary/ResolvedApiConfig) — server-side, decrypted.
Integrations module:
- ApiConfig entity (org-scoped) + IntegrationsDbContext (schema "integrations") +
InitialIntegrations migration; the key is AES-256-GCM encrypted at rest (key derived from
Encryption:MasterKey) and never returned to a client.
- Model adapters: StubModelClient (no-network, provider "stub"/"echo"), an OpenAI-compatible
HTTP adapter, and a ModelClientRouter; ApiConfigResolver decrypts server-side only.
- Endpoints: POST/GET/DELETE /api/integrations/api-configs and POST .../{id}/test. Create/
test/delete require ManageApiKeys (owner); listing requires ConfigureAgents (assign-only,
no key). Dev master key in appsettings; override Encryption__MasterKey in prod.
Verified: build green; ArchitectureTests 8/8 (Integrations references only SharedKernel);
IntegrationTests 26/26 incl. a BYOK flow — key never appears in any response, the connection
test succeeds (stub), and a Member is 403'd from create + list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TeamUp.Modules.Integrations.Domain;
|
||||
using TeamUp.SharedKernel.Persistence;
|
||||
|
||||
namespace TeamUp.Modules.Integrations.Persistence;
|
||||
|
||||
internal sealed class IntegrationsDbContext(DbContextOptions<IntegrationsDbContext> options)
|
||||
: DbContext(options), IModuleDbContext
|
||||
{
|
||||
public DbSet<ApiConfig> ApiConfigs => Set<ApiConfig>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("integrations");
|
||||
|
||||
modelBuilder.Entity<ApiConfig>(config =>
|
||||
{
|
||||
config.ToTable("api_configs");
|
||||
config.HasKey(c => c.Id);
|
||||
config.Property(c => c.Name).HasMaxLength(120).IsRequired();
|
||||
config.Property(c => c.Provider).HasMaxLength(60).IsRequired();
|
||||
config.Property(c => c.Model).HasMaxLength(120).IsRequired();
|
||||
config.Property(c => c.Endpoint).HasMaxLength(500);
|
||||
config.Property(c => c.EncryptedKey).IsRequired();
|
||||
config.HasIndex(c => c.OrganizationId);
|
||||
config.HasIndex(c => new { c.OrganizationId, c.Name }).IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user