Skip to content

feat(auth): add AmplifyCognitoASF, an experimental public API for user context data - #4403

Open
thisisabhash wants to merge 2 commits into
mainfrom
feat/cognito-user-context-data
Open

thisisabhash wants to merge 2 commits into
mainfrom
feat/cognito-user-context-data

Conversation

@thisisabhash

@thisisabhash thisisabhash commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Issue #

None. The gap comes from the AWS SDK for iOS reaching end of support on 2026-08-01.

Description

Problem. Apps that send Cognito requests themselves, through the AWS SDK for Swift or their own backend, used the AWS SDK for iOS's AWSCognitoIdentityProviderASF.userContextData(...) to fill UserContextData.EncodedData. Cognito threat protection, which was called advanced security features (ASF), uses that data to assess the request. Amplify Swift builds the same fields for its own requests, but only in package code in InternalAWSCognitoAuth, so those apps had nothing to migrate to.

Change. CognitoUserContextData, public under @_spi(AmplifyExperimental) like AmplifyCognitoClient, is made once for a user pool and app client, and has two ways in:

// Before: AWS SDK for iOS, before every request
import AWSCognitoIdentityProviderASF
let encodedData = AWSCognitoIdentityProviderASF.userContextData(poolId, username: username, deviceId: deviceId, userPoolClientId: clientId)

// After, the main path: once, on the app's own CognitoIdentityProviderClient (.product(name: "AmplifyCognitoClient", ...))
import AWSCognitoIdentityProvider
@_spi(AmplifyExperimental) import AmplifyCognitoClient
let contextData = CognitoUserContextData(userPoolId: poolId, userPoolClientId: clientId)
config.addInterceptorProvider(contextData.interceptorProvider())
let cognito = CognitoIdentityProviderClient(config: config)

// After, the fallback: per request, for a backend's Admin* call, a hosted UI URL the app builds or its own HTTP stack
// (.product(name: "AmplifyCognitoASF", ...), which builds neither Amplify nor the AWS SDK)
@_spi(AmplifyExperimental) import AmplifyCognitoASF
let encodedData = await contextData.encodedData(username: username, deviceId: deviceId)

The plain function behaves like the legacy API. A nil username sends "unknown", and a nil deviceId sends the identifier for vendor, or no DeviceId where there is none, as on macOS. It differs in two ways:

  • It returns nil when the user pool ID or the app client ID is empty, before reading the device, and when the app client ID isn't ASCII. The legacy API encodes empty IDs, and passes a NULL key to strlen for a non-ASCII client ID.
  • It reads the device on the main actor, so it is async. The legacy API is synchronous.

The result is timestamped and signed, so build it for each request. Two of the values it sends also differ from the legacy API's; see the quirks under What stays the same.

The interceptor, interceptorProvider(deviceId:), is an HttpInterceptorProvider for the AWS SDK for Swift's CognitoIdentityProviderClient. In modifyBeforeSerialization it fills userContextData with the plain function's data:

  • Which calls: the seven inputs that have the field (InitiateAuth, RespondToAuthChallenge, SignUp, ConfirmSignUp, ForgotPassword, ConfirmForgotPassword, ResendConfirmationCode), when the request's ClientId is the configured app client's. A request for another app client, or with no ClientId, and every other operation go out unchanged.
  • Username: the request's own: USERNAME in AuthParameters or ChallengeResponses, else Username. A refresh-token sign-in names none, so it sends "unknown".
  • Device ID: the app's optional deviceId closure, given the request's username, else the identifier for vendor, on all seven calls.
  • The caller's value wins: a non-empty encodedData is sent as it is; otherwise an ipAddress is kept.
  • It never throws: data that can't be built leaves the request unchanged. The data is made once per call, so a retry resends it.
  • It lives in AmplifyCognitoClient, which already depends on the Cognito SDK and the encoder, and which now re-exports AmplifyCognitoASF with its SPI, so one import names both.

One implementation for the API and for Amplify's own requests. The encoder and the device and app readers move out of the engine into the new target, AmplifyClients/Internal/AmplifyCognitoASF/, and the engine depends on it:

  • ASFAppInfo, ASFDeviceInfo and EngineDeviceInfo move verbatim, except for one doc paragraph. So do the ASFDeviceBehavior and ASFAppInfoBehavior protocols.
  • CognitoUserPoolASF's keys and its context-data, payload, HMAC, envelope and time-zone methods move to UserContextDataEncoder as static members, together with ASFError. timeZoneOffet is renamed timeZoneOffset.
  • A new UserContextDataEncoder.encodedData(username:contextData:userPoolId:clientId:) chains the payload, signature and envelope for both callers.
  • CognitoUserPoolASF keeps userContextData(for:deviceInfo:appInfo:configuration:), unchanged in signature, and becomes an adapter over the encoder.
  • In the module, only CognitoUserContextData's init(userPoolId:userPoolClientId:) and encodedData(username:deviceId:) are public. The rest is package.

What stays the same.

  • Amplify's own requests send exactly what they sent before. The two pinning tests in UserContextDataFormatTests (keys, values, envelope and signature) are in a file this PR adds. They ran against main before anything moved, and they pass unchanged.
  • Two quirks in that output are left for a separate fix(auth), so that this PR changes nothing Amplify sends. The new API has them too:
    • PhoneType and DeviceFingerprint carry the whole 256-byte utsname.machine buffer, trailing NULs included. Fixing this changes what Cognito sees from every device that uses Amplify today. The AWS SDK for iOS sends the machine name without the NULs, so an app that moves from it to this API sends Cognito different values in these two fields.
    • The time-zone offset has the wrong sign for offsets strictly between −1 h and 0. No current time zone has such an offset, so no device sends a wrong value today.
  • No source call site changes. Engine files that use the moved types only gain import AmplifyCognitoASF.
  • Tests that use the moved types also just gain the import, with three exceptions (see Testing):
    • One client test reads the DeviceId key from UserContextDataEncoder instead of CognitoUserPoolASF.
    • The plugin's tests of the moved encoder methods move to AmplifyCognitoASFTests.
    • EngineDeviceInfoTests gains checks of the real reader's language and fingerprint.

Packaging.

  • AmplifyCognitoASF depends only on the in-package AmplifyAvailability C module, and the engine's dependency on AmplifyAvailability moves to it. An app that uses only this product compiles neither Amplify nor the AWS SDK.
  • SwiftPM still resolves all of amplify-swift's package dependencies, as it does for every product. So the exact pins on aws-sdk-swift (1.7.60), smithy-swift (0.242.0) and SQLite.swift (0.15.4) still apply to the app.
  • It ships a PrivacyInfo.xcprivacy with the Auth plugin's collected-data entry (other data types, not linked, no tracking, analytics) and no required-reason APIs.
  • The engine now depends on the module, so every product that links the engine also ships this resource bundle: AWSCognitoAuthPlugin, AmplifyCognitoClient, AWSPinpointAnalyticsPlugin and AWSPinpointPushNotificationsPlugin. Its entry repeats the one in the Auth plugin's and the client's manifests.
  • AmplifyCognitoClient now declares AmplifyCognitoASF (re-exported) and smithy-swift's ClientRuntime (the interceptor protocols) as direct dependencies. Both were already in its closure, so it compiles nothing new.

CI and repository wiring.

  • AmplifyCognitoASFTests runs in the AWSCognitoAuthPlugin scheme, which also lists AmplifyCognitoASF as a coverage target. Amplify-Package builds both and runs AmplifyCognitoASFTests in its test action, as it does AmplifyBigIntegerTests.
  • AWSCognitoAuthPluginUnitTests and AmplifyCognitoClientTests now depend on AmplifyCognitoASF, because their test doubles conform to its protocols.
  • In unit_test_groups.json and integ_test_groups.json, the new folder joins the paths of the Auth plugin and Cognito client groups, as AmplifyBigInteger does.
  • .swiftlint.yml lints the new Sources, so the moved code keeps its lint coverage.
  • scripts/cognito-engine/engine_paths.txt is regenerated, which drops the three moved files. rename_table.json now maps Amplify.DeviceInfo to AmplifyCognitoASF.EngineDeviceInfo.
  • AmplifyClients/AGENTS.md describes the module and where its interceptor and tests live, and lists it among the engine's dependencies in place of AmplifyAvailability. AGENTS.md adds it to the modules SwiftLint lints.
  • AmplifyCognitoClient's README gains a "User context data for your own requests" section. The interceptor's tests join AmplifyCognitoClientTests, so they need no new scheme or test group.

What to review closely.

  • Admin-owned files (these need an admin approval):
    • scripts/cognito-engine/rename_table.json: its Amplify.DeviceInfo entry now points to AmplifyCognitoASF.
    • The AWSCognitoAuthPlugin scheme: it gains only the AmplifyCognitoASFTests testable and the AmplifyCognitoASF coverage target, and its launch environment doesn't change.
    • engine_paths.txt and the Amplify-Package scheme are team-owned.
  • Gate coverage. No engine gate checks the moved code's sources any more for an Amplify, AWSPluginsCore or InternalAmplifyCredentials import, a required-reason API, or an error match:
    • check_engine_deps.py still walks the new target as part of the engine's dependency closure, so a declared dependency on a forbidden module there still fails it. It also greps the target's sources for an AmplifyCognitoClient import, as it does every non-test target's. But it greps only the engine's and the client's sources for the other forbidden imports.
    • check_engine_privacy_manifest.py scans only the engine's sources for required-reason APIs.
    • error_match_gate.sh scans only the plugin's and the engine's sources. The module can't name the error types it looks for anyway, since it depends only on AmplifyAvailability.
    • Today the moved code has no forbidden imports and no required-reason APIs; I checked it by hand with the gates' own patterns. An import Amplify there would still fail the client scheme's build.
    • Decision for the script owners: accept this gap and point the import grep and the required-reason scan at AmplifyClients/Internal/AmplifyCognitoASF/Sources in a follow-up, or ask for that change in this PR.
  • Location: the product lives under AmplifyClients/Internal/, next to the other modules the engine shares, and is the only product there. Keep it there, or move it to AmplifyClients/AmplifyCognitoASF/, beside the client products, before it ships?
  • Naming: please confirm the names before customers adopt them.
    • The product and module are AmplifyCognitoASF, after the AWS SDK for iOS's AWSCognitoIdentityProviderASF.
    • The type is CognitoUserContextData.
    • The method, encodedData, is named for the UserContextDataType field it fills.
    • The interceptor method is interceptorProvider(deviceId:), returning some HttpInterceptorProvider & Sendable.
  • Interceptor choices: requests for another app client are left alone rather than signed with the configured ID; a refresh-token sign-in sends "unknown", where the legacy user-pool layer named the signed-in user; the request's DEVICE_KEY isn't used, since only two of the seven calls carry it.
  • The re-export: @_spi(AmplifyExperimental) import AmplifyCognitoClient now also names CognitoUserContextData; a plain import AmplifyCognitoClient still doesn't.

Testing.

  • AmplifyCognitoASFTests (13 tests on macOS, 12 elsewhere):
    • the envelope, payload and signature format;
    • the username and device ID rules;
    • nil for empty IDs (before the device is read) and for a non-ASCII client ID;
    • on macOS only, the public method over the real device. Swapping the two IDs in init makes four tests fail.
  • AWSCognitoAuthPluginUnitTests:
    • The new UserContextDataFormatTests holds the pin above, and checks that the public API's context data equals the engine's for the same device.
    • EngineDeviceInfoTests also checks the real reader's language and fingerprint format.
    • The encoder tests move to UserContextDataEncoderTests in AmplifyCognitoASFTests. ASFCognitoTests.swift (three time-zone tests) is deleted, and CognitoUserPoolASFTests loses its two calculateSecretHash tests. They become three tests:
      • one time-zone test, which adds +05:30, −03:30 and +12:45;
      • a signature test that checks the exact HMAC rather than a non-empty string;
      • the non-ASCII hashKey test.
  • AmplifyCognitoClientTests, UserContextDataInterceptorTests (22 tests on macOS, 20 elsewhere): most drive a real CognitoIdentityProviderClient whose HTTP engine keeps every request, and decode the bodies:
    • each of the seven operations gets data signed with the app client ID, naming the pool and the request's user, with the plain function's context data;
    • the caller's encoded data is sent unchanged, and an IP address alone is kept;
    • other operations, other app clients, empty IDs and data that can't be encoded leave the request unchanged, without reading the device or asking for a device ID;
    • refresh-token sign-in sends "unknown"; the device ID closure answers on all seven calls, else the vendor ID is sent;
    • a retry resends the data made once for the call; 24 concurrent calls each get their own user;
    • the public method keeps the IDs and the closure, and, on macOS, describes the real device.
    • Mutation testing on a copy of the repo killed 22 of 27 mutants. Of the 5 survivors, 2 led to new checks (the caller's data never asks for a device ID; the public method keeps the closure), 2 are equivalent, and the last, a missing @_spi on the type, can't be caught by XCTest; a plain-import probe confirms the SPI holds.
  • UserContextDataInterceptorImportTests compiles only while the client re-exports the module.
  • Full unit suites, on main 5a9f3f0: the plugin ran 1,219 tests (0 failures, 2 skipped), the client 1,488 (0 failures) and the new module 13 (0 failures).
  • Platforms, before the rebase on main: xcodebuild builds the AWSCognitoAuthPlugin scheme for iOS, watchOS, tvOS and visionOS, and the AmplifyCognitoClient scheme, tests included, for all four. On an iOS simulator, the interceptor, ASF and format tests pass.
  • Outside the repository: a package that depends only on the AmplifyCognitoASF product builds just AmplifyAvailability and AmplifyCognitoASF. With a plain import, CognitoUserContextData is not in scope, and the package encoder is never visible.
  • Gates and lint: the engine gates as CI runs them (check_engine_deps.py --stage final, check_engine_privacy_manifest.py, error_match_gate.sh check S9 and gen_engine_paths.py --check), SwiftFormat 0.60.1 with --swiftversion 6.0 and SwiftLint 0.54.0 are all clean, with no new warnings.
  • Integration tests: not run locally. CI's integration workflows select every suite for this PR, because a change to the Amplify-Package scheme makes scripts/python/affected_targets.py select all groups. No CI user pool has threat protection, so none of them exercises Cognito's use of the data.

General Checklist

  • Added new tests to cover change, if needed
  • Build succeeds with all target using Swift Package Manager
  • All unit tests pass: the Auth plugin, Cognito client and AmplifyCognitoASF suites. The other changed module, the engine, is tested in the Auth plugin's suite
  • All integration tests pass. Not run locally; CI runs every suite on this PR (see Testing)
  • Security oriented best practices and standards are followed (e.g. using input sanitization, principle of least privilege, etc)
  • Documentation update for the change if required
  • PR title conforms to conventional commit style
  • New or updated tests include Given When Then inline code documentation and are named accordingly testThing_condition_expectation(). The new tests use sentence-style names instead, as ASFDeviceSeamTests and EngineDeviceInfoTests do
  • If breaking change, documentation/changelog update with migration instructions Not a breaking change: the API is additive and experimental

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@thisisabhash
thisisabhash requested review from a team as code owners October 27, 2026 22:16
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:00 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:20 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:21 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:24 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:25 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:30 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:31 — with GitHub Actions Active
@thisisabhash
thisisabhash deployed to IntegrationTest October 27, 2026 23:33 — with GitHub Actions Active
@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.35036% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.09%. Comparing base (5a9f3f0) to head (c08dc85).

Files with missing lines Patch % Lines
...s/CognitoUserContextData+InterceptorProvider.swift 92.68% 3 Missing ⚠️
...ifyCognitoASF/Sources/UserContextDataEncoder.swift 97.18% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4403      +/-   ##
==========================================
+ Coverage   76.04%   76.09%   +0.05%     
==========================================
  Files        1362     1365       +3     
  Lines       57864    57933      +69     
==========================================
+ Hits        44003    44086      +83     
+ Misses      13861    13847      -14     
Flag Coverage Δ
API_plugin_unit_test 70.52% <ø> (-0.12%) ⬇️
AWSPluginsCore 77.28% <ø> (ø)
Amplify 48.69% <ø> (ø)
Amplify_Foundation_Bridge_unit_test 62.28% <ø> (ø)
Amplify_Foundation_unit_test 81.35% <ø> (ø)
Amplify_Keychain_unit_test 83.93% <ø> (ø)
Analytics_plugin_unit_test 83.43% <ø> (ø)
Auth_plugin_unit_test 84.16% <97.91%> (+0.16%) ⬆️
CloudWatchClient_unit_test 83.50% <ø> (ø)
CognitoClient_unit_test 95.56% <92.68%> (-0.02%) ⬇️
Connect_unit_test 42.95% <ø> (ø)
DataStore_plugin_unit_test 81.90% <ø> (-0.02%) ⬇️
EventEnrichment_unit_test 90.36% <ø> (ø)
Firehose_plugin_unit_test 53.60% <ø> (ø)
Geo_plugin_unit_test 73.39% <ø> (ø)
Kinesis_plugin_unit_test 52.60% <ø> (ø)
Logging_plugin_unit_test 66.18% <ø> (ø)
Predictions_plugin_unit_test 34.98% <ø> (ø)
PushNotifications_plugin_unit_test 84.58% <ø> (ø)
RecordCache_unit_test 79.94% <ø> (ø)
Storage_plugin_unit_test 79.42% <ø> (-0.03%) ⬇️
unit_tests 76.09% <96.35%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

cadivus
cadivus previously approved these changes Oct 28, 2026
…r context data

Replaces AWSCognitoIdentityProviderASF from the AWS SDK for iOS for apps that send
Cognito requests themselves: CognitoUserContextData.encodedData(userPoolId:
userPoolClientId:username:deviceId:) in the new AmplifyCognitoASF product, public
under @_spi(AmplifyExperimental). A nil username sends "unknown"; a nil device ID
sends the identifier for vendor, or none where there is none.

- The engine's encoder and device and app readers move into the new target under
  AmplifyClients/Internal/, so the API and Amplify's own requests share one
  implementation. CognitoUserPoolASF becomes an adapter over the encoder.
- Amplify's own requests are unchanged: UserContextDataFormatTests pinned the
  engine's output before the move and passes unchanged after it.
- The product depends only on AmplifyAvailability and ships a privacy manifest with
  the Auth plugin's collected-data entry.
- AmplifyCognitoASFTests run in the AWSCognitoAuthPlugin scheme, which now also
  reports the module's coverage; test-group paths, SwiftLint scope,
  engine_paths.txt and rename_table.json follow the move.
…context data

Makes a one-time setup the main path, as asked in review. CognitoUserContextData
is now a Sendable struct made once with the user pool and app client IDs, and
encodedData(username:deviceId:) replaces the static
encodedData(userPoolId:userPoolClientId:username:deviceId:) the previous commit
added.

- interceptorProvider(deviceId:), in AmplifyCognitoClient, returns an
  HttpInterceptorProvider for the AWS SDK for Swift's CognitoIdentityProviderClient.
  It fills userContextData on that app client's InitiateAuth,
  RespondToAuthChallenge, SignUp, ConfirmSignUp, ForgotPassword,
  ConfirmForgotPassword and ResendConfirmationCode requests, for the user each
  names ("unknown" when none, as on a refresh-token sign-in). A non-empty value
  the caller set wins, and an ipAddress is kept. The device ID is the app's
  closure's, else the identifier for vendor. The data is made once per call, and
  the hook never throws.
- AmplifyCognitoClient re-exports AmplifyCognitoASF with its SPI, so one
  @_spi(AmplifyExperimental) import names both, and declares AmplifyCognitoASF and
  ClientRuntime as direct dependencies.
- UserContextDataInterceptorTests drive a real CognitoIdentityProviderClient over
  a capturing HTTP engine; UserContextDataInterceptorImportTests checks the
  re-export. The README and AGENTS.md describe the interceptor.

@harsh62 harsh62 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for turning this around. The interceptor and the IDs-once plain function are exactly what I was after. Looks good.

Non-blocking:

  1. An empty-string deviceId is sent as an empty DeviceId rather than falling back to the vendor ID (deviceId ?? ... in CognitoUserContextData.swift). That applies to both the plain function and the interceptor's closure. Treating "" as nil would be safer.
  2. The doc example in CognitoUserContextData+InterceptorProvider.swift uses try await CognitoIdentityProviderClientConfig(region:), but that initializer isn't async. The README correctly has plain try.
  3. Still open from my notes: how was "nil deviceId sends the identifier for vendor" confirmed against the AWS SDK for iOS, given AWSCognitoIdentityProviderASF shipped only as a binary? A one-line answer here is fine.

This branch was successfully deployed

2 active deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants