remove user-secrets approach & prepare a pilot deploy
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Build artefacts — copying them in poisons the container's restore/publish with host-built binaries.
|
||||
**/bin/
|
||||
**/obj/
|
||||
**/logs/
|
||||
**/.vs/
|
||||
**/graphify-out/
|
||||
|
||||
# Never needed by the image
|
||||
**/*.user
|
||||
**/*.suo
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
CLAUDE.md
|
||||
CONVENTIONS.md
|
||||
AGENTS.md
|
||||
README.md
|
||||
LICENSE.md
|
||||
+9
-8
@@ -67,8 +67,10 @@ Persistence below). **In deployed environments**, boot instead only *checks* the
|
||||
reachable SQL Server is required to start. Startup **fails fast**
|
||||
(`StartupSecretsGuard`) if a load-bearing secret — the DB connection strings, and in deployed environments the
|
||||
JWE + field-encryption keys — is missing or left at its committed `SET_VIA_USER_SECRETS_OR_ENV` placeholder
|
||||
(refinement-phase-5). Development supplies working dev-only crypto keys via `appsettings.Development.json`; only
|
||||
the connection string must come from user-secrets (see [RUNBOOK](../dev/post-phase/refinement/RUNBOOK.md)).
|
||||
(refinement-phase-5). **`dotnet user-secrets` is no longer used** — the `<UserSecretsId>` was removed from
|
||||
`Baya.Web.Api.csproj`, so that store is not read at all. Every value, connection strings and dev-only crypto keys
|
||||
alike, lives in `appsettings.Development.json`; the deployment's two container-specific overrides live in the root
|
||||
`docker-compose.yml` (see [DEPLOY.md](../DEPLOY.md)).
|
||||
|
||||
---
|
||||
|
||||
@@ -128,11 +130,10 @@ stamped by `AuditFieldInterceptor` (Persistence), not in handlers.
|
||||
**real HTTP adapter** in `Baya.Infrastructure.CrossCutting/Seams/Real/`, **config-selected** by a per-rail
|
||||
`Seams:*:Provider` selector in `AddCrossCuttingSeams` (default = the mock, so an unconfigured env is unchanged;
|
||||
a typo falls closed to the mock). Real adapters use `HttpClient` (typed via `IHttpClientFactory`) +
|
||||
`System.Text.Json` + BCL crypto — **no new NuGet packages**; credentials come from `Seams:*` (user-secrets/env,
|
||||
never committed). Swapping is a registration change; **no handler is touched**. The adapters:
|
||||
`System.Text.Json` + BCL crypto — **no new NuGet packages**; credentials come from `Seams:*` (appsettings/env). Swapping is a registration change; **no handler is touched**. The adapters:
|
||||
`KavenegarSmsSender` (`Sms:Provider=kavenegar` — **launch-critical**; when a real gateway is selected the
|
||||
Development OTP-in-logs bridge is **disabled**, so the OTP is never logged), `TelegramSmsSender`
|
||||
(`Sms:Provider=telegram` — **Development-only, broadcast, not a gateway**: it posts to the standalone
|
||||
(`Sms:Provider=telegram` — **broadcast, not a gateway**; the pre-launch demo OTP rail: it posts to the standalone
|
||||
`telegram-otp-bot/` relay, which pushes *every* code to a fixed list of Telegram chat ids, so manual testing
|
||||
beats reading OTPs out of the log. It is the **one non-mock SMS provider that keeps the OTP-capture bridge
|
||||
enabled** — see Startup wiring — and its `Seams:Sms:Telegram:ApiKey` is a user-secret, never committed),
|
||||
@@ -733,9 +734,9 @@ action to `sender.Send(...)`. Full conventions are in [CONVENTIONS.md](CONVENTIO
|
||||
- Dynamic permission system: `DynamicPermissionHandler` reads `[controller]` + `[action]` route
|
||||
values and checks role claims. Always use `[controller]`/`[action]` tokens so the keys stay
|
||||
consistent (see CONVENTIONS.md §1 Routing).
|
||||
- Settings bound from `appsettings.json` → `IdentitySettings`. **JWE keys are never committed**: the
|
||||
committed values are `SET_VIA_USER_SECRETS_OR_ENV` placeholders (real ones via user-secrets/env; Development
|
||||
uses dev-only keys in `appsettings.Development.json`). `RequireHttpsMetadata` is **on outside Dev/Testing**
|
||||
- Settings bound from `appsettings.json` → `IdentitySettings`. The base `appsettings.json` carries
|
||||
`SET_VIA_USER_SECRETS_OR_ENV` placeholders that `StartupSecretsGuard` rejects; the real values live in the
|
||||
environment-specific file (`appsettings.Development.json` holds the dev-only keys the demo deployment runs on). `RequireHttpsMetadata` is **on outside Dev/Testing**
|
||||
(passed into `RegisterIdentityServices`), the access-token lifetime is `ExpirationMinutes: 60`, and
|
||||
`Issuer`/`Audience` are real (`Balinyaar`/`BalinyaarClient`) — refinement-phase-5.
|
||||
- Auth and OTP endpoints must be rate-limited (CONVENTIONS.md §11) — `request_otp`/`verify_otp` use
|
||||
|
||||
@@ -477,8 +477,8 @@ Place these tests in a dedicated `Baya.Test.Api` project so they can run against
|
||||
|
||||
## 11. Security rules
|
||||
|
||||
- **Never hardcode secrets.** Keys, connection strings, and tokens come from `appsettings.*.json` / user-secrets / environment variables, bound to typed settings classes.
|
||||
- `SecretKey` and `Encryptkey` (in `IdentitySettings`) must be set in environment-specific config, never in `appsettings.json` committed to the repo.
|
||||
- **Never hardcode secrets in C#.** Keys, connection strings, and tokens come from `appsettings.*.json` or environment variables, bound to typed settings classes — never a literal in a handler or service. (`dotnet user-secrets` is not used; see [DEPLOY.md](../DEPLOY.md) for the configuration model.)
|
||||
- `SecretKey` and `Encryptkey` (in `IdentitySettings`) belong in the environment-specific file, never in the base `appsettings.json`, which stays at its `StartupSecretsGuard`-rejected placeholder.
|
||||
- Always validate all external input with FluentValidation before processing.
|
||||
- EF Core parameterizes queries automatically — never concatenate raw SQL.
|
||||
- If you must use raw SQL, use `FromSqlInterpolated` (parameterized), never `FromSqlRaw` with user data.
|
||||
|
||||
+31
-27
@@ -1,36 +1,40 @@
|
||||
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
# Balinyaar API — build context is `server/` (see the root docker-compose.yml).
|
||||
#
|
||||
# The csproj/props files are copied on their own first so `dotnet restore` lands in a layer that only
|
||||
# re-runs when a project reference or package version actually changes; the source copy below it churns
|
||||
# on every commit. Directory.Packages.props is the central version manifest — restore fails without it.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY ["../Directory.Packages.props", "./"]
|
||||
COPY ["src/API/Baya.Web.Api/Baya.Web.Api.csproj", "src/API/Baya.Web.Api/"]
|
||||
COPY ["src/API/Baya.WebFramework/Baya.WebFramework.csproj", "src/API/Baya.WebFramework/"]
|
||||
COPY ["src/API/Plugins/Baya.Web.Plugins.Grpc/Baya.Web.Plugins.Grpc.csproj", "src/API/Plugins/Baya.Web.Plugins.Grpc/"]
|
||||
COPY ["src/Core/Baya.Application/Baya.Application.csproj", "src/Core/Baya.Application/"]
|
||||
COPY ["src/Core/Baya.Domain/Baya.Domain.csproj", "src/Core/Baya.Domain/"]
|
||||
COPY ["src/Infrastructure/Baya.Infrastructure.CrossCutting/Baya.Infrastructure.CrossCutting.csproj", "src/Infrastructure/Baya.Infrastructure.CrossCutting/"]
|
||||
COPY ["src/Infrastructure/Baya.Infrastructure.Identity/Baya.Infrastructure.Identity.csproj", "src/Infrastructure/Baya.Infrastructure.Identity/"]
|
||||
COPY ["src/Infrastructure/Baya.Infrastructure.Persistence/Baya.Infrastructure.Persistence.csproj", "src/Infrastructure/Baya.Infrastructure.Persistence/"]
|
||||
COPY ["src/Infrastructure/Baya.Infrastructure.Monitoring/Baya.Infrastructure.Monitoring.csproj", "src/Infrastructure/Baya.Infrastructure.Monitoring/"]
|
||||
COPY ["src/Shared/Baya.SharedKernel/Baya.SharedKernel.csproj", "src/Shared/Baya.SharedKernel/"]
|
||||
COPY ["src/Tests/Baya.Test.Infrastructure.Identity/Baya.Test.Infrastructure.Identity/Baya.Test.Infrastructure.Identity.csproj", "src/Tests/Baya.Test.Infrastructure.Identity/Baya.Test.Infrastructure.Identity/"]
|
||||
COPY ["src/Tests/Baya.Tests.Setup/Baya.Tests.Setup.csproj", "src/Tests/Baya.Tests.Setup/"]
|
||||
|
||||
COPY Directory.Packages.props ./
|
||||
COPY src/API/Baya.Web.Api/Baya.Web.Api.csproj src/API/Baya.Web.Api/
|
||||
COPY src/API/Baya.WebFramework/Baya.WebFramework.csproj src/API/Baya.WebFramework/
|
||||
COPY src/API/Plugins/Baya.Web.Plugins.Grpc/Baya.Web.Plugins.Grpc.csproj src/API/Plugins/Baya.Web.Plugins.Grpc/
|
||||
COPY src/Core/Baya.Application/Baya.Application.csproj src/Core/Baya.Application/
|
||||
COPY src/Core/Baya.Domain/Baya.Domain.csproj src/Core/Baya.Domain/
|
||||
COPY src/Infrastructure/Baya.Infrastructure.CrossCutting/Baya.Infrastructure.CrossCutting.csproj src/Infrastructure/Baya.Infrastructure.CrossCutting/
|
||||
COPY src/Infrastructure/Baya.Infrastructure.Identity/Baya.Infrastructure.Identity.csproj src/Infrastructure/Baya.Infrastructure.Identity/
|
||||
COPY src/Infrastructure/Baya.Infrastructure.Monitoring/Baya.Infrastructure.Monitoring.csproj src/Infrastructure/Baya.Infrastructure.Monitoring/
|
||||
COPY src/Infrastructure/Baya.Infrastructure.Persistence/Baya.Infrastructure.Persistence.csproj src/Infrastructure/Baya.Infrastructure.Persistence/
|
||||
COPY src/Shared/Baya.SharedKernel/Baya.SharedKernel.csproj src/Shared/Baya.SharedKernel/
|
||||
|
||||
RUN dotnet restore src/API/Baya.Web.Api/Baya.Web.Api.csproj
|
||||
|
||||
RUN dotnet restore "src/API/Baya.Web.Api/Baya.Web.Api.csproj"
|
||||
COPY . .
|
||||
WORKDIR "src/API/Baya.Web.Api"
|
||||
RUN dotnet build "Baya.Web.Api.csproj" -c Release -o /app/build
|
||||
RUN dotnet publish src/API/Baya.Web.Api/Baya.Web.Api.csproj \
|
||||
-c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "Baya.Web.Api.csproj" -c Release -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
FROM base AS final
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
# TLS is terminated by Caddy; the API speaks plain HTTP on the shared container network only.
|
||||
ENV ASPNETCORE_URLS=http://+:8080
|
||||
EXPOSE 8080
|
||||
|
||||
# Written to by the Serilog file sink (Development) and the local-disk object storage seam — both are
|
||||
# bind-mounted in compose so an image rebuild doesn't discard uploaded verification documents.
|
||||
RUN mkdir -p /app/logs /app/data/object-storage
|
||||
|
||||
ENTRYPOINT ["dotnet", "Baya.Web.Api.dll"]
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
<!-- Enables `dotnet user-secrets` for the local-dev connection string (never a committed secret). -->
|
||||
<UserSecretsId>baya-web-api</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
|
||||
@@ -9,9 +9,9 @@ namespace Baya.Web.Api.Configuration;
|
||||
/// A database connection is required in every real environment; the JWE + field-encryption keys are
|
||||
/// required only in <b>deployed</b> environments (Development keeps working dev-only defaults in
|
||||
/// <c>appsettings.Development.json</c>, and the "Testing" environment runs on in-memory SQLite with
|
||||
/// test-injected keys). The effect: a fresh clone with no user-secrets stops at boot with a clear
|
||||
/// message instead of silently connecting somewhere unintended, and a deployment can never fall back
|
||||
/// to a committed placeholder key.
|
||||
/// test-injected keys). The effect: a clone whose configuration was never filled in stops at boot with
|
||||
/// a clear message instead of silently connecting somewhere unintended, and a Production/Staging
|
||||
/// deployment can never fall back to a committed placeholder or a dev-only key.
|
||||
/// </summary>
|
||||
public static class StartupSecretsGuard
|
||||
{
|
||||
@@ -39,7 +39,7 @@ public static class StartupSecretsGuard
|
||||
RequireReal(errors, "ConnectionStrings:logDb", config.GetConnectionString("logDb"));
|
||||
|
||||
// Development supplies working dev-only keys via appsettings.Development.json; only deployed
|
||||
// environments must inject real per-environment secrets (env vars / Key Vault / KMS).
|
||||
// environments must supply real per-environment secrets.
|
||||
if (!builder.Environment.IsDevelopment())
|
||||
{
|
||||
RequireReal(errors, "IdentitySettings:SecretKey", config["IdentitySettings:SecretKey"]);
|
||||
@@ -53,8 +53,8 @@ public static class StartupSecretsGuard
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"Refusing to start: required secret configuration is missing or still a committed placeholder. " +
|
||||
"Provide real values via user-secrets (Development) or environment variables (deployed) — see " +
|
||||
"dev/post-phase/refinement/RUNBOOK.md.\n - " + string.Join("\n - ", errors));
|
||||
"Provide real values in appsettings.<Environment>.json (or as Seams__…-style environment " +
|
||||
"variables) — see DEPLOY.md.\n - " + string.Join("\n - ", errors));
|
||||
}
|
||||
|
||||
private static void RequireReal(List<string> errors, string key, string? value)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"SqlServer": "Server=87.107.152.16,1433;Database=Baya;User Id=hamid_root_un_sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;",
|
||||
"logDb":"Server=87.107.152.16,1433;Database=Baya_Logs;User Id=hamid_root_un_sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;"
|
||||
"logDb": "Server=87.107.152.16,1433;Database=Baya_Logs;User Id=hamid_root_un_sa;Password=N8@s5Taw1zWeh@#Hm;TrustServerCertificate=True;Encrypt=False;"
|
||||
},
|
||||
"IdentitySettings": {
|
||||
"SecretKey": "SET_VIA_USER_SECRETS_OR_ENV",
|
||||
"Encryptkey": "SET_VIA_USER_SECRETS_OR_ENV",
|
||||
"SecretKey": "dev-only-jwe-signing-key-not-for-production-0123456789abcdef",
|
||||
"Encryptkey": "dev-only-16bytes",
|
||||
"Issuer": "Balinyaar",
|
||||
"Audience": "BalinyaarClient",
|
||||
"NotBeforeMinutes": "0",
|
||||
@@ -13,8 +13,9 @@
|
||||
},
|
||||
"Seams": {
|
||||
"FieldEncryption": {
|
||||
"Key": "SET_VIA_USER_SECRETS_OR_ENV",
|
||||
"HashKey": "SET_VIA_USER_SECRETS_OR_ENV"
|
||||
"//": "DO NOT CHANGE. Every encrypted column in the Baya database (phones, addresses, IBANs, clinical notes) was written with these exact values, and users.PhoneHash — which every login looks up — is derived from HashKey. Rotating either makes the existing data unreadable and locks every account out.",
|
||||
"Key": "local-dev-field-encryption-key-not-for-production",
|
||||
"HashKey": "local-dev-field-hash-key-not-for-production"
|
||||
},
|
||||
"ObjectStorage": {
|
||||
"RootPath": ""
|
||||
@@ -23,7 +24,8 @@
|
||||
"Provider": "telegram",
|
||||
"Telegram": {
|
||||
"BaseUrl": "http://127.0.0.1:5010",
|
||||
"ApiKey": "ab8984974bc1fe5ce514d0fd74f71c8738b3aed92a7e4d86",
|
||||
"//": "Must equal the relay's API_KEY. NOT the value in telegram-otp-bot/.env.example — that one is published, so TelegramSmsSender rejects it.",
|
||||
"ApiKey": "6a8dfaeea1aa375eb61da7663cadf23a3ea245fd939264dd",
|
||||
"TimeoutSeconds": 10
|
||||
}
|
||||
},
|
||||
@@ -34,11 +36,20 @@
|
||||
}
|
||||
},
|
||||
"Cors": {
|
||||
"AllowedOrigins": []
|
||||
"AllowedOrigins": [
|
||||
"https://balinyaar.ir",
|
||||
"https://www.balinyaar.ir",
|
||||
"http://localhost:3000"
|
||||
]
|
||||
},
|
||||
"ForwardedHeaders": {
|
||||
"//": "Docker bridge ranges — the reverse proxy (Caddy) shares a container network with the API, so its hop must be trusted for X-Forwarded-For to resolve the real client IP the rate limiter partitions on.",
|
||||
"KnownProxies": [],
|
||||
"KnownNetworks": []
|
||||
"KnownNetworks": [
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"10.0.0.0/8"
|
||||
]
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Kestrel": {
|
||||
|
||||
+12
-7
@@ -13,9 +13,11 @@ namespace Baya.Infrastructure.CrossCutting.Seams.Real;
|
||||
/// single JSON call: <c>POST /send_otp</c> and <c>POST /send</c>, authenticated with the shared
|
||||
/// <c>X-Api-Key</c> secret.
|
||||
///
|
||||
/// <para><b>Development only.</b> The relay broadcasts every message to a fixed list of Telegram chat ids — every
|
||||
/// recipient reads every code. It exists so manual testing beats reading OTPs out of the server log; it is not a
|
||||
/// gateway and must never be selected in a deployed environment (see <see cref="TelegramOptions"/>).</para>
|
||||
/// <para><b>Broadcast, not per-user routing.</b> The relay sends every message to a fixed list of Telegram chat
|
||||
/// ids — every recipient reads every code, whichever phone requested it. That makes it a shared-inbox channel for
|
||||
/// a small trusted group, not an SMS gateway: it is the deliberate OTP rail for the pre-launch demo deployment
|
||||
/// (no Iranian gateway contract yet), and must be replaced by <see cref="KavenegarSmsSender"/> before real
|
||||
/// customers sign up (see <see cref="TelegramOptions"/>).</para>
|
||||
///
|
||||
/// <para><b>The OTP is never logged</b> — only the phone tail and the relay's HTTP outcome, exactly like
|
||||
/// <see cref="KavenegarSmsSender"/>. A non-2xx (the relay answers <c>502</c> when <i>no</i> recipient got the
|
||||
@@ -27,7 +29,9 @@ public sealed class TelegramSmsSender(
|
||||
IOptions<SeamOptions> options,
|
||||
ILogger<TelegramSmsSender> logger) : ISmsSender
|
||||
{
|
||||
/// <summary>The repo's committed stand-in for a secret — treated as "not configured".</summary>
|
||||
/// <summary>The example key published in <c>telegram-otp-bot/.env.example</c> and its README. Anyone
|
||||
/// reading the repo knows it, so it is a documentation sample, not a secret — treated as "not configured"
|
||||
/// so a deployment can never quietly authenticate the OTP rail with a publicly-known value.</summary>
|
||||
private const string SecretPlaceholder = "ab8984974bc1fe5ce514d0fd74f71c8738b3aed92a7e4d86";
|
||||
|
||||
private readonly TelegramOptions _options = options.Value.Sms.Telegram;
|
||||
@@ -43,11 +47,12 @@ public sealed class TelegramSmsSender(
|
||||
// Fail with the config key rather than sending an unauthenticated request the relay answers with a bare
|
||||
// 401 — the cause of that 401 is invisible from this side.
|
||||
var apiKey = _options.ApiKey;
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
if (string.IsNullOrWhiteSpace(apiKey) || apiKey == SecretPlaceholder)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Seams:Sms:Telegram:ApiKey is not configured. Set it (user-secrets or environment) to the same " +
|
||||
"value as the relay's API_KEY, or select another Seams:Sms:Provider.");
|
||||
"Seams:Sms:Telegram:ApiKey is not configured (unset, or still the published example key). Set it " +
|
||||
"(appsettings or environment) to the same value as the relay's API_KEY, or select another " +
|
||||
"Seams:Sms:Provider.");
|
||||
}
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, path)
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Baya.Infrastructure.CrossCutting.Seams;
|
||||
|
||||
/// <summary>
|
||||
/// Options bound from the <c>Seams</c> configuration section. The mock seams read non-secret defaults
|
||||
/// from here; production keys/paths come from environment variables or user-secrets, never committed.
|
||||
/// from here; deployed keys/paths come from the environment-specific appsettings file or environment variables.
|
||||
///
|
||||
/// <para><b>Provider selection (refinement-phase-8).</b> Each vendor rail carries a <c>Provider</c> selector
|
||||
/// (default = the mock, so an unconfigured environment behaves exactly as before). Setting it to a real
|
||||
@@ -37,7 +37,7 @@ public sealed class SeamOptions
|
||||
/// (<c>IShahkarVerifier</c>), e-KYC (<c>IIdentityKycProvider</c>), and استعلام شبا
|
||||
/// (<c>IBankAccountOwnershipVerifier</c>). Each seam opts in with its own <c>Provider = finnotech</c> selector,
|
||||
/// but they authenticate against the same tenant, so the connection facts live here once. All values are
|
||||
/// secrets — user-secrets / environment, never committed.
|
||||
/// secrets — the environment-specific appsettings file or environment variables.
|
||||
/// </summary>
|
||||
public sealed class FinnotechOptions
|
||||
{
|
||||
@@ -63,8 +63,9 @@ public static class SeamProviders
|
||||
public const string SmsIr = "smsir";
|
||||
public const string Ghasedak = "ghasedak";
|
||||
|
||||
/// <summary><b>Development convenience channel, not an SMS gateway</b> — the local <c>telegram-otp-bot/</c>
|
||||
/// relay broadcasts every code to a fixed list of Telegram chat ids. Never select it in a real environment.</summary>
|
||||
/// <summary><b>Broadcast relay, not an SMS gateway</b> — the standalone <c>telegram-otp-bot/</c> service
|
||||
/// sends every code to a fixed list of Telegram chat ids. The pre-launch demo rail; replace with a real
|
||||
/// gateway before onboarding customers outside the trusted group.</summary>
|
||||
public const string Telegram = "telegram";
|
||||
|
||||
// Object storage
|
||||
@@ -105,7 +106,7 @@ public sealed class SmsOptions
|
||||
/// (Development only).</summary>
|
||||
public string Provider { get; set; } = SeamProviders.Mock;
|
||||
|
||||
/// <summary>Gateway API key / token (secret — user-secrets or environment, never committed).</summary>
|
||||
/// <summary>Gateway API key / token (secret — the environment-specific appsettings file or environment variables).</summary>
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>The registered sender line (used by <c>SendAsync</c> free-form messages and non-template sends).</summary>
|
||||
@@ -122,13 +123,15 @@ public sealed class SmsOptions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <b>Development-only</b> Telegram OTP relay (the standalone <c>telegram-otp-bot/</c> Node service),
|
||||
/// selected by <c>Seams:Sms:Provider = telegram</c>. It replaces "read the OTP out of the server log" during
|
||||
/// manual testing — the tester gets the code on their phone without paying an Iranian SMS gateway.
|
||||
/// The Telegram OTP relay (the standalone <c>telegram-otp-bot/</c> Node service), selected by
|
||||
/// <c>Seams:Sms:Provider = telegram</c>. It replaces "read the OTP out of the server log" — the tester gets the
|
||||
/// code on their phone without an Iranian SMS gateway contract.
|
||||
///
|
||||
/// <para><b>It is not an SMS gateway.</b> There is no per-user routing: the relay <i>broadcasts</i> every code
|
||||
/// to a fixed list of Telegram chat ids, so every configured recipient reads every login code. That is fine for
|
||||
/// a test group and disqualifying for anything else — never point a deployed environment at it.</para>
|
||||
/// to a fixed list of Telegram chat ids, so every configured recipient reads every login code. That is workable
|
||||
/// for a trusted demo group — which is why the pre-launch <c>balinyaar.ir</c> deployment uses it — and
|
||||
/// disqualifying once anyone outside that group can request a code. Switch <c>Seams:Sms:Provider</c> to
|
||||
/// <c>kavenegar</c> at that point; nothing else changes.</para>
|
||||
/// </summary>
|
||||
public sealed class TelegramOptions
|
||||
{
|
||||
@@ -137,7 +140,7 @@ public sealed class TelegramOptions
|
||||
|
||||
/// <summary>The shared secret sent as the relay's <c>X-Api-Key</c> header — it must equal the relay's
|
||||
/// <c>API_KEY</c>. <b>Secret:</b> committed config carries an empty/placeholder value; the real one comes
|
||||
/// from user-secrets (<c>Seams:Sms:Telegram:ApiKey</c>) or the environment, never git.</summary>
|
||||
/// from <c>Seams:Sms:Telegram:ApiKey</c> in appsettings or the environment.</summary>
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Per-request timeout. The relay itself talks to Telegram (over a proxy in a filtered region), so
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ public static class ServiceCollectionExtension
|
||||
/// token swaps in the real HTTP adapter behind the same Application contract — <b>callers never change</b>. An
|
||||
/// unconfigured/typo'd provider falls closed to the mock. This makes a partial rollout the normal case (real SMS
|
||||
/// + real geocoder while payments stay mocked in a pre-launch environment). Real adapters read credentials from
|
||||
/// <c>Seams:*</c> (user-secrets/environment) and get an <see cref="System.Net.Http.HttpClient"/> from the
|
||||
/// <c>Seams:*</c> (appsettings/environment) and get an <see cref="System.Net.Http.HttpClient"/> from the
|
||||
/// <c>IHttpClientFactory</c>. (The real in-app <c>INotificationDispatcher</c> needs the database, so it is
|
||||
/// registered in the Persistence layer.)
|
||||
/// </summary>
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class SeedDataBase : ISeedDataBase
|
||||
}
|
||||
|
||||
// The bootstrap admin is config-driven, never a committed credential: it is created only when both
|
||||
// Seed:AdminUsername and Seed:AdminPassword are supplied (via user-secrets in Development, environment
|
||||
// Seed:AdminUsername and Seed:AdminPassword are supplied (via the environment-specific appsettings file, environment
|
||||
// variables in a deployment). With neither configured — the default for Testing and any fresh boot —
|
||||
// no admin account is created, so no well-known password ever lands in a real database. Day-to-day
|
||||
// admins reach the backoffice through the phone-OTP demo seeds (Development) or are provisioned
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using Baya.Application.Contracts.Analytics;
|
||||
using Baya.Application.Contracts.Analytics;
|
||||
using Baya.Application.Contracts.Audit;
|
||||
using Baya.Application.Contracts.Common;
|
||||
using Baya.Application.Contracts.Configuration;
|
||||
@@ -129,7 +129,7 @@ public static class ServiceCollectionExtensions
|
||||
/// <summary>
|
||||
/// Idempotently seeds one active <c>standard</c> payment gateway so the b10 card rail has a selectable
|
||||
/// provider out of the box. <c>config_json</c> is encrypted at rest by the EF converter on save (so it
|
||||
/// must go through the DbContext, not <c>HasData</c>). Real merchant credentials come from user-secrets /
|
||||
/// must go through the DbContext, not <c>HasData</c>). Real merchant credentials come from appsettings /
|
||||
/// environment per deployment — this sandbox row is non-secret and only enables the local/dev flow.
|
||||
/// </summary>
|
||||
public static async Task SeedPaymentGatewaysAsync(this WebApplication app)
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class BayaApiFactory : WebApplicationFactory<Program>
|
||||
_keepAlive = new SqliteConnection(_connectionString);
|
||||
_keepAlive.Open();
|
||||
|
||||
// The committed appsettings.json ships placeholder JWE keys (real ones come from user-secrets /
|
||||
// The committed appsettings.json ships placeholder JWE keys (real ones come from the environment-specific appsettings file /
|
||||
// env in Development / deploy). The Testing host has neither, so supply working test keys via
|
||||
// environment variables — they sit after appsettings.json in the default config chain, so they
|
||||
// reliably override the placeholders. The Encrypt key must be exactly 16 bytes for the AES-128
|
||||
|
||||
Reference in New Issue
Block a user