DbCloudConfig.LinqToDb 1.0.9

dotnet add package DbCloudConfig.LinqToDb --version 1.0.9
                    
NuGet\Install-Package DbCloudConfig.LinqToDb -Version 1.0.9
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DbCloudConfig.LinqToDb" Version="1.0.9" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DbCloudConfig.LinqToDb" Version="1.0.9" />
                    
Directory.Packages.props
<PackageReference Include="DbCloudConfig.LinqToDb" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DbCloudConfig.LinqToDb --version 1.0.9
                    
#r "nuget: DbCloudConfig.LinqToDb, 1.0.9"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DbCloudConfig.LinqToDb@1.0.9
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DbCloudConfig.LinqToDb&version=1.0.9
                    
Install as a Cake Addin
#tool nuget:?package=DbCloudConfig.LinqToDb&version=1.0.9
                    
Install as a Cake Tool

DB Cloud Config

DB Cloud Config adalah editor konfigurasi koneksi database berbasis .NET 10:

  • DbCloudConfig.Api: API Opx.Api.Web, default port 9003.
  • DbCloudConfig.Ui: Blazor Interactive Server, default HTTP development 4547.
  • DbCloudConfig.Client: wrapper Opx.Api.Client dengan IOpxApiClientFactory, HTTP/2, dan policy RequestVersionOrLower.
  • DbCloudConfig.Jwt: penerbit dan validator JWT HS256 bersama. Default session editor 15 menit (900 detik) dan batas maksimum 3600 detik.
  • DbCloudConfig.Contracts: kontrak API dan envelope AES-256-GCM.
  • DbCloudConfig.Configuration: loader snapshot untuk memasukkan connection string dan metadata/secret ke IConfiguration.
  • DbCloudConfig.AspNetCore: registrasi DI client dan health check ASP.NET Core.
  • DbCloudConfig.LinqToDb: shared loader cloud, decrypt delivery, mapper provider LinqToDB, dan helper setup AppDbContext.

Panduan pemasangan pertama tersedia di docs/INITIAL-INSTALLATION.md. Panduan tersebut mencakup Development, IIS Production, setup key, Create new, Import existing, permission folder, smoke test, dan troubleshooting.

Package NuGet DbCloudConfig.Client menggunakan versi 1.0.9, author opx, dan copyright Copyright © 2026 opx. Build Release tidak menghasilkan PDB. Untuk membuat package:

dotnet pack .\src\DbCloudConfig.Client\DbCloudConfig.Client.csproj -c Release -o .\artifacts\packages

Alternatif Command Prompt:

tools\Pack-DbCloudConfigClient.cmd

Seluruh response backend mengikuti envelope OPX: {"result":true|false,"data":...,"statusCode":"..."}. Payload sukses maupun detail error selalu berada di data, termasuk validasi model, exception controller, JWT 401/403, dan route yang tidak ditemukan. HTTP status asli tetap dipertahankan.

OPX AuthorizationGuard backend aktif secara default. Endpoint controller yang memerlukan autentikasi tetap dilindungi Bearer JWT, sedangkan endpoint status, setup/login, health, Swagger (Development), dan machine-token yang diberi [AllowAnonymous] tetap dapat diakses tanpa token.

Database OSF default berada di <content-root-api>/App_Data/cloud-config.osf. Lokasinya dapat diubah melalui CloudConfig:DbPath atau environment variable CloudConfig__DbPath. Setting aktif disimpan backend pada <content-root-api>/App_Data/cloud-config.settings.json. File settings hanya berisi path database aktif, token expiry, batas waktu delivery read-only, flag encrypt output, backend port, profil company, dan master DBOwner; password dan key tidak pernah disimpan di sana.

API tidak membuat database saat startup. Jika file tidak ditemukan, statusnya NotSetup dan UI menampilkan dua mode Initial Setup:

  • Create new membuat database kosong menggunakan setup key, nama file .osf, dan password baru.
  • Import existing mengunggah database DB Cloud Config OSF terenkripsi yang sudah ada menggunakan setup key dan password database tersebut.

Import dibatasi 64 MB dan hanya menerima nama file aman. Backend menulis unggahan ke file sementara dalam folder DbPath, membuka dan memvalidasi password/schema, menjalankan migrasi ENV legacy yang didukung, lalu mengaktifkannya secara atomik. File target yang sudah ada tidak pernah ditimpa. Payload gagal, password salah, schema tidak didukung, path absolut, dan traversal ditolak serta file sementara dibersihkan.

Jika file aktif ditemukan, statusnya Ready dan UI meminta password database. Initial Setup dan import tidak lagi tersedia setelah database aktif.

Backend mencoba membuka dan memvalidasi database OSF secara otomatis saat API startup. Password database disimpan sebagai file DPAPI protected pada backend; plaintext tidak ditulis ke appsettings.json, source, atau log. Database lama yang belum mempunyai protected password cukup melakukan satu login berhasil. Login tersebut memprovisi protected password sehingga restart API berikutnya dapat memuat OSF dan memastikan schema secara otomatis.

Auto-load backend tidak membuat sesi editor dan tidak melewati autentikasi UI. Status tetap RequiresLogin=true; pengguna tetap harus login untuk memperoleh JWT editor. Jika protected password tidak tersedia, tidak dapat didekripsi oleh service identity, atau tidak lagi sesuai dengan database, API tetap hidup dan meminta login manual.

Selain tabel CLOUD_CONFIG, setiap database baru memiliki tabel legacy ENV untuk konfigurasi key-value:

ENV
  OWNER_KEY   string(100), primary key (internal normalized owner)
  NAME        string(128), primary key
  OWNER       string(100), nullable
  CATEGORY    string(100)
  VALUE       string(2048)
  IS_ACTIVE   boolean

Saat database lama berhasil dibuka menggunakan password yang benar, backend menambahkan tabel ENV secara otomatis jika belum tersedia. Tabel ENV lama dengan primary key Name saja dimigrasikan ke primary key komposit, dengan record lama tetap ownerless. Record CLOUD_CONFIG yang sudah ada tidak diubah.

Model keamanan UI

Signing secret JWT tidak pernah dikirim ke browser dan tidak terdapat pada appsettings.json. UI berjalan sebagai Blazor Interactive Server dan wrapper DbCloudConfig.Client berjalan pada server UI dalam scope circuit pengguna. JWT hasil login dan key payload tetap berada di memori server UI; signing secret tetap hanya berada di environment API.

Saat setup atau login:

  1. Wrapper membuat key AES acak 32 byte.
  2. Wrapper mengirim password OSF dan public session input melalui HTTPS.
  3. API memvalidasi password lalu menerbitkan JWT menggunakan expiry aktif (default 15 menit/900 detik) melalui DbCloudConfig.Jwt.
  4. API mengikat JWT ke password dan key sesi dalam store memori terenkripsi.
  5. Response detail konfigurasi dienkripsi AES-256-GCM dengan key sesi; wrapper mendekripsinya pada server UI sebelum data dirender oleh komponen Blazor. Administrator dapat mematikan lapisan output ini; transport HTTPS tetap wajib untuk deployment.

Password form dikirim melalui circuit SignalR Blazor dan tidak disimpan ke localStorage atau sessionStorage. JWT dan key sesi hanya berada di memori scope circuit pada server UI. Saat circuit berakhir atau token kedaluwarsa, UI meminta login kembali. Deployment wajib memakai HTTPS pada UI dan API serta melindungi koneksi SignalR dari penyadapan.

DbCloudConfig.Jwt tetap menjadi source of truth penerbitan dan validasi token di backend. Wrapper sengaja memperlakukan token sebagai opaque backend-issued credential; memasukkan symmetric signing secret ke browser atau menerima “random token” tanpa signature akan membuat autentikasi dapat dipalsukan.

Konfigurasi dan menjalankan lokal

Buat dua nilai acak yang berbeda untuk JWT secret dan setup key:

$jwtBytes = New-Object byte[] 32
$setupBytes = New-Object byte[] 32
$rng = [Security.Cryptography.RandomNumberGenerator]::Create()
try {
    $rng.GetBytes($jwtBytes)
    $rng.GetBytes($setupBytes)
}
finally {
    $rng.Dispose()
}
$env:DB_CLOUD_CONFIG_JWT_SECRET_KEY = [Convert]::ToBase64String($jwtBytes)
$env:DB_CLOUD_CONFIG_SETUP_KEY = [Convert]::ToBase64String($setupBytes)

Untuk IIS Production, buat JWT langsung pada Machine scope tanpa bergantung pada User environment Administrator:

tools\Set-DbCloudConfigJwtMachine.cmd generate --restart-iis

Tool tidak mencetak atau menyalin secret. Gunakan aksi check untuk memastikan Machine scope sudah terkonfigurasi tanpa memperlihatkan nilainya.

Setup key minimal 16 karakter dan hanya digunakan saat file database belum ada. Hanya setup key terakhir yang diterbitkan backend tersebut yang berlaku; generate atau replace berikutnya langsung membatalkan key sebelumnya. Untuk Create new, nama file dan password database dibuat melalui form UI. Untuk Import existing, pilih file OSF lama dan masukkan password yang mengenkripsi file tersebut. Default nama file backend adalah cloud-config.osf.

Pada environment Development, UI menyediakan tombol Generate setup key. API hanya melayani generator tersebut dari koneksi loopback dan hanya selama database berstatus NotSetup. Key kriptografis yang baru langsung diisikan ke form dan disinkronkan ke environment proses API. Endpoint generator tidak tersedia pada production. Production tetap harus menerima setup key melalui DB_CLOUD_CONFIG_SETUP_KEY atau memakai autentikasi administrator eksternal sebelum generator production dapat diaktifkan dengan aman.

Jalankan API:

dotnet run --project .\src\DbCloudConfig.Api\DbCloudConfig.Api.csproj --no-launch-profile --urls http://localhost:9003

Jalankan UI Interactive Server pada terminal lain:

dotnet run --project .\src\DbCloudConfig.Ui\DbCloudConfig.Ui.csproj --launch-profile http

Buka http://localhost:4547 untuk development. Backend URL UI berada di src/DbCloudConfig.Ui/appsettings.json:

{
  "DbCloudConfigClient": {
    "HttpClientName": "db-cloud-config",
    "BaseAddress": "http://localhost:9003",
    "ClientId": "db-cloud-config-ui",
    "TimeoutSeconds": 30
  }
}

Konfigurasi repository memakai HTTP hanya untuk komunikasi loopback development antara UI Server dan API. Sebelum deployment, gunakan endpoint HTTPS production. Konfigurasi UI Server dapat dioverride dengan environment variable DbCloudConfigClient__BaseAddress. Environment variable berikut tetap dapat dipakai untuk konfigurasi API:

$env:CloudConfig__DbPath = "D:\DbCloudConfig\data\cloud-config.osf"
$env:CloudConfig__SettingsPath = "D:\DbCloudConfig\data\cloud-config.settings.json"

Identity yang menjalankan API membutuhkan permission read/write/modify pada folder DbPath. UI tidak memiliki akses ke file OSF. Karena panggilan wrapper berjalan server-to-server, browser tidak mengakses origin API secara langsung dan CORS tidak diperlukan untuk alur UI ini.

Jika circuit Interactive Server terputus, UI menampilkan progress bar reconnect tipis di bagian atas beserta nomor percobaan. Bar hilang otomatis setelah koneksi pulih. Jika reconnect gagal atau circuit ditolak, bar berubah menjadi status gagal dan menyediakan tautan Muat ulang.

Pada halaman editor, status sesi dan menu overflow tiga titik ditempatkan pada AppBar global. Aksi Settings dan Keluar berada di dalam menu tersebut. Tombol Tambah setting tetap berada pada header halaman tepat di atas grid; pada mobile tombol memenuhi lebar header agar mudah disentuh.

Wrapper

Registrasi wrapper:

services.AddDbCloudConfigClient(configuration);

Konfigurasi machine certificate berada seluruhnya di appsettings.json consumer API. Untuk file PFX:

{
  "DbCloudConfigClient": {
    "HttpClientName": "db-cloud-config",
    "BaseAddress": "https://cloud-config.example",
    "ClientId": "consumer-api",
    "TimeoutSeconds": 30,
    "MachineAuthentication": {
      "AssertionAudience": "db-cloud-config-machine-token",
      "CertificatePath": "certificates/consumer-api.pfx",
      "CertificatePasswordEnvironmentVariable": "CONSUMER_API_PFX_PASSWORD"
    }
  }
}

Path relatif PFX dihitung dari folder binary/publish aplikasi (AppContext.BaseDirectory); path absolut juga didukung. Password PFX tidak boleh ditulis ke appsettings dan hanya dibaca dari environment variable yang namanya dikonfigurasi. Format flat lama MachineClientCertificatePath, MachineClientCertificatePasswordEnvironmentVariable, dan MachineAssertionAudience tetap didukung.

Untuk Production Windows/IIS, private certificate dapat dipilih langsung dari Windows Certificate Store sehingga proses tidak memerlukan password PFX pada environment variable:

{
  "DbCloudConfigClient": {
    "HttpClientName": "db-cloud-config",
    "BaseAddress": "https://cloud-config.example",
    "ClientId": "consumer-api",
    "TimeoutSeconds": 30,
    "MachineAuthentication": {
      "AssertionAudience": "db-cloud-config-machine-token",
      "CertificateStore": {
        "StoreName": "My",
        "StoreLocation": "LocalMachine",
        "Thumbprint": "EXACT_CERTIFICATE_THUMBPRINT"
      }
    }
  }
}

CertificatePath dan CertificateStore saling eksklusif. Store lookup memakai exact thumbprint setelah spasi dan tanda : dinormalisasi; Subject Name tidak digunakan karena dapat ambigu. Store mode hanya didukung pada Windows dan menolak CertificatePasswordEnvironmentVariable. Certificate harus memiliki private key RSA/ECDSA yang masih berlaku dan identity proses harus mempunyai izin read pada private key tersebut. Untuk IIS gunakan LocalMachine\My dan berikan izin hanya kepada IIS AppPool\<NamaAppPool>. Panduan lengkap tersedia di docs/WINDOWS-CERTIFICATE-STORE.md.

Alur penggunaan:

var status = await client.GetStatusAsync(cancellationToken);

if (status.RequiresSetup)
    await client.SetupAsync(setupKey, databasePassword, cancellationToken);
else
    await client.LoginAsync(databasePassword, cancellationToken);

var rows = await client.ListAsync(cancellationToken: cancellationToken);
var detail = await client.GetAsync(
    "PRODUCTION",
    "PRIMARY",
    cancellationToken);

Untuk workload/API machine, wrapper menyediakan operasi satu-panggilan. Method ini memuat certificate dari PFX ephemeral atau Windows Certificate Store, membuat assertion, mengambil JWT machine, memanggil API, dan mendekripsi delivery. Sesi machine untuk resource yang sama digunakan kembali sampai mendekati kedaluwarsa:

var detail = await client.GetMachineConfigurationAsync(
    "PRODUCTION",
    "PRIMARY",
    cancellationToken);

var productionCatalog = await client.ListMachineDatabaseConfigurationsAsync(
    "PRODUCTION",
    "PRIMARY",
    "PRODUCTION",
    cancellationToken);

Katalog machine hanya mengembalikan metadata konfigurasi database pada environment yang sama dengan resource autentikasi machine. Endpoint GET /api/v1/client/database-configs?environment=PRODUCTION tidak mengirim connection string atau payload secret dan tetap memerlukan JWT machine aktif.

Untuk library lama yang hanya membaca Windows environment variable, simpan nilainya sebagai record ENV aktif di OSF lalu konfigurasi bootstrap berikut:

{
  "DbCloudConfigProcessEnvironment": {
    "AuthenticationEnvironment": "PRODUCTION",
    "AuthenticationDatabaseName": "FINAC",
    "Group": "UNGROUPED",
    "Owner": "HRIS",
    "NamePrefix": "",
    "OverwriteExisting": false,
    "MaximumVariables": 256,
    "RequiredNames": [
      "OCR_API_KEY",
      "Ocr__BaseUrl"
    ]
  }
}

Panggil bootstrap sebelum library tersebut digunakan:

using var scope = app.Services.CreateScope();
var cloudClient =
    scope.ServiceProvider.GetRequiredService<IDbCloudConfigClient>();
var processOptions =
    DbCloudConfigProcessEnvironmentOptions.FromConfiguration(
        app.Configuration);

var result =
    await cloudClient.ApplyMachineEnvironmentVariablesToProcessAsync(
        processOptions,
        app.Lifetime.ApplicationStopping);

Helper hanya menulis EnvironmentVariableTarget.Process; tidak menulis scope Windows User/Machine atau registry dan tidak membutuhkan Administrator. Seluruh payload aktif serta RequiredNames divalidasi sebelum mutasi. Default tidak menimpa nilai process yang sudah ada; jika penerapan gagal di tengah, perubahan yang sudah dilakukan di-roll back. Result hanya berisi jumlah, bukan nama atau value secret. Certificate machine tetap harus memiliki grant ENV legacy yang sesuai (UNGROUPED untuk record baru dari editor).

ENV yang dimuat merupakan startup snapshot. Consumer API mengambilnya satu kali sebelum service yang bergantung pada konfigurasi mulai menerima request. Wrapper tidak melakukan polling, refresh realtime, atau request Cloud Config pada setiap request aplikasi. Setelah operator mengubah ENV di OSF, restart consumer API agar snapshot baru dimuat. Startup harus gagal tertutup bila required key tidak tersedia atau delivery tidak dapat divalidasi.

Wrapper tidak membuat HttpClient langsung. Named client dibuat oleh Opx.Api.Client, default request memakai HTTP/2 dan dapat turun ke versi lebih rendah bila server/proxy tidak mendukung HTTP/2. Pada Interactive Server, request dijalankan oleh HttpClient server UI melalui factory yang sama.

Shared configuration packages

DbCloudConfig.Configuration dapat memetakan metadata record menjadi key IConfiguration. Loader melakukan machine authentication, membaca dan mendekripsi delivery melalui DbCloudConfig.Client, serta menolak record yang inaktif.

var snapshot = await DbCloudConfigConfigurationLoader.LoadAsync(
    cloudClient,
    new DbCloudConfigConfigurationOptions
    {
        Environment = "PRODUCTION",
        Name = "AI-CHAT",
        KeyPrefix = "Cloud"
    },
    cancellationToken);

configurationBuilder.AddDbCloudConfigSnapshot(snapshot);
var apiKey = configurationBuilder.Build()["Cloud:SUMOPOD_AI_API_KEY"];

Metadata seperti SUMOPOD_AI_API_KEY tersedia sebagai key konfigurasi tanpa harus disimpan pada environment variable aplikasi. Connection string tersedia sebagai ConnectionStrings:{Name}. Jangan menulis snapshot, value, atau IConfiguration yang mengandung secret ke log.

Tabel ENV dapat dimuat sebagai konfigurasi aktif menggunakan wrapper dan package DbCloudConfig.Configuration:

var envSnapshot = await DbCloudConfigEnvironmentVariablesLoader.LoadAsync(
    cloudClient,
    new DbCloudConfigEnvironmentVariablesOptions
    {
        Environment = "DEVELOPMENT",
        Group = "OCR",
        Owner = "HRIS",
        KeyPrefix = "Cloud",
        AuthenticateMachine = true,
        MachineEnvironment = "DEVELOPMENT",
        MachineDatabaseName = "OCR"
    },
    cancellationToken);

configurationBuilder.AddDbCloudConfigEnvironmentVariablesSnapshot(envSnapshot);
var baseUrl = configurationBuilder.Build()["Cloud:Ocr:BaseUrl"];

Nama dengan pemisah ganda mengikuti konvensi konfigurasi .NET: Ocr__BaseUrl menjadi Ocr:BaseUrl. Opsi ini dapat dinonaktifkan. MaximumVariables default 256 dan dibatasi maksimal 1024 untuk mencegah pembacaan secret tanpa batas. Key hasil normalisasi yang duplikat ditolak. Wrapper juga menyediakan GetActiveEnvironmentVariablesAsync untuk consumer yang membutuhkan payload aktif tanpa membangun IConfiguration. Untuk mengambil satu nilai aktif berdasarkan NAME, gunakan helper berikut:

var ocrApiKey = await cloudClient.GetMachineEnvironmentVariableValueAsync(
    "DEVELOPMENT",
    "FINAC",
    "OCr_ApiKey",
    "HRIS",
    cancellationToken);

Certificate machine harus memiliki izin Group milik variabel tersebut. Helper menolak record inaktif dan hanya mengembalikan VALUE; jangan menulis hasilnya ke log, response diagnostik, atau exception.

Machine client hanya dapat membaca Group ENV yang tercantum pada policy certificate-nya. Endpoint list machine wajib menyebut satu group, tidak boleh meminta inactive data, dan PUT/DELETE tetap khusus sesi editor. Group dapat ditentukan saat membuat certificate melalui menu Machine Access. Policy lama tanpa Group ENV tetap valid tetapi tidak memperoleh akses ENV.

Untuk ASP.NET Core:

builder.Services.AddDbCloudConfigAspNetCore(builder.Configuration);
builder.Services.AddHealthChecks();

Health check hanya membaca status backend dan tidak memasukkan secret ke output. Dependency package shared mengikuti urutan topologis berikut:

DbCloudConfig.Contracts
└── DbCloudConfig.Client
    ├── DbCloudConfig.Configuration
    │   └── DbCloudConfig.AspNetCore
    └── DbCloudConfig.LinqToDb

DbCloudConfig.AspNetCore mempertahankan direct dependency ke Client karena implementasinya menggunakan IDbCloudConfigClient secara langsung, selain memakai DbCloudConfig.Configuration.

Untuk clean Release dan pack seluruh package dalam urutan dependency:

tools\Pack-DbCloudConfigAll.cmd --clean

Opsi --clean menjalankan dotnet clean Release hanya pada lima project package di graph tersebut, menghapus package DbCloudConfig.*.nupkg lama dalam artifacts\packages, lalu membangun ulang versi source saat ini tanpa auto-increment tambahan. Project aplikasi, dependency source eksternal, OSF, konfigurasi, dan artifact non-DB-Cloud tidak dibersihkan. Jalankan builder tanpa --clean hanya ketika memang menerbitkan set versi berikutnya.

Builder individual tetap tersedia dan memakai auto-increment:

tools\Pack-DbCloudConfigContracts.cmd
tools\Pack-DbCloudConfigClient.cmd
tools\Pack-DbCloudConfigConfiguration.cmd
tools\Pack-DbCloudConfigLinqToDb.cmd
tools\Pack-DbCloudConfigAspNetCore.cmd

Untuk release auto-increment, builder pusat memperbarui <Version> project utama sebelum pack lalu menjalankan pack tanpa override versi global. Jika pack gagal, project version dikembalikan ke versi awal. Dengan demikian versi package dan assembly utama sama, sedangkan setiap ProjectReference tetap mengambil versi milik project dependency, bukan mewarisi versi package induk. Mode --clean hanya membangun ulang versi source saat ini.

ENV load test

Runner berikut mengirim request ENV dengan laju awal yang dibatasi dan tidak mencetak token maupun VALUE:

tools\Test-EnvironmentVariablesLoad.cmd ^
  --base-url http://localhost:9003 ^
  --client-id workload-client ^
  --certificate C:\secure\workload-client.pfx ^
  --certificate-password-env WORKLOAD_PFX_PASSWORD ^
  --environment DEVELOPMENT ^
  --database CHINOOK ^
  --name Ocr__BaseUrl ^
  --rps 100 ^
  --duration 10 ^
  --warmup true

Certificate policy harus mengizinkan konfigurasi database untuk penerbitan token dan Group ENV milik NAME yang diuji. Password PFX wajib berasal dari environment variable proses. Konfigurasi default rate limit 60 request per 60 detik akan menghasilkan HTTP 429 pada test 100 RPS; capacity test hanya boleh menaikkan limit melalui override proses sementara dan harus mengembalikan konfigurasi normal setelah selesai.

Rate limit backend dapat diatur tanpa perubahan source melalui OpxApiProtection:RateLimiting dan OpxApiProtection:Policies. Default umum tetap 60 request per 60 detik per alamat client dan bucket path. Bucket terpisah disediakan untuk status/login/setup/import, token dan pembacaan machine client, ENV, serta administrasi Machine Access agar lonjakan pada satu kelompok endpoint tidak menghabiskan kuota kelompok lain. Policy path yang lebih spesifik menang karena dicocokkan dengan prefix terpanjang.

Nilai global dapat dioverride, misalnya melalui environment variable proses OpxApiProtection__RateLimiting__Limit dan OpxApiProtection__RateLimiting__WindowSeconds. Policy tertentu dapat dioverride berdasarkan indeks, misalnya OpxApiProtection__Policies__5__RateLimit untuk policy machine token pada konfigurasi default. Untuk deployment yang sering mengubah urutan policy, lebih aman mengubah keseluruhan bagian JSON agar indeks tidak salah sasaran. Perubahan file konfigurasi berlaku ketika provider konfigurasi berhasil reload; perubahan environment variable memerlukan restart aplikasi. Jangan menonaktifkan rate limit pada endpoint login, setup, import, atau machine token.

Menu Settings hanya tersedia setelah login dan menyediakan:

  • Language / Bahasa: pilihan English (EN) dan Indonesia (ID). Default backend dan UI adalah EN. Pilihan disimpan pada settings backend dan ikut dikirim melalui endpoint status agar halaman setup/login memakai bahasa aktif sebelum autentikasi.
  • Token expired: 30 sampai 3600 detik. Perubahan berlaku pada login berikutnya.
  • Expired date: label informasi read-only. Backend menetapkan default lima tahun sejak database dibuat atau diaktifkan. Nilai ini tidak dapat diubah melalui UI atau update Settings biasa. Setelah waktu terlewati, endpoint detail mengembalikan HTTP 410.
  • Backend URL: origin HTTP/HTTPS absolut tanpa path, misalnya https://localhost:9003. URL digunakan oleh self-hosted Kestrel setelah API direstart. Argumen --urls atau environment ASPNETCORE_URLS tetap menjadi explicit override. Setelah URL berubah, sesuaikan DbCloudConfigClient:BaseAddress pada UI Server lalu restart UI.
  • Encrypt output: jika aktif, detail response dibungkus AES-256-GCM. Jika tidak aktif, payload detail dikirim plaintext di dalam HTTPS dan response OPX. Enkripsi file OSF di backend tetap aktif pada kedua mode.
  • Ubah password: API membuat database sementara dengan password baru, menyalin seluruh schema dan row legacy, memvalidasinya, lalu melakukan atomic replace. Database dengan password lama dipertahankan sebagai file *.password-backup.bak; seluruh session kemudian dihapus.
  • New database: membuat file .osf baru pada folder backend yang sama dan menjadikannya database aktif. Database lama tidak dihapus. Nama file tidak boleh mengandung path atau traversal.

Setelah mengganti password atau membuat database, pengguna wajib login kembali. Perubahan backend port memerlukan restart API. Bila UI terhubung langsung ke Kestrel tanpa reverse proxy, sesuaikan juga DbCloudConfigClient:BaseAddress pada appsettings.json, lalu restart UI. Binding IIS/reverse proxy tetap dikendalikan oleh konfigurasi hosting.

Backup dan restore OSF

Menu Backup and restore tersedia pada menu tiga titik AppBar setelah login. Backup manual merupakan salinan byte-for-byte database OSF yang tetap terenkripsi dan disimpan pada folder backups di samping database aktif. Daftar backup hanya menampilkan nama file, ukuran, dan waktu pembuatan; isi OSF dan password tidak pernah dikirim ke grid atau log.

Restore meminta password yang digunakan oleh file backup karena backup lama mungkin dibuat sebelum password database diganti. Backend menyalin backup ke file sementara, memigrasikan schema ENV legacy bila diperlukan, memvalidasi schema penuh, lalu mengganti database aktif secara atomik. Database aktif sebelumnya otomatis dipertahankan sebagai file pre-restore.osf untuk rollback. Setelah restore, expiration aktivasi direset, password DPAPI backend diperbarui, seluruh sesi dihapus, dan pengguna harus login menggunakan password database hasil restore.

File restore dibatasi pada file .osf di folder backup backend. Traversal, symbolic link/reparse point, file kosong, schema yang tidak didukung, serta file di atas 512 MiB ditolak.

Menu Setup pada AppBar menyimpan profil aplikasi berupa Company Name, Description, dan master data DBOwner ke file settings backend. DBOwner bersifat unik tanpa membedakan huruf besar/kecil, mempertahankan casing tampilan, dan menjadi sumber pilihan field Owner pada dialog setting database serta Environment Variables. Daftar Group lama tetap dipertahankan secara internal untuk kompatibilitas backup, contract, dan policy machine lama, tetapi tidak lagi ditampilkan atau dapat diubah melalui UI.

Endpoint

GET    /health
GET    /api/v1/session/status
POST   /api/v1/session/setup
POST   /api/v1/session/login
POST   /api/v1/session/development/setup-key  (Development + loopback only)
GET    /api/v1/database-configs?environment=PRODUCTION
GET    /api/v1/database-configs/{environment}/{name}
PUT    /api/v1/database-configs/{environment}/{name}
POST   /api/v1/database-configs/{environment}/{name}/duplicate
DELETE /api/v1/database-configs/{environment}/{name}
GET    /api/v1/environment-variables?environment=PRODUCTION&group=OCR&owner=HRIS&includeInactive=false
GET    /api/v1/environment-variables/{name}?environment=PRODUCTION&owner=HRIS
PUT    /api/v1/environment-variables/{name}?environment=PRODUCTION&owner=HRIS
POST   /api/v1/environment-variables/{name}/duplicate?environment=PRODUCTION&owner=HRIS
DELETE /api/v1/environment-variables/{name}?environment=PRODUCTION&owner=HRIS
GET    /api/v1/backups
POST   /api/v1/backups
POST   /api/v1/backups/restore
GET    /api/v1/settings
PUT    /api/v1/settings
GET    /api/v1/settings/setup
PUT    /api/v1/settings/setup
POST   /api/v1/settings/change-password
POST   /api/v1/settings/databases

Endpoint status, setup, dan login bersifat anonymous. Setup tetap dilindungi DB_CLOUD_CONFIG_SETUP_KEY, hanya tersedia ketika database belum ada, dan melewati rate limiting Opx.Api.Web. Endpoint CRUD memerlukan Bearer JWT serta sesi server yang belum kedaluwarsa.

Endpoint environment-variables hanya menerima sesi editor interaktif; machine JWT tidak boleh mengadministrasikan secret. List tidak mengembalikan VALUE, sedangkan detail dikirim dengan response header Cache-Control: no-store. Request PUT menggunakan bentuk:

{
  "environment": "PRODUCTION",
  "owner": "HRIS",
  "value": "secret-or-configuration-value",
  "isActive": true
}

Identitas ENV adalah Environment + Owner + Name tanpa membedakan huruf besar/kecil. Record baru hanya menerima PRODUCTION, STAGING, atau DEVELOPMENT. Nama yang sama boleh memiliki nilai berbeda pada environment atau owner berbeda. owner nullable untuk kompatibilitas record legacy, sedangkan owner baru dipilih dari master DBOwner. Endpoint detail menggunakan query environment dan owner; menghilangkan owner berarti meminta record ownerless, bukan mengambil sembarang owner. Duplicate menyalin Group, Value, dan status ke kombinasi target Environment + Owner + Name baru, lalu backend memeriksa konflik secara atomik. Pada edit, query membawa Environment dan Owner sumber, sedangkan body membawa Environment dan Owner tujuan. UI memperingatkan konflik Environment + Owner + Name sebelum submit, kemudian backend mengulang pemeriksaan di bawah store lock dan mengembalikan HTTP 409 tanpa mengubah record sumber atau target jika identitas tersebut sudah ada. Group/Category tidak lagi ditampilkan atau dapat diedit; record baru memakai nilai kompatibilitas internal UNGROUPED, sedangkan nilai Group record lama tetap dipertahankan.

Database OSF dengan tabel ENV lama (NAME atau OWNER_KEY + NAME sebagai primary key) dimigrasikan saat database berhasil dibuka. Record lama dipertahankan pada scope internal GLOBAL, Owner yang sudah ada tetap dipertahankan, dan salinan database terenkripsi sebelum migrasi disimpan sebagai backup. Scope GLOBAL tidak diwariskan otomatis ke Development, Staging, atau Production; operator harus memindahkan record secara eksplisit.

Hamburger pada AppBar editor menyediakan dua halaman: Database Config dan Environment Variables. Halaman Environment Variables menampilkan Environment, Name, Owner, status aktif, serta aksi Edit/Duplicate/View endpoint/Hapus. View endpoint menampilkan URL detail, Copy endpoint, dan Execute GET berformat envelope OPX. Nilai tidak pernah ditampilkan di grid dan baru dimuat melalui endpoint detail ketika editor atau Execute GET digunakan oleh sesi yang terautentikasi. Record baru wajib memilih Environment kanonik. Record lama dengan Group legacy tetap dapat diedit selama nilai Group tersebut tidak diganti. Editor VALUE memakai textarea multiline dengan batas dan penghitung real-time 2048 karakter. Card mobile memenuhi lebar container, membungkus nama panjang, dan tidak membuat halaman overflow horizontal.

Body endpoint PUT:

{
  "provider": "Microsoft.Data.SqlClient",
  "connectionString": "Server=...;Database=...;Encrypt=True",
  "commandTimeoutSeconds": 30,
  "owner": null,
  "isActive": true,
  "metadata": {
    "region": "primary"
  }
}

owner adalah parameter opsional dengan panjang maksimal 100 karakter. Nilai kosong dinormalisasi menjadi null. Record lama yang menyimpan owner pada metadata.owner tetap dibaca sebagai fallback kompatibilitas.

isActive menentukan status aktif konfigurasi dan default-nya true. Editor menyediakan checkbox Konfigurasi aktif. Record OSF lama yang belum memiliki field ini otomatis dianggap aktif; menonaktifkan konfigurasi tidak menghapus record atau connection string.

Editor UI menyediakan pilihan provider berikut:

  • MySQL disimpan sebagai MySql.8.0.MySqlConnector.
  • MariaDB disimpan sebagai MariaDB.10.MySqlConnector.
  • SQL Server disimpan sebagai Microsoft.Data.SqlClient.
  • PostgreSQL disimpan sebagai Npgsql.
  • SAP HANA disimpan sebagai SapHana.Odbc.

Record lama dengan provider MySqlConnector tetap dapat ditampilkan, diedit, dan dites sebagai MySQL tanpa diubah otomatis.

Environment pada editor menyediakan tiga mode pengguna:

  • PRODUCTION disimpan sebagai PRODUCTION untuk record baru.
  • STAGING disimpan sebagai STAGING.
  • DEVELOPMENT disimpan sebagai DEVELOPMENT.

Konfigurasi baru memakai PRODUCTION sebagai default. Record lama yang tersimpan sebagai RELEASE tetap dibaca dan ditampilkan sebagai PRODUCTION, sedangkan DEBUG ditampilkan sebagai DEVELOPMENT. Penyimpanan baru selalu memakai nama kanonik; alias lama tidak dibuat kembali. Pengujian connection lokal tetap memakai environment DEVELOPMENT, dengan Name terpisah bila harus hidup bersama shared development. Panduan lengkap tersedia di docs/DEVELOPMENT-OSF-DEPLOYMENT.md.

Pada viewport mobile (maksimal 760 px), daftar setting berubah dari tabel menjadi card. Setiap card menampilkan environment, nama, provider, waktu perubahan, serta ikon Edit/Duplicate/View endpoint/Hapus dalam satu baris pada header. Setiap ikon memiliki label aksesibel dan tooltip. Desktop tetap memakai tabel dengan aksi berbentuk teks.

Layout halaman utama memakai seluruh lebar viewport tanpa sidebar permanen. Navigasi hamburger bersifat ringkas dan hanya membuka menu sementara pada AppBar. Batas lebar tetap diterapkan pada kartu login/setup dan modal agar form tetap nyaman dibaca.

Seluruh UI mengikuti resolusi viewport secara responsif. Desktop menggunakan grid penuh, sedangkan layar sempit menggunakan card dan kontrol bertumpuk untuk mencegah overflow halaman. Modal dibatasi oleh lebar/tinggi viewport, memiliki scroll internal untuk konten panjang, dan menjaga aksi utama tetap dapat dijangkau pada desktop, tablet, maupun mobile. Grid Database Config dan Seluruh grid Database Configuration dan Environment Variables memiliki tinggi 70vh, scroll internal, dan header tabel sticky yang dapat di-sort pada setiap kolom data desktop. Area card mobile juga 70vh dan memakai urutan sort yang sama. AppBar selalu fixed di bagian atas viewport pada desktop maupun mobile; shell menyediakan offset 52px agar konten tidak tertutup, sedangkan backdrop modal tetap berada di atas AppBar.

Tombol View endpoint pada setiap setting membuka modal berisi URL absolut GET /api/v1/database-configs/{environment}/{name} dan tombol Copy endpoint. URL mengikuti BaseAddress wrapper pada konfigurasi UI. Modal hanya menampilkan alamat endpoint, bukan JWT atau secret; consumer tetap wajib mengirim Bearer JWT yang valid saat memanggil endpoint.

Tombol Execute GET pada modal menjalankan request melalui wrapper dan sesi JWT editor yang aktif, lalu menampilkan response delivery mentah sebagai JSON terformat. Jika backend mengaktifkan encrypted output, yang ditampilkan adalah encrypted envelope; jika dinonaktifkan, yang ditampilkan adalah plain payload. Token autentikasi tidak disertakan dalam output. Plain payload dapat memuat connection string atau credential sensitif dan hanya boleh digunakan pada sesi editor yang dipercaya.

Identitas unik setting adalah kombinasi Environment + Nama. Nama yang sama boleh digunakan pada PRODUCTION, STAGING, dan DEVELOPMENT sebagai record berbeda. Menyimpan kembali pasangan Environment/Nama yang sama akan memperbarui record tersebut, bukan membuat duplikat baru.

Tombol Duplicate menyalin seluruh value setting—provider, connection string, timeout, metadata, owner, dan status aktif—kecuali Environment dan Name target. Dialog menyediakan pilihan PRODUCTION, STAGING, atau DEVELOPMENT serta meminta Name baru. UI memeriksa pasangan Environment/Name target terlebih dahulu, kemudian backend mengulang pemeriksaan secara atomik. Jika target sudah ada, API mengembalikan HTTP 409 dan tidak mengubah record sumber maupun target.

Tombol Setup connection di samping provider membuka generator connection string untuk SQL Server, MySQL, MariaDB, PostgreSQL, dan SAP HANA. Tool menyediakan host, port default, database, username, password, TLS/encryption, dan opsi trust server certificate. Nilai dibangun memakai DbConnectionStringBuilder agar karakter khusus ter-escape dengan benar, lalu hanya mengisi editor; data baru dikirim ke API ketika pengguna menekan Simpan.

Untuk SQL Server, tool menyediakan Windows Authentication (Trusted Connection). Pada mode ini username/password dinonaktifkan dan generator menghasilkan Integrated Security=True. Tombol tes koneksi menggunakan identity proses UI Server, sehingga akun dotnet, IIS App Pool, atau service account yang menjalankan UI harus memiliki login dan permission pada SQL Server.

Jika editor sudah memiliki connection string, Setup connection mem-parsing nilai existing dan mengisi seluruh field tool tanpa perlu mengetik ulang. Nilai credential tetap berada pada circuit server UI dan tidak ditulis ke log. Untuk SQL Server named instance tanpa port eksplisit, tool mempertahankan format server\instance dan membiarkan SQL Browser menemukan dynamic port. Checkbox Gunakan port eksplisit tersedia saat koneksi memang harus memakai server\instance,port.

Tombol Tes koneksi membuka koneksi langsung dari server UI memakai driver Microsoft.Data.SqlClient, MySqlConnector, Npgsql, atau System.Data.Odbc sesuai engine. Percobaan dibatasi 10 detik, mematikan pooling pada provider yang mendukung keyword tersebut, tidak menyimpan hasil tes, dan menampilkan status serta durasi. Kegagalan tes tidak mengubah connection string editor dan tidak mengirim konfigurasi ke Cloud Config API.

SAP HANA memakai System.Data.Odbc dan connection string Driver={HDBODBC};ServerNode=host:port;DatabaseName=.... Mesin server UI wajib memiliki SAP HANA Client 64-bit agar driver HDBODBC tersedia. Port default generator adalah 30015; TLS aktif secara default dan validasi sertifikat hanya dinonaktifkan jika opsi trust server certificate dipilih. Tombol ini tersedia baik di connection string generator maupun langsung di dialog tambah/edit setting, sehingga connection string manual atau existing dapat diuji sebelum disimpan.

Table OSF CLOUD_CONFIG memakai kolom SECTION, KEYNAME, DATA, CONTENT_TYPE, dan IS_SECRET. Route {environment}/{name} dipetakan ke SECTION/KEYNAME, yang menjadi primary key komposit. Connection string berada dalam file OSF terenkripsi dan response detail memiliki lapisan enkripsi AES-256-GCM tambahan. Lapisan response tersebut mengikuti setting EncryptOutput; enkripsi at-rest OSF tidak terpengaruh.

Sample Chinook

Sample adaptasi Chinook yang memuat konfigurasi database DEVELOPMENT/CHINOOK dari Cloud Config tersedia di samples/chinook-cloud-config. Sample menggunakan assertion JWT yang ditandatangani private certificate, menukarkannya dengan JWT read-only dua menit, mengambil payload melalui DbCloudConfig.Client, mendekripsinya, lalu memasok provider dan connection string ke DbContext. Password OSF hanya tersedia sebagai file DPAPI terenkripsi pada backend dan tidak diberikan kepada Chinook. Proses load yang aman dapat dilihat dari operasi Swagger GET /api/cloud-config/status; endpoint itu hanya menampilkan state, endpoint sumber, provider, waktu load, dan tahapan non-sensitif. Detail provisioning dan menjalankan sample tersedia pada README di folder sample.

Machine Access berdasarkan konfigurasi database

Menu Machine Access pada AppBar editor mengelola autentikasi backend yang mengambil konfigurasi database. Untuk penerbitan baru, administrator cukup mengisi nama certificate dan memilih satu atau beberapa konfigurasi database exact dari daftar, misalnya DEVELOPMENT/FINAC, PRODUCTION/FINAC, dan PRODUCTION/FINGERSPOT, lalu mengisi password PFX serta masa berlaku. Editor menghasilkan PFX sebagai download satu kali. Backend hanya menyimpan public CER, kid fingerprint SHA-256, policy, status, dan masa berlaku. Policy tidak memperluas izin berdasarkan Owner dan tidak otomatis memasukkan environment lain. Satu certificate boleh memuat konfigurasi DEVELOPMENT, STAGING, atau PRODUCTION hanya jika masing-masing resource dipilih. Policy lama dengan DEBUG atau RELEASE tetap dibaca sebagai alias kompatibilitas. Registry dinamis lama berbasis Owner/Environment/Database Name dan grant ENV Group tetap dibaca untuk kompatibilitas, tetapi penerbitan baru menggunakan exact-resource allowlist.

Tool menyediakan rotasi certificate, revoke, dan aktivasi kembali. Rotasi menghasilkan PFX baru dan langsung membatalkan public key lama. PFX/password, private key, assertion, JWT, password OSF, serta connection string tidak pernah disimpan dalam registry machine client. Production wajib memakai HTTPS dan PFX hasil download harus dipindahkan ke certificate store atau secret store milik backend tujuan.

Dev Machine

Menu Dev Machine menerbitkan satu certificate bersama bernama dev-machine khusus untuk proses development. Penerbitan awal menyimpan exact allowlist seluruh konfigurasi DEVELOPMENT yang tersedia. Setelah certificate ada, setiap penyimpanan atau duplicate konfigurasi DEVELOPMENT yang berhasil akan menambahkan resource tersebut ke allowlist secara atomik. STAGING dan PRODUCTION tidak pernah ikut.

Dev Machine juga dapat membaca ENV aktif dari Group yang disinkronkan oleh administrator. Nilai ENV tetap tidak pernah ditampilkan di daftar certificate atau log. Karena ENV belum memiliki dimensi DEVELOPMENT/PRODUCTION, fitur ini bersifat global terhadap Group yang diberikan dan UI menampilkan peringatan eksplisit. Gunakan tombol Synchronize access setelah perubahan master Group lama.

PFX Dev Machine tetap read-only. Certificate tidak dapat membuat, mengubah, duplicate, atau menghapus konfigurasi database, ENV, settings, backup, maupun machine client. Untuk otomasi Codex, pola yang disarankan adalah:

  1. Operator membuka sesi editor sekali; token admin mengikuti expiry UI (default 15 menit).
  2. Tool lokal memakai DbCloudConfig.Client dan sesi tersebut untuk operasi create/update yang memang diminta.
  3. Setelah sesi berakhir, tool harus login ulang; jangan menyimpan password OSF atau Bearer token dalam source, command history, log, atau file handoff.
  4. Workload development memakai PFX dev-machine hanya untuk startup/read.

Simpan PFX di luar repository, misalnya pada folder secret development milik user, dan berikan password melalui secret provider lokal. Jangan menaruh password PFX di appsettings.json atau launchSettings.json.

Build

Repository hanya memakai feed publik NuGet.org dari NuGet.Config; tidak ada NuGet lokal.

dotnet restore .\DbCloudConfig.slnx --configfile .\NuGet.Config
dotnet build .\DbCloudConfig.slnx --no-restore -c Release
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.9 117 8/25/2026
1.0.8 108 8/12/2026
1.0.6 116 8/2/2026
1.0.5 109 7/28/2026
1.0.4 108 7/28/2026
1.0.2 110 7/28/2026
1.0.1 110 7/25/2026