diff --git a/README.md b/README.md index 89cac1c..3345710 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The same Function endpoint supports both SMS and voice. Each request's `channel` configured provider URL, and the SMS and voice URLs can differ. Validate every channel you intend to activate. -The screenshots show the Azure portal and the setup script. You can also register the application +The screenshots show the Azure portal. You can also register the application in the Microsoft Entra admin center. Identifying details are hidden; your portal layout may differ. ## 1. Get ready @@ -111,19 +111,10 @@ Setup first lists cached Azure CLI user subscriptions. Select the customer tenan then select or enter the dedicated application client ID and choose an Azure region. Refresh or sign in when the expected account is missing. -Choose the provider route and provider, then **one** platform: Node.js (JavaScript), .NET (C#), or Python. -When `-Provider` is omitted, setup reads the application's allowed-tenant configuration: no -supported provider means enter a provider name, one means confirm it, and several means choose -one by name. Provider choices are never numbered, and existing allowed provider tenants are preserved. -Setup configures both SMS and voice provider URLs without asking you to select a channel. -It downloads and verifies the package, builds it if needed, and deploys it for you. -You do not need a separate ZIP upload or local settings file. - -![Earlier PowerShell setup prompts for tenant, subscription, application, region, and SMS or voice, with identifiers hidden](docs/images/onboarding/setup-identifiers.png) - -*Use the dedicated application created in Step 2 and your intended Azure region. This screenshot -is from an earlier script version; current setup uses selection lists for the tenant, subscription, -application, and region, and no longer has the channel prompt. It is an input screen, not a deployment result.* +Choose the provider route and enter or confirm the provider **by name**, then choose **one** +platform: Node.js (JavaScript), .NET (C#), or Python. Setup configures both SMS and voice URLs; +there is no channel prompt. It downloads and verifies the package, builds it if needed, and +deploys it for you. You do not need a separate ZIP upload or local settings file. Choose **FC1** for on-demand hosting that can scale to zero, or **EP1** for paid warm capacity. FC1 can cold-start, and its free usage grant does not make the whole deployment free. @@ -132,24 +123,13 @@ In **Tenant scope**, choose Global or EU for your provider route. This does not Azure region or guarantee data residency. For the resource prefix, use 2-8 lowercase letters or digits, starting with a letter. -![Cropped setup choices for provider route, provider, language, and hosting plan, with provider names hidden](docs/images/onboarding/setup-choices.png) - -*Choose the options approved for your deployment; do not copy the example selection numbers. -The Global/EU choice is not the Azure region. The hosting-plan warning applies even when a -plan has a free usage grant.* +Complete any required CLI sign-in, MFA, or administrator consent yourself. Setup checks +prerequisites and prepares the deployment plan; resolve any reported failure before continuing. -Next, setup checks prerequisites, revalidates the Azure CLI and Microsoft Graph context, verifies -the selected package, and prepares the deployment plan. For C#, this includes an automatic local build. -Complete any required CLI sign-in, MFA, or administrator consent yourself. If a permission or prerequisite check fails, resolve it -before continuing. - -Setup also checks the application's existing **encryption certificates** before deployment. -If one exists, choose to reuse it and supply its **versioned Key Vault certificate URL**, or -replace it. If several exist, select one to keep and approve removal of the others, or replace -them all with one new certificate. Reuse preserves the selected key ID rather than adding another -credential. See [certificate selection](setup/docs/README.md#encryption-certificate-selection) -for the URL, permissions, and unattended options. Coordinate maintenance before changing an -active application's encryption credentials; every endpoint sharing that application is affected. +For a new application, setup creates its encryption certificate. If encryption credentials +already exist, follow [certificate selection](setup/docs/README.md#encryption-certificate-selection) +to reuse one exact Key Vault certificate version or approve replacement. Coordinate maintenance +first: changing these credentials affects every endpoint sharing the application. **Review the complete deployment plan before typing `Yes`.** Check the accounts, region, SMS and voice provider URLs, platform, plan, cache settings, resource names, permissions, and @@ -159,17 +139,12 @@ application-read permission. `No` or Enter cancels deployment; sign-in and prere are separate from this approval. **Want to review without deploying?** Run interactively and choose **No** at that final prompt. -There is no `-DryRun` or `-WhatIf` switch. This is not an offline dry run: sign-in, prerequisite -checks, downloads, and template compilation happen first. Do not pass `-ApproveDeployment` -when you only want to review the plan. +This still performs online preflight and downloads. There is no `-DryRun` or `-WhatIf` switch; +see [plan review](setup/docs/README.md#reviewing-the-plan-without-deploying) for details. After you approve, leave the script running while it configures the application, creates the Azure resources, and publishes the code. Wait for **Deployment completed** and the saved summary -before moving to Step 4. The input screenshots do not show a completed deployment. - -Normal console output ends with **Next steps** after resource generation. Use -`.\Setup-Epp.ps1 -PassThru` when you also need the structured deployment result in a variable -or pipeline; the summary JSON is always saved. +before moving to Step 4. Setup creates the Function App, storage, Key Vault, identities, and monitoring resources. **Do not create these resources manually first.** @@ -265,5 +240,5 @@ Deleting Azure resources does not undo an authentication policy change. | Understand the architecture, settings, or packages | [Technical reference](TECHNICAL.md) and [HTTP contract](docs/CONTRACT.md) | | Explore multiple Azure regions | [Manual Front Door guide](docs/FRONTDOOR.md); not included in guided setup | | Build a custom implementation | [JavaScript](javascript/README.md), [C#](dotnet/README.md), or [Python](python/README.md) | -| Implement the SAS-facing BYOT interface | [BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md); caller authentication, encrypted payload, and responses, with customer-owned service integration | +| Implement the Entra ID-facing BYOT interface | [BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md); caller authentication, encrypted payload, and responses, with customer-owned service integration | | Design primary-to-secondary provider fallback | [Developer walkthrough](docs/MULTI-PROVIDER-IMPLEMENTATION.md); not a built-in feature | diff --git a/TECHNICAL.md b/TECHNICAL.md index 045466d..1dcab2a 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -53,7 +53,7 @@ For local development, manual configuration, and deployment checks, see the [detailed configuration and validation guide](docs/ONBOARDING.md). For the provider-neutral inbound interface, read the -[BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md). It covers the SAS envelope, +[BYOT request and response guide](docs/BRING-YOUR-OWN-PROVIDER.md). It covers the Entra ID envelope, caller authentication, encrypted delivery context, and endpoint responses. The outbound service integration remains customer-owned; the guide does not add an adapter or guided setup support. @@ -163,16 +163,16 @@ extend it deliberately if you add runtime assets, and never put secrets in appli ## The design in one line -SAS → Easy Auth → anonymous HTTP handler (`POST /api/SendOtp`, validate envelope + decrypt JWE) → +Microsoft Entra ID → Easy Auth → anonymous HTTP handler (`POST /api/SendOtp`, validate envelope + decrypt JWE) → configured provider (API key or OAuth) → HTTP result with nonce on success. Only provider acceptance returns the nonce for live requests. Incoming `mode: 2` (evaluation) is the -generic shutter: after platform authentication, validate and decrypt, then echo the nonce without +non-delivery path: after platform authentication, validate and decrypt, then echo the nonce without calling a provider. See [docs/CONTRACT.md](docs/CONTRACT.md) for the full specification every implementation follows. Request `tenantId`, `channel`, `mode` and `ttlSeconds` are request data, not extra environment settings. -The trusted tenant issuer, endpoint-app audience and authorized SAS caller are configured in Easy Auth, +The trusted tenant issuer, endpoint-app audience and authorized Microsoft caller are configured in Easy Auth, not in application environment settings or incoming request data. ## Configure environment variables @@ -253,10 +253,12 @@ credential acquisition. Platform-managed identity caching and decryption-key ref Core Tools does not resolve Azure Key Vault reference expressions locally. Supply the local test PEM or base64 PEM directly; use a reference such as `@Microsoft.KeyVault(SecretUri=https://.vault.azure.net/secrets//)` for `EPP_DECRYPTION_KEY_PEM` in Azure app settings, where the platform resolves it. -Guided setup instead uses a **versioned** reference to -`secrets/phone-provider-encryption/`, containing a certificate and its exportable RSA private -key in PEM format. A new Key Vault certificate version does not automatically switch the Function -or update Entra. See [certificate lifecycle](setup/docs/README.md#encryption-certificate-lifecycle). +Guided setup uses the **versioned** backing-secret URL recorded as `certificateSecretId` in the +deployment summary. Initial creation uses `phone-provider-encryption`; replacement uses a suffixed +name, and reuse preserves the selected certificate's name and vault. The secret contains the +certificate and its exportable RSA private key in PEM format. Do not reconstruct its URL from the +certificate name or version. A new Key Vault certificate version does not automatically switch +the Function or update Entra. See [certificate lifecycle](setup/docs/README.md#encryption-certificate-lifecycle). Configure inbound issuer/audience/caller trust in **Easy Auth**, not these application variables. Incoming `tenantId`, `channel`, `mode` and `ttlSeconds` are request data and never override the @@ -276,16 +278,16 @@ The `telesign` adapter sends its JSON contract to the complete SMS or voice URL request channel from the configured endpoints. It does not append or infer a route. Basic authentication uses `base64(customer-id:api-key)`, with the existing Key Vault secrets -`telesign-customer-id` and `telesign-api-key`. Digest and Phase 2 token authentication are not +`telesign-customer-id` and `telesign-api-key`. Digest and provider-token authentication are not implemented. The incoming caller's Authorization header is never forwarded. -The adapter builds the following JSON from the decrypted delivery context and envelope: +For an SMS request, the adapter builds the following JSON from the decrypted delivery context and envelope: ```json { "recipient": { "phone_number": "+1234567890" }, "message": { "text": "Your verification code is 4821", "language": "en" }, - "channels": [{ "channel": "voice" }], + "channels": [{ "channel": "sms" }], "correlation_id": "unique-string-123" } ``` @@ -313,8 +315,8 @@ include only the provider HTTP status, mapped status/outcome and the bounded raw ID as `providerMessageId` for support lookup, never the raw response or description. Existing numeric success codes are retained (SMS: 200, 203, 290-292; Voice: 100-103). EPP code `3001` ("Message in progress"), observed for both channels, is also accepted on successful HTTP responses. This acknowledges -provider acceptance, not handset receipt or completed audio playback. The supplied EPP integration -overview does not provide a complete replacement status-code catalog. Missing, malformed, or +provider acceptance, not handset receipt or completed audio playback. The sample's recognized +codes are not a complete provider status-code catalog. Missing, malformed, or unknown codes fail closed, as do unsuccessful HTTP responses. Confirm the status-code catalog and account access with Telesign before production. @@ -323,12 +325,12 @@ account access with Telesign before production. **Easy Auth (App Service Authentication) is the only caller-authentication gate, before the anonymous Function.** Enable it with `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`. Configure the trusted tenant issuer and `allowedAudiences` for the endpoint app, -and a **nonempty `allowedApplications`** list pinned to the authorized SAS caller application ID. +and a **nonempty `allowedApplications`** list pinned to the authorized Microsoft caller application ID. Do not exclude the SendOtp path. The handler does not parse or validate bearer tokens, and there is no backup application validation or function-key gate. **Never expose this endpoint to the public internet with Easy Auth disabled or bypassed.** See [platform setup](docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust). -JWE decryption protects the payload but **does not authenticate SAS**: anyone with the public key can +JWE decryption protects the payload but **does not authenticate the caller**: anyone with the public key can encrypt a request. A nonce echo, including a fixed nonce, is not caller authentication. Provider API keys are read from **Key Vault** via **managed identity**; they authenticate the outbound provider call, not the inbound request. diff --git a/docs/BRING-YOUR-OWN-PROVIDER.md b/docs/BRING-YOUR-OWN-PROVIDER.md index 82dac10..7f6fb69 100644 --- a/docs/BRING-YOUR-OWN-PROVIDER.md +++ b/docs/BRING-YOUR-OWN-PROVIDER.md @@ -1,9 +1,9 @@ -# BYOT integration: the SAS request and response +# BYOT integration: the Entra ID request and response -**SAS (Strong Authentication Service)** sends your **External Phone Provider (EPP)** endpoint +**Microsoft Entra ID** sends your **External Phone Provider (EPP)** endpoint an encrypted phone number and one-time passcode message. Your endpoint verifies the caller, decrypts the details, and either evaluates the request without sending or uses your phone service. -It then responds to SAS. **Your team owns the connection to that service.** +It then responds to Entra ID. **Your team owns the connection to that service.** Before deploying, ask your tenant administrator to confirm [eligibility, testing, and activation with Microsoft](ONBOARDING.md#before-purchasing-or-deploying). @@ -13,13 +13,13 @@ Deploying the endpoint alone does not enable tenant policy. ## 1. Verify the caller -SAS sends **`POST /api/SendOtp` over HTTPS**, with a signed Entra bearer token in `Authorization`. +Entra ID sends **`POST /api/SendOtp` over HTTPS**, with a signed bearer token in `Authorization`. In this sample, **Easy Auth** (Azure App Service Authentication) checks the token and authorizes the caller before the Function runs. The Function's trigger is anonymous and has no backup authentication check, so do not expose it with Easy Auth disabled or bypassed. Require HTTPS and authentication, return `401` for unauthenticated requests, and restrict access -to the trusted tenant issuer, endpoint-app audience, and authorized SAS caller application ID. +to the trusted tenant issuer, endpoint-app audience, and authorized Microsoft caller application ID. Your Azure operator should use the endpoint app registration and [approved setup configuration](../setup/support/Epp.Setup.psm1) for these values, then verify **Function App > Authentication**. Follow the @@ -28,9 +28,7 @@ caller-ID guidance, and token-version details. **Encryption is not caller authentication.** Anyone with the public key can create a JWE (JSON Web Encryption) payload. Neither its nonce, the body's `tenantId`, nor tracing or -forwarded principal headers prove that SAS sent it. - - +forwarded principal headers prove that Entra ID sent it. ## 2. Read and decrypt the request @@ -93,7 +91,7 @@ Configured workers may independently prepare or refresh credentials, but that do **Live (`mode: 1` or `"live"`):** your team implements and maintains the service integration. Map the requested SMS or voice delivery to your service, preserving the destination, message, and passcode. Handle voice rendering and locales as needed. Authenticate with your own service -credentials; **never forward SAS's bearer token**. +credentials; **never forward the incoming bearer token**. Wait for recognized acceptance before returning success. A queued or pending result counts only if your integration recognizes it as acceptance. Unknown or incomplete responses must fail, @@ -104,9 +102,7 @@ The sample uses one configured service per deployment. A custom integration does guided setup support. Follow [Where to add your integration](#where-to-add-your-integration) for the files to create, registration, settings, and packaging. - - -## 4. Respond to SAS +## 4. Respond to Entra ID Return **HTTP `200` with a JSON object containing exactly these fields** after a successful evaluation or recognized live acceptance: @@ -122,8 +118,11 @@ Correlation header sanitization and empty-value handling differ by runtime; see the internal attempt `messageId`, with a generated fallback. These IDs do not provide deduplication. **For live requests, echo the nonce only after acceptance, not before sending.** -Acceptance is not proof of handset receipt. SAS treats a `2xx` response with a matching nonce -as handled; a mismatched nonce, non-`2xx` response, or timeout causes SAS to fall back to native delivery. +Acceptance is not proof of handset receipt. The sample's integration contract expects Entra ID to +recognize a `2xx` response with a matching nonce; failures or timeouts can lead to native fallback. +This repository does not implement or verify the Entra ID caller's current fallback or retry policy. +Confirm that behavior through the [approved test procedure](ONBOARDING.md#validate-the-deployed-endpoint), +including whether a lost response can lead to another send. On failure, omit both the nonce and accepted status. Handler errors contain `error` and a generated `requestId`, plus a fixed `reason` for validation errors and `correlationId` where @@ -136,8 +135,8 @@ errors can occur before the handler and need not use this JSON shape. Use the Choose **one runtime** and work in its source directory. Keep Easy Auth and the shared handler's envelope validation, JWE decryption, evaluation branch, and nonce responses. Your provider code -builds the outbound request and interprets the service result; it does not authenticate SAS or -write the HTTP response to SAS. +builds the outbound request and interprets the service result; it does not authenticate the +incoming caller or write the HTTP response to Entra ID. ### Add and register the provider diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md index 3e616cd..d9b5f36 100644 --- a/docs/CONTRACT.md +++ b/docs/CONTRACT.md @@ -22,8 +22,8 @@ is separate from this contract and must not deliver OTPs. **Endpoint:** `POST /api/SendOtp` (Functions HTTP trigger, `authLevel: anonymous`). **Easy Auth is the only caller-authentication boundary and runs before the handler**; the application has no token -validator or function-key gate. This is the interface **SAS (StrongAuthenticationService)** calls. -SAS sends an Entra bearer JWT as part of its protocol; Easy Auth validates it, not the handler. +validator or function-key gate. This is the interface **Microsoft Entra ID** calls. +Entra ID sends a signed bearer JWT; Easy Auth validates it, not the handler. PII (phone number + the rendered message, which contains the passcode) is **encrypted** inside a JWE; the cleartext envelope carries routing/scheduling only. @@ -102,7 +102,7 @@ Decryption failure → `400`. Missing `nonce` / `phoneNumber` / `message` → `4 For Soprano live voice, the provider finds the first six consecutive digits in `message`, preserving leading zeros, and splits the rendered text into `beforePasswordText`, `password`, and -`afterPasswordText`. It uses a nonblank SAS `locale` as `language`, falling back to `en-US`, and sends +`afterPasswordText`. It uses the request's nonblank `locale` as `language`, falling back to `en-US`, and sends fixed `gender: 1` and `loop: 2`. The resulting object is sent as `voice.text2voice` without a top-level `text` field. A message without a six-digit sequence fails closed before provider HTTP. No additional environment settings are required. Soprano SMS continues to forward `message` unchanged, and @@ -124,7 +124,7 @@ federated credential trusts the identity's principal ID, home-tenant v2 issuer, `api://AzureADTokenExchange` audience. Key Vault identity selection remains independent. Only the final application token is sent as `Authorization: Bearer ...`. No Soprano API ID/key, -managed-identity assertion, or incoming SAS token is forwarded. Missing settings, token-acquisition +managed-identity assertion, or incoming Entra ID token is forwarded. Missing settings, token-acquisition failure, blank tokens, or tokens with 30 seconds or less remaining lifetime fail before provider HTTP; there is no API-key fallback. Evaluation skips acquisition. A provider rejection is not retried. Tokens are treated as opaque: the Function checks SDK expiry metadata, not custom JWT claims. @@ -148,27 +148,19 @@ acquisition's asynchronous context. Python filters Azure Identity/Core/MSAL reco handlers in that context; configure logging sinks before handling requests. .NET disables credential diagnostics. Keep platform body tracing off and never log credential objects or tokens. -When migrating from the earlier optional-JWT branch, replace `EPP_PROVIDER_APPLICATION_ID` with -`EPP_OUTBOUND_CLIENT_ID` and `EPP_PROVIDER_MI_CLIENT_ID` with `EPP_OUTBOUND_MI_CLIENT_ID`. -Remove `EPP_PROVIDER_JWT_ENABLED`; it no longer controls authentication. Reuse the exact provider -scope selected by setup and replace old base URLs with complete send URLs. These source changes -do not update deployed settings or establish provider authorization. Earlier QA4 tests of API keys -plus JWT do not validate the current Bearer-only configuration or production endpoints. +Use the exact provider scope and complete send URLs selected by setup. Source changes do not +update deployed settings or establish provider authorization. Validate the current OAuth +configuration with your provider before activation. See [Microsoft's managed-identity federation guidance](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity). -Use a SAS locale supported by the selected Soprano endpoint and account. On QA4, an API-key -voice request using `en` returned HTTP `400` with error code `400101`; the same request structure -using `en-US` returned HTTP `201` with `ENROUTE` on September 15, 2026. This confirms acceptance, -not handset receipt or audio quality. When SAS omits the locale, the providers use `en-US`. +Confirm that the request's locale is supported by your Soprano endpoint and account. The Soprano +adapter uses `en-US` when the locale is absent or blank; a nonblank value is forwarded without +checking provider support. Test recipient audio separately from API acceptance. +This adapter uses the JSON `/messages/omnimsg` interface configured by the provider profile, +not a form-encoded voice API. Use the field names and response mapping in the selected adapter. -The supplied Soprano Connect Voice PDF describes a different API: `POST /voice/voice_orderApiCreate.do` -with form-encoded fields, `subAction=20`, and numeric language IDs (`1` is default English). -Its password fields are `beforePassword`, `passwordText`, and `afterPassword`. This provider follows -the reference integration's JSON `/messages/omnimsg` contract instead; do not mix the form API's -language IDs, field names, or `ApiResponse.StatusCode` response format with this JSON interface. - -JWE provides payload confidentiality and integrity, **not SAS caller authentication**. Anyone with the +JWE provides payload confidentiality and integrity, **not caller authentication**. Anyone with the public key can encrypt a request. The nonce acknowledges decryption; it is not an authentication credential or replay protection, and a fixed nonce cannot substitute for Easy Auth. @@ -178,10 +170,17 @@ credential or replay protection, and a fixed nonce cannot substitute for Easy Au { "nonce": "", "correlationId": "", "providerStatus": "accepted" } ``` -`accepted`/`pending` are **not** failures (provider queued it; acceptance ≠ delivery to the handset). -The endpoint returns **`200`** on acceptance (any `2xx` counts as transport acceptance). On `2xx` **with a -matching nonce**, SAS treats the send as handled. **Nonce mismatch / non-2xx / timeout → SAS falls back -to native CAPP delivery.** `Evaluation` mode returns `200` + nonce echo without delivering. +The endpoint returns **`200`** only after the adapter recognizes acceptance on a successful +provider HTTP response. A queued or pending status counts only when that adapter explicitly +recognizes it; a provider `2xx` alone is insufficient. Acceptance is not handset delivery. +`Evaluation` mode returns `200` + nonce echo without a provider send. + +The sample's integration contract expects the caller to treat `2xx` with a matching nonce as +handled; a nonce mismatch, non-`2xx` response, or timeout can lead to native fallback. +This repository does not implement or verify the Entra ID caller's current fallback, retry, +or timing behavior. Confirm it through the +[approved EPP procedure](ONBOARDING.md#validate-the-deployed-endpoint); +do not rely on fallback as a guaranteed recovery mechanism. Live handlers await provider acceptance; they do not launch background delivery after replying. Handler failures omit the nonce and accepted status and return a sanitized error with a request ID @@ -227,7 +226,7 @@ See the handlers for exact binding and response paths: [JavaScript](../javascript/src/functions/SendOtp.js), [Python](../python/function_app.py), and [.NET](../dotnet/Functions/SendOtp.cs). -### Evaluation (generic shutter) +### Evaluation (no provider send) The only shared non-delivery control is the existing incoming `mode: 2` or `mode: "evaluation"`, for every provider and language. On Azure, Easy Auth still authenticates the caller before the @@ -264,6 +263,7 @@ implemented `StepUp`/`409` mapping. | `Fail` | `401` | provider returned 401/403 (auth) | | `Fail` | `400` | other provider 4xx | | `Fail` | `502` | other provider error, or missing credential/endpoint | +| N/A | `400` | configured provider name is missing or not registered | | N/A | `504` | request to the provider timed out | | N/A | `502` | network error to the provider (non-timeout) | @@ -279,8 +279,9 @@ Each provider owns: - safe failure classification: `provider_http_error`, `provider_rejected`, `unrecognized_provider_status`, `missing_provider_message_id` or `invalid_provider_json`. -The shared transport owns only final HTTPS validation, a capped timeout, manual redirects, -`AbortController`/cancellation through response-body reading and one JSON parse. The provider receives +The shared transport owns final HTTPS validation, timeout handling, disabled redirects, and +response-body reading and decoding. JavaScript/.NET propagate cancellation through body reading; +Python uses connect/read inactivity timeouts. The provider receives the transport result and returns a normalized provider result. Raw API-specific JSON is not inspected by the Function and never enters public responses or logs. @@ -289,9 +290,9 @@ This model is internal: do not serialize it into the endpoint response or logs. status, the mapped outcome and a bounded raw provider message ID; descriptions and raw metadata remain private. Public HTTP responses still expose only the existing nonce/correlation/status or sanitized error contract. -Provider requests are serialized only when building the outbound HTTP body; incoming provider JSON -is parsed once and normalized inside its provider. No serialization framework or provider-specific -class hierarchy is required. +Provider requests are serialized when building the outbound HTTP body. JavaScript/Python map +parsed response objects; .NET deserializes provider-specific response types through +`PhoneProviderBase.SendJsonAsync`. The handler uses only the normalized result. Consult the selected provider implementation for required credentials and options. The [onboarding credential naming table](ONBOARDING.md#provider-credential-names) lists exact secret @@ -304,12 +305,12 @@ SMS and Voice use the complete provider-approved URLs selected from the provider supplies `recipient.phone_number`, `message.text` (unchanged for SMS, paced/repeated for voice), optional `message.language`, one selected `channels[].channel`, and `correlation_id`. Keep the leading `+` in the E.164 phone number. -Phase 1 supports Basic and Digest; this sample implements Basic only. Per +This sample implements Basic authentication only. Per [Telesign's authentication instructions](https://developer.telesign.com/enterprise/docs/authentication#basic-authentication), the header is `Authorization: Basic `, using the raw Customer ID and API Key strings from Key Vault. Do not decode the API key first, send the API key alone, or substitute a key identifier. The guide's `Basic YOUR_API_KEY` is abbreviated, not the literal encoding. -Provider-token authentication is described as Phase 2 and is not implemented for Telesign here. +Digest and provider-token authentication are not implemented for Telesign here. The optional `account_lifecycle_event` and `originating_ip` fields are reserved for future intelligence capabilities. They are omitted; do not infer an originating address from the Function or synthesize @@ -352,7 +353,7 @@ managed identity. Soprano exchanges an outbound managed-identity assertion for a configured provider tenant/scope. Do not put provider secrets in code or app settings. Caller trust is configured in **Easy Auth**, not application environment variables: pin the trusted -tenant issuer, the endpoint-app audience and the authorized SAS caller application ID. Incoming +tenant issuer, the endpoint-app audience and the authorized Microsoft caller application ID. Incoming `tenantId` is routing metadata and cannot select any of these. There is no application host-detection guard or backup token validation. See [platform onboarding](ONBOARDING.md#2-provision-encryption-and-deployment-trust). @@ -463,13 +464,13 @@ Do not log SDK exception details or tokens. No cache retries/deduplicates provid - **Platform authentication only**: enable Easy Auth with `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`. Configure the trusted tenant issuer and `allowedAudiences` for the endpoint app, plus a **nonempty `allowedApplications`** list pinned to - the authorized SAS caller application ID. No excluded path may bypass authentication for SendOtp. + the authorized Microsoft caller application ID. No excluded path may bypass authentication for SendOtp. The trigger remains `authLevel: anonymous`; there is no application token validation or function-key fallback. The platform rejects unauthenticated requests with `401` and denies callers outside the allowlist before the handler. **Never expose the endpoint to the public internet with Easy Auth off or bypassed.** Core Tools supplies no Easy Auth: local execution must bind only to loopback, with no tunnels or public forwarding. Neither request data, JWE decryption, a fixed nonce nor forwarded - principal headers authenticate the SAS caller. + principal headers authenticate the Entra ID caller. - **Timeout boundaries**: platform authentication and Key Vault retrieval happen outside the outbound HTTP timer. Python uses connect/read inactivity timeouts, not a hard elapsed-time deadline. The cap therefore does not guarantee a 3.2-second end-to-end response, especially on cold starts. diff --git a/docs/COST-CALCULATOR.md b/docs/COST-CALCULATOR.md index 3a8326d..f2dbe54 100644 --- a/docs/COST-CALCULATOR.md +++ b/docs/COST-CALCULATOR.md @@ -28,6 +28,12 @@ account must be allowed to consent to or use these Microsoft Graph delegated per - `AuditLog.Read.All` - `UserAuthenticationMethod.Read.All` +Reading other users' phone methods also requires a +[supported Entra role](https://learn.microsoft.com/en-us/graph/api/authentication-list-phonemethods?view=graph-rest-1.0#permissions), +not just consent to those scopes. Authentication Administrator receives masked phone numbers; +if the calling code is unavailable, the script reports `Unknown`. Confirm approved read access +with your identity administrator rather than granting broader roles just to fill the report. + Access to sign-in logs also depends on your Microsoft Entra licensing, directory role, and log-retention period. Review the requested permissions before consenting. The script processes sign-in authentication details that can include phone numbers. It exports only diff --git a/docs/FRONTDOOR.md b/docs/FRONTDOOR.md index 480ac98..f6020ab 100644 --- a/docs/FRONTDOOR.md +++ b/docs/FRONTDOOR.md @@ -52,6 +52,8 @@ Give each Function App, storage account, and Key Vault a globally unique name. E its own identity IDs and vault references. Keep the **provider and authentication configuration consistent and use the same RSA key**, rather than copying every setting literally. The SMS/voice arrows show live delivery. Evaluation returns the nonce without contacting the provider. +The diagram's provider-secret arrows apply to API-key integrations; OAuth uses each region's +outbound managed identity. Match the key material across regions, not the vault URLs or version IDs. | Component | Purpose | |---|---| @@ -296,9 +298,10 @@ double-load capacity, complete dependency isolation, or a full regional outage. Run only in an approved, isolated nonproduction environment, with synthetic data, bounded load, and the [fire-drill's approval, abort, and restoration safeguards](#6-regional-failover-fire-drill). -Use an authorized test harness calling the Front Door endpoint, not a real Entra/SAS sign-in flow. -**SAS evaluation can trigger native fallback; do not assume it is non-delivering.** Real-SAS and -delivery testing require separate safety approval and are not part of this checklist. +Use an authorized test harness calling the Front Door endpoint, not an actual Entra ID sign-in flow. +**Function evaluation suppresses sends only inside this endpoint.** Confirm Entra ID fallback +and retry behavior with Microsoft rather than assuming a whole sign-in flow is non-delivering. +Entra ID sign-in and delivery testing require separate safety approval and are not part of this checklist. Agree load, headroom, recovery time, error/latency limits, soak duration, and restoration deadlines before starting; do not substitute the historical measurements for your acceptance targets. diff --git a/docs/MONITORING.md b/docs/MONITORING.md index 5f85575..54f0f58 100644 --- a/docs/MONITORING.md +++ b/docs/MONITORING.md @@ -513,10 +513,10 @@ component/role/region grouping for the multi-region query. | Starting rule | Query adaptation | Condition | | --- | --- | --- | -| Elevated request failure rate | Append `| where Requests >= 20 and FailurePct > 5` to the rolling-window summary. | Table rows > 0; evaluate every 5 minutes; require 2 of 3 evaluations. | -| High request latency | Append `| where Requests >= 20 and P95Ms > 2000` to the same summary. | Table rows > 0; evaluate every 5 minutes; require 2 of 3 evaluations. | +| Elevated request failure rate | Append a `where` filter with `Requests >= 20 and FailurePct > 5` to the rolling-window summary. | Table rows > 0; evaluate every 5 minutes; start with one violating evaluation. | +| High request latency | Append a `where` filter with `Requests >= 20 and P95Ms > 2000` to the same summary. | Table rows > 0; evaluate every 5 minutes; start with one violating evaluation. | | JavaScript credential failures | Use the service-event query with `ago(15m)` and only `credential_refresh_failed`. | Table rows > 0; evaluate every 5 minutes; start with a warning. | -| Expected origin missing | Append `| where MissingTelemetry` to the inventory query. | Table rows > 0; 20-minute lookback; evaluate every 5 minutes after accounting for ingestion delay. | +| Expected origin missing | Append a `where MissingTelemetry` filter to the inventory query. | Table rows > 0; 20-minute lookback; evaluate every 5 minutes after accounting for ingestion delay. | The minimum request count deliberately avoids unstable percentages on tiny samples; it also means low-traffic failures need separate investigation or @@ -525,6 +525,11 @@ traffic, provider behavior, and your error budget. Use dimension splits for regional rules; do not configure a numeric threshold on an already filtered table-row query as though it returned raw events. +These rolling summaries no longer return a datetime column. Do not select multiple violations +for them as written: [Azure Monitor requires datetime output][log-alerts] for that option. +For a sustained-failure rule, have the query maintainer design time-bucketed output and align +the query lookback, aggregation granularity, and evaluation period before enabling it. + Verify cross-workspace access for the alert's evaluation identity, enable appropriate resolution behavior, and inspect rule health. Query/permission failures must be visible to operators. A Workbook that loads under your own diff --git a/docs/MULTI-PROVIDER-IMPLEMENTATION.md b/docs/MULTI-PROVIDER-IMPLEMENTATION.md index 6d7a76a..c7f4a8b 100644 --- a/docs/MULTI-PROVIDER-IMPLEMENTATION.md +++ b/docs/MULTI-PROVIDER-IMPLEMENTATION.md @@ -1,7 +1,7 @@ # Add conservative primary-to-secondary provider fallback You can adapt this application to try a secondary provider **only when the primary is known -not to have accepted the message**. Keep one SAS-facing URL, `/api/SendOtp`, with a primary +not to have accepted the message**. Keep one URL for Microsoft Entra ID, `/api/SendOtp`, with a primary account and a secondary account for the same SMS channel. You choose their order according to your approved account policy; these roles do not imply a vendor ranking or recommendation. The caller does not select an account or supply a provider header. @@ -192,7 +192,7 @@ Record operation state, safe provider context ID and per-attempt classifications request logs. Do not store/log bodies, OTPs or credentials as attempt evidence. Retention and key reuse rules must cover the caller's replay window. A store guard cannot atomically commit an external provider send: it is **not provider-side idempotency or true exactly-once delivery**. -Real SAS/native fallback outside this endpoint can still produce duplicates; coordinate that +Entra ID retries or native fallback outside this endpoint can still produce duplicates; confirm that behavior with the owner rather than promising the local guard prevents it. ## 5. Integrate one guarded fallback into the handler @@ -309,7 +309,7 @@ store faults; block external network. Verify: An offline policy-model experiment can check those branches, but a fake classifier does not prove any real provider's nonacceptance semantics, and a fake store does not prove distributed -durability. Earlier routing/isolation tests are **not fallback tests**. No production +durability. Routing/isolation tests alone are **not fallback tests**. No production nonacceptance classifier is supplied here; keep response-based fallback disabled until reviewed. Use one Function App/package and the existing `/api/SendOtp` entry point. Deploy new code for the @@ -321,10 +321,10 @@ or editing Markdown needs no redeployment. Preserve inbound authentication and origin/network restrictions, including any Front Door forwarding. Regional failover is separate from provider fallback. Before live use, review account permissions/consent, sender/recipient entitlement, durable-store guarantees and caller -retry/native-fallback behavior. Resolve exposed-credential rotation first. Any real SAS or +retry/native-fallback behavior. Rotate any exposed credentials before testing. Any Entra ID sign-in or provider test needs separate authorization, an approved recipient, explicit attempt limits and stop conditions for uncertainty. HTTP 200/`PENDING` is not handset receipt. Nothing here authorizes -a send or proves real SAS integration, capacity, regional resilience or live delivery. +a send or proves Entra ID integration, capacity, regional resilience or live delivery. ## .NET and Python integration pointers diff --git a/docs/SIGN-IN-MONITORING.md b/docs/SIGN-IN-MONITORING.md index da5eedb..31fb9bb 100644 --- a/docs/SIGN-IN-MONITORING.md +++ b/docs/SIGN-IN-MONITORING.md @@ -404,7 +404,7 @@ saving an alert query. Portal JSON downloads are not Log Analytics rows. **Failed Signin reasons** groups by code/reason; **Failed login Count** counts failures by resource; **Signin Locations** groups success/failure by location. Pick the measure you need. Some examples contain personal fields or limit MFA failures to one code; none is a - complete CYOT/provider-failure detector. + complete endpoint/provider-failure detector. 2. Add your source tenant/app filters **before aggregation**. Use the [filtering tutorial](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-tutorial#apply-multiple-query-filters) or [Simple mode column filters](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-simple-mode#filter-by-column), diff --git a/dotnet/README.md b/dotnet/README.md index 830badd..5f647df 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -31,7 +31,7 @@ used JavaScript; validate an equivalent .NET readiness implementation and deploy [Easy Auth](../docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust), the only authentication gate before the anonymous Function. Enable `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the trusted tenant issuer, - endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized SAS + endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized Microsoft caller. Do not exclude SendOtp. There is no backup application token validation; never expose the endpoint to the public internet with Easy Auth disabled or bypassed. @@ -91,19 +91,19 @@ or the offline tests' injected environment and secret resolver for local develop `mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not deployment authentication settings. Easy Auth authenticates and authorizes the caller before the anonymous handler validates the payload and decrypts the JWE. Incoming `Authorization` is not parsed or echoed by the handler. JWE does not -authenticate SAS: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. +authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. Requests that bind successfully retain the application validation, PII-safe logging and response lifecycle. -Use incoming `mode: 2` or `mode: "evaluation"` as the generic shutter for every provider: platform -authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault +For any provider, use incoming `mode: 2` or `mode: "evaluation"` to evaluate without sending. +Platform authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault reads and provider HTTP do not. No provider configuration or diagnostic environment flag is required. Live requests use the configured provider's API key or OAuth token and await acceptance before returning the nonce; failures omit it. Acceptance is not handset delivery. Platform/key prerequisites and HTTP outcomes are defined in the -[contract](../docs/CONTRACT.md#evaluation-generic-shutter). +[contract](../docs/CONTRACT.md#evaluation-no-provider-send). For Soprano voice, the adapter extracts the first six-digit passcode from the rendered message. It -uses a nonblank SAS request locale as the language, falling back to `en-US`, and sends fixed gender +uses the request's nonblank locale as the language, falling back to `en-US`, and sends fixed gender `1` and loop `2`. These values require no additional environment settings. Soprano SMS continues to forward the rendered message unchanged. @@ -113,7 +113,7 @@ six-digit numeric run that is not part of a longer number and repeats the comple ## Source Start with the provider-neutral -[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the SAS payload, +[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, caller authentication, and response rules. Your team owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and @@ -122,14 +122,15 @@ The host's OpenTelemetry setting does not by itself initialize a direct worker e verify actual `ILogger` collection before depending on event-based alerts. `SendOtp` validates and decrypts the request, selects the configured provider in its private -`SelectProvider` method, resolves that provider's credentials, builds and sends the common bounded HTTP -request, asks the provider to deserialize its typed response DTO and return the final outcome, then maps that -outcome to the endpoint HTTP status. Providers -return standard `HttpRequestMessage` instances with typed `JsonContent`; the shared transport sends those -messages directly with redirects disabled, `ResponseHeadersRead`, and the bounded timeout. Each provider maps -its own normalized response status to the final outcome and reports whether that status was recognized. The -default method selects by `EPP_PROVIDER_NAME`; replace only its body if deployment policy later needs country, -tenant or other request-aware selection. No router or routing configuration abstraction is required. +`SelectProvider` method, resolves credentials, and calls the provider's `SendOtpAsync`. +That method passes its request factory and typed response mapper to +`PhoneProviderBase.SendJsonAsync`, which builds and sends the `HttpRequestMessage`, +reads/deserializes the response, and returns a normalized `ProviderResult`. +`SendOtp` maps that result to the endpoint HTTP response. +The shared transport uses redirects disabled, `ResponseHeadersRead`, and a bounded timeout. +Each provider maps its recognized statuses to an outcome; selection uses `EPP_PROVIDER_NAME`. +Request-aware routing or multiple accounts require a separate design for trust, configuration, +credentials, and testing, not just a change to `SelectProvider`. `CredentialTokenService` is the hosted startup warmer and runtime credential cache. `EPP_KEY_VAULT_CACHE_ENABLED` controls API-key caching; `EPP_ACCESS_TOKEN_CACHE_ENABLED` controls @@ -146,7 +147,7 @@ shared-acquisition behavior applies here. Evaluation remains independent. | Source | Purpose | |---|---| | [Program.cs](Program.cs) | Host, provider, credential cache and HTTP client registration | -| [Functions/SendOtp.cs](Functions/SendOtp.cs) | HTTP handler, provider selection, delivery orchestration and common HTTP transport | +| [Functions/SendOtp.cs](Functions/SendOtp.cs) | HTTP handler, provider selection, credential resolution and delivery orchestration | | [Src/AppConfig.cs](Src/AppConfig.cs) | Shared deployment settings | | [Src/EntraSendOtpPayload.cs](Src/EntraSendOtpPayload.cs) | Bound request model, strict channel/mode converters and semantic validation | | [Src/JweDeliveryContext.cs](Src/JweDeliveryContext.cs) | Pinned JWE decryption and decrypted delivery context | @@ -163,5 +164,5 @@ credential resolution, request construction and private typed response DTOs. The method delegates to the base `SendJsonAsync` transport and maps the typed response into `ProviderResult`; provider mapping code never handles `HttpResponseMessage` or raw JSON DOM types. Provider-specific response semantics stay in each provider; no shared response-mapping -registry is used. Common async HTTP transport remains in `SendOtp`. See +registry is used. Common async HTTP transport lives in `PhoneProviderBase`. See [production limitations](../docs/CONTRACT.md#production-limitations) before production use. diff --git a/javascript/README.md b/javascript/README.md index 833a0a5..460301f 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -34,7 +34,7 @@ sample's release package. No Front Door deployment script is supplied. Auth is the only caller-authentication gate before the anonymous Function. Enable `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the trusted tenant issuer, endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for - the authorized SAS caller. Do not exclude SendOtp. There is no backup application token validation; + the authorized Microsoft caller. Do not exclude SendOtp. There is no backup application token validation; never expose the endpoint to the public internet with Easy Auth disabled or bypassed. 5. Install dependencies from [package.json](package.json), run its offline test script and start the local Functions host from this folder. Core Tools has no Easy Auth: bind only to loopback, with no @@ -75,16 +75,13 @@ once per call to `readConfig()`. Restart the host after changing settings. Confi storage other than Azurite separately; do not copy a local emulator connection into Azure. Core Tools does not resolve Key Vault references locally; supply the local test PEM or base64 PEM directly. -Older private settings may contain `DEFAULT_PROVIDER`, `ENDPOINT_TIMEOUT_MS`, `REQUIRE_AUTH`, -`EXPECTED_AUDIENCE`, `ISSUER_TENANT_ID`, `EUDB`, or per-provider `*_ENDPOINT` entries. Those do not -configure the current shared engine. Use `EPP_PROVIDER_NAME`, the endpoint settings above and -`EPP_PROVIDER_TIMEOUT_MS` instead; configure caller authentication in Easy Auth. Keep provider options -that are actually read, such as a service-plan ID. Private integration helpers may -load settings from another location or use test credential variables, but the Function itself does not. +Use `EPP_PROVIDER_NAME`, the channel-specific endpoint settings above, and `EPP_PROVIDER_TIMEOUT_MS`; +configure caller authentication in Easy Auth, not application settings. Add only provider options +read by the selected adapter, such as a service-plan ID. The Soprano provider uses the configured complete endpoint and an OAuth bearer token. For voice, it extracts the first six-digit passcode from the rendered message and sends fixed synthesis values: -gender `1` and loop `2`. It uses a nonblank SAS request locale as the language, falling back to +gender `1` and loop `2`. It uses the request's nonblank locale as the language, falling back to `en-US` when the locale is absent or invalid. These values require no additional environment settings. Soprano SMS continues to forward the rendered message unchanged. The Telesign provider uses the configured complete endpoint and API-key credentials from Key Vault. Telesign SMS forwards @@ -100,15 +97,15 @@ provider lookup, while tests inject mocked credentials and HTTP. Keep the local Easy Auth authenticates and authorizes the caller before `POST /api/SendOtp`; the anonymous handler validates the envelope and decrypts the JWE, without parsing or echoing incoming `Authorization`. -JWE does not authenticate SAS: anyone with the public key can encrypt a request, and a fixed nonce +JWE does not authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. Request `mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not environment settings or sources of identity trust. SMS forwards the caller-rendered message; voice uses the provider-specific extraction/pacing described above. -For non-delivery validation, use incoming `mode: 2` or `mode: "evaluation"`. This generic shutter +For non-delivery validation, use incoming `mode: 2` or `mode: "evaluation"`. This evaluation path works for every provider without provider configuration, provider Key Vault reads or provider HTTP; platform authentication on Azure and handler decryption still run. No diagnostic environment flag is needed. See the -[evaluation contract](../docs/CONTRACT.md#evaluation-generic-shutter) for authentication/key prerequisites. +[evaluation contract](../docs/CONTRACT.md#evaluation-no-provider-send) for authentication/key prerequisites. Live requests use the configured provider's API key or OAuth token and await acceptance before returning the nonce. Acceptance is not handset delivery; failures omit the nonce, and timeouts must not trigger blind @@ -117,7 +114,7 @@ retries. The shared contract defines validation, HTTP outcomes and privacy-safe ## Source and extension points Start with the provider-neutral -[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the SAS payload, +[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, caller authentication, and response rules. Your team owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and the diff --git a/python/README.md b/python/README.md index 033de18..8a2e810 100644 --- a/python/README.md +++ b/python/README.md @@ -31,7 +31,7 @@ used JavaScript; validate an equivalent Python readiness implementation and depl [Easy Auth](../docs/ONBOARDING.md#2-provision-encryption-and-deployment-trust), the only authentication gate before the anonymous Function. Enable `requireAuthentication=true`, `unauthenticatedClientAction=Return401` and `requireHttps=true`; pin the trusted tenant issuer, - endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized SAS + endpoint-app `allowedAudiences` and a nonempty `allowedApplications` list for the authorized Microsoft caller. Do not exclude SendOtp. There is no backup application token validation; never expose the endpoint to the public internet with Easy Auth disabled or bypassed. 4. Use a virtual environment, install [requirements.txt](requirements.txt) and pytest, then run the @@ -86,18 +86,18 @@ tests on an ordinary workstation, and bind local hosts only to loopback. `mode`, `channel`, `ttlSeconds` and `tenantId` are request data, not deployment authentication settings. Easy Auth authenticates and authorizes the caller before the anonymous handler validates the envelope and decrypts the JWE. Incoming `Authorization` is not parsed or echoed by the handler. JWE does not -authenticate SAS: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. +authenticate the caller: anyone with the public key can encrypt a request, and a fixed nonce is not authentication. -Use incoming `mode: 2` or `mode: "evaluation"` as the generic shutter for every provider: platform -authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault +For any provider, use incoming `mode: 2` or `mode: "evaluation"` to evaluate without sending. +Platform authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault reads and provider HTTP do not. No provider configuration or diagnostic environment flag is required. Live requests use the configured provider's API key or OAuth token and await acceptance before returning the nonce; failures omit it. Acceptance is not handset delivery. Platform/key prerequisites and HTTP outcomes are defined in the -[contract](../docs/CONTRACT.md#evaluation-generic-shutter). +[contract](../docs/CONTRACT.md#evaluation-no-provider-send). For Soprano voice, the adapter extracts the first six-digit passcode from the rendered message. It -uses a nonblank SAS request locale as the language, falling back to `en-US`, and sends fixed gender +uses the request's nonblank locale as the language, falling back to `en-US`, and sends fixed gender `1` and loop `2`. These values require no additional environment settings. Soprano SMS continues to forward the rendered message unchanged. @@ -107,7 +107,7 @@ six-digit numeric run that is not part of a longer number and repeats the comple ## Source Start with the provider-neutral -[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the SAS payload, +[BYOT request and response guide](../docs/BRING-YOUR-OWN-PROVIDER.md) for the Microsoft Entra ID payload, caller authentication, and response rules. Your team owns the outbound service integration. For deployed diagnostics, use [Application Insights](../docs/APPLICATION-INSIGHTS.md) and diff --git a/setup/docs/README.md b/setup/docs/README.md index c8570f5..2e74f09 100644 --- a/setup/docs/README.md +++ b/setup/docs/README.md @@ -139,6 +139,7 @@ disclosed outbound managed-identity federated credential. directory permissions. - Microsoft Graph **beta** access for the Entra `signInAudienceRestrictions` allowed-tenants preview. The selected provider tenant is allowed in addition to the app's home tenant, which Entra always allows. + See the [allowed-tenants schema](https://learn.microsoft.com/en-us/graph/api/resources/allowedtenantsaudience?view=graph-rest-beta). - The selected **Linux Flex Consumption FC1 or Premium EP1** plan available in the chosen region, with sufficient subscription quota. Availability is checked for that plan only; regional service availability and resource-provider registration do not guarantee quota. If deployment reports @@ -253,8 +254,8 @@ The flow is: name. Existing allowed provider tenants are preserved; provider options are never numbered. Setup downloads the provider JSON, resolves one complete regional route containing both SMS and voice endpoints, authentication, app-ID/scope - when applicable, timeout, and retry interval. Explicit test values are allowed, shown as test - configuration, and passed to Azure settings. It also resolves the latest stable CI package + when applicable, timeout, and retry interval. These values come from the selected profile; + the launcher has no endpoint or credential override parameters. It also resolves the latest stable CI package release and looks up the language asset name in `packages/catalog.json`; there are no `PackageUrl` or `PackageSha256` inputs. Use `-PackageReleaseTag epp-packages--` to pin a previous CI release. Malformed or @@ -354,10 +355,6 @@ region checks, verify package checksums, build the C# package locally when selec Installation, sign-in, and consent prompts are separate from deployment approval. A preflight error is not a successful preview. -The [main guide's terminal captures](../../README.md#3-run-guided-setup) show real input screens, -with identifying details and provider names hidden. They illustrate the prompts, not deployment -approval, resource creation, or the final completion summary. - ### After approving the plan After approval, setup rechecks the selected subscription and registers only missing @@ -399,8 +396,9 @@ subscription/app/prefix can reconfigure the existing endpoint. See The plan creates or updates a dedicated resource group, selected Linux FC1 or EP1 hosting plan, Function App, storage account/private package container, Key Vault, Log Analytics workspace, Application Insights, outbound managed identity, diagnostics, Easy Auth, and scoped role assignments. Storage/package -access uses managed identity, not account keys or SAS. Telemetry uses the system identity; the -outbound identity is selected explicitly, not through a global `AZURE_CLIENT_ID`. +access uses managed identity, not account keys or Azure Storage shared access signatures. +Telemetry uses the system identity; the outbound identity is selected explicitly, not through +a global `AZURE_CLIENT_ID`. The Function starts with public ingress disabled. After Bicep creates the vault and permissions, setup applies the approved certificate action. New certificates are self-signed, exportable