Skip to content

Conversation

@chriscanin
Copy link

@chriscanin chriscanin commented Oct 29, 2025

Added client_trust_state and untrusted_first_factors fields to Client and SignIn resources to support the backend's Setup vs Unlock fraud protection feature.

  • Backend determines if a client is trusted based on session history
  • For known/trusted clients: password appears in supported_first_factors
  • For new/untrusted clients: password is omitted from supported_first_factors and moved to untrusted_first_factors
  • Frontend simply receives and stores these fields - no UI logic changes needed
  • Trusted users automatically see password option
  • Untrusted users automatically don't see password option
  • Added Expo dummy data

Description

This PR adds type definitions and serialization support for two new fields coming from FAPI:

  1. client_trust_state: A string enum ('new' | 'known' | 'pending') that indicates whether the backend trusts this client based on whether the user has had a session on this client before.

  2. untrusted_first_factors: An array of sign-in factors that are not allowed for this client (e.g., password for new/untrusted clients).

How It Works

The backend (clerk_go PR #14403) handles all the filtering logic:

  • Checks if user has had a session on this client via the user_clients table
  • For trusted clients (client_trust_state = 'known'): password goes in supported_first_factors
  • For untrusted clients (client_trust_state = 'new' or 'pending'): password is excluded from supported_first_factors and placed in untrusted_first_factors

The frontend changes are purely additive:

  • Added TypeScript types for the new fields
  • Updated Client and SignIn resource classes to deserialize/serialize the fields
  • Updated test fixtures and dummy data
  • No UI component changes - components already display factors from supported_first_factors, which the backend now filters appropriately

Testing

  1. Build the types package: pnpm --filter @clerk/types build
  2. Build dependent packages: pnpm --filter @clerk/clerk-js build
  3. Run tests: pnpm test
  4. To test with backend: Ensure clerk_go branch tom/setup-v-unlock-initial is deployed or built locally, then verify:
    • New users on new clients don't see password option
    • Returning users on known clients see password option

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

Release Notes

  • New Features
    • Added client trust state tracking to support fraud protection mechanisms (states: new, known, pending).
    • Introduced untrusted first factors tracking during the sign-in process to enhance security monitoring.

… to Client and SignIn resources

  - Trusted users automatically see password
  - Untrusted users automatically don't see password
  - Added Expo dummy data.
@changeset-bot
Copy link

changeset-bot bot commented Oct 29, 2025

🦋 Changeset detected

Latest commit: 72ddfdc

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Oct 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Oct 29, 2025 9:45pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

This change introduces fraud protection support by adding two new fields across the Clerk packages: clientTrustState to the Client resource to track whether a client is new, known, or pending; and untrustedFirstFactors to the SignIn resource to list risky authentication factors. Types, implementations, and fixtures are updated consistently.

Changes

Cohort / File(s) Summary
Type Definitions
packages/types/src/json.ts, packages/types/src/client.ts, packages/types/src/signIn.ts, packages/types/src/snapshots.ts
Added ClientTrustState type ('new' | 'known' | 'pending') and extended ClientJSON, ClientResource, and SignInJSON/SignInResource interfaces with optional client_trust_state and untrustedFirstFactors fields. Updated SignInJSONSnapshot to include untrusted_first_factors in nullable keys.
Resource Implementations
packages/clerk-js/src/core/resources/Client.ts, packages/clerk-js/src/core/resources/SignIn.ts
Added clientTrustState property with fromJSON population, toSnapshot serialization, and destroy reset. Added untrustedFirstFactors property with snake/camel case conversions for JSON deserialization and snapshot serialization.
Test & Fixture Data
packages/clerk-js/src/test/core-fixtures.ts, packages/expo/src/cache/dummy-data/client-resource.ts
Updated createSignIn fixture and DUMMY_CLERK_CLIENT_RESOURCE to include untrusted_first_factors field with empty array defaults.
Changeset Documentation
.changeset/client-trust-state.md
Documented minor version bumps for @clerk/types, @clerk/clerk-js, and @clerk/expo with explanation of fraud protection feature.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Homogeneous changes following consistent patterns across files (field additions with matching serialization logic)
  • Straightforward type additions with no complex business logic
  • Data structure extensions are well-defined and repetitive
  • Areas for attention: Verify consistency of snake/camel case conversions across Client.ts and SignIn.ts implementations; confirm test fixtures and dummy data align with new field contracts

Poem

🐰 A trust state emerges, three flavors so fine,
New, known, or pending—fraud's guard in line!
Untrusted factors flagged for all to see,
Hop safely through auth, as protected can be! 🛡️✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title accurately captures the main changes in the changeset. It clearly identifies the two primary additions: clientTrustState (added to the Client resource) and untrustedFirstFactors (added to the SignIn resource) across the affected packages. The title uses conventional commit format with an appropriate scope covering the main packages (clerk-js, types) and provides specific, descriptive terms rather than vague language. A teammate scanning the commit history would immediately understand that this PR introduces new fields to support a fraud-protection feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chris/supported_factors

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Member

@tmilewski tmilewski left a comment

Choose a reason for hiding this comment

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

🚀 Just a small change for clientTrustState/client_trust_state.

@chriscanin chriscanin marked this pull request as ready for review October 29, 2025 21:45
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (10)
packages/clerk-js/src/test/core-fixtures.ts (1)

211-211: Good defaulting to an empty array

Defaulting untrusted_first_factors to [] avoids undefined in snapshots/tests. Consider doing the same for supported_first_factors for consistency in fixtures to reduce snapshot churn.

Example change outside this hunk:

-    supported_first_factors: signInParams.supported_first_factors,
+    supported_first_factors: signInParams.supported_first_factors || [],
packages/types/src/client.ts (1)

23-23: Optional clientTrustState aligns with omission semantics

Making clientTrustState optional matches “omit when unknown”. Please add a short JSDoc since this is a public API.

Suggested doc:

   lastAuthenticationStrategy: LastAuthenticationStrategy | null;
-  clientTrustState?: ClientTrustState;
+  /**
+   * Trust classification for this client; omitted when unknown.
+   * Values: 'new' | 'known' | 'pending'.
+   */
+  clientTrustState?: ClientTrustState;
.changeset/client-trust-state.md (1)

1-7: Changeset looks right for a minor bump

Message is clear. Consider noting omission semantics explicitly: “client_trust_state omitted when unknown; untrusted_first_factors present but often empty.”

packages/clerk-js/src/core/resources/SignIn.ts (3)

106-107: Public field: add JSDoc and clarify null vs [] semantics

Add a brief JSDoc describing when untrustedFirstFactors is null vs [] for consumers.

Example:

-  untrustedFirstFactors: SignInFirstFactor[] | null = [];
+  /**
+   * First factors the backend marked untrusted for this client.
+   * null: not provided; []: provided but empty.
+   */
+  untrustedFirstFactors: SignInFirstFactor[] | null = [];

531-532: Avoid undefined in fromJSON

If the backend omits the field, deepSnakeToCamel(undefined) yields undefined and overrides your default. Coalesce to null to maintain a stable tri-state (array | null) and avoid undefined at runtime.

-      this.untrustedFirstFactors = deepSnakeToCamel(data.untrusted_first_factors) as SignInFirstFactor[] | null;
+      this.untrustedFirstFactors =
+        (deepSnakeToCamel(data.untrusted_first_factors) as SignInFirstFactor[] | null) ?? null;

550-551: Snapshot should not emit undefined

Coalesce to null so snapshots never contain undefined.

-      untrusted_first_factors: deepCamelToSnake(this.untrustedFirstFactors),
+      untrusted_first_factors: deepCamelToSnake(this.untrustedFirstFactors ?? null),
packages/types/src/signIn.ts (1)

44-45: Add JSDoc for new public API

Please document untrustedFirstFactors usage for SDK consumers (e.g., “factors disallowed for this client by trust signals”).

-  untrustedFirstFactors: SignInFirstFactor[] | null;
+  /**
+   * First factors that are not allowed for this client due to trust evaluation.
+   */
+  untrustedFirstFactors: SignInFirstFactor[] | null;
packages/types/src/json.ts (1)

105-106: Type addition looks good; add a brief doc

Add a one-line comment to clarify intended states and usage.

-export type ClientTrustState = 'new' | 'known' | 'pending';
+// Trust classification for a client, used by setup vs unlock flows.
+export type ClientTrustState = 'new' | 'known' | 'pending';
packages/expo/src/cache/dummy-data/client-resource.ts (1)

95-96: Dummy data parity

Adding untrusted_first_factors: [] is good. Consider also including client_trust_state: null at the top-level to mirror the new type (optional, but helps parity in snapshots/demos).

Example:

   last_authentication_strategy: null,
+  client_trust_state: null,
   created_at: new Date().getTime(),
packages/clerk-js/src/core/resources/Client.ts (1)

30-30: Consider adding JSDoc and simplifying the type declaration.

The property declaration has minor refinement opportunities:

  1. Missing JSDoc: The similar property lastAuthenticationStrategy (line 28) includes JSDoc documentation. For consistency and API clarity, consider documenting this public property.

  2. Type redundancy: The optional marker ? already makes this ClientTrustState | undefined, so the explicit | undefined and initialization to undefined are redundant.

Consider this refinement:

+  /** Client trust state for fraud protection; undefined when not yet determined by the backend. */
-  clientTrustState?: ClientTrustState | undefined = undefined;
+  clientTrustState?: ClientTrustState;

As per coding guidelines

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ebc0f74 and 72ddfdc.

📒 Files selected for processing (9)
  • .changeset/client-trust-state.md (1 hunks)
  • packages/clerk-js/src/core/resources/Client.ts (5 hunks)
  • packages/clerk-js/src/core/resources/SignIn.ts (3 hunks)
  • packages/clerk-js/src/test/core-fixtures.ts (1 hunks)
  • packages/expo/src/cache/dummy-data/client-resource.ts (1 hunks)
  • packages/types/src/client.ts (2 hunks)
  • packages/types/src/json.ts (2 hunks)
  • packages/types/src/signIn.ts (2 hunks)
  • packages/types/src/snapshots.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

**/*.{js,jsx,ts,tsx}: All code must pass ESLint checks with the project's configuration
Follow established naming conventions (PascalCase for components, camelCase for variables)
Maintain comprehensive JSDoc comments for public APIs
Use dynamic imports for optional features
All public APIs must be documented with JSDoc
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Profile and optimize critical paths
Validate all inputs and sanitize outputs
Implement proper logging with different levels

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
**/*.{js,jsx,ts,tsx,json,css,scss,md,yaml,yml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
packages/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
packages/**/*.{ts,tsx,d.ts}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Packages should export TypeScript types alongside runtime code

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Prefer readonly for properties that shouldn't change after construction
Prefer composition and interfaces over deep inheritance chains
Use mixins for shared behavior across unrelated classes
Implement dependency injection for loose coupling
Let TypeScript infer when types are obvious
Use const assertions for literal types: as const
Use satisfies operator for type checking without widening
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Use type-only imports: import type { ... } from ...
No any types without justification
Proper error handling with typed errors
Consistent use of readonly for immutable data
Proper generic constraints
No unused type parameters
Proper use of utility types instead of manual type construction
Type-only imports where possible
Proper tree-shaking friendly exports
No circular dependencies
Efficient type computations (avoid deep recursion)

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Support multiple Clerk environment variables (CLERK_, NEXT_PUBLIC_CLERK_, etc.) for configuration.

Files:

  • packages/expo/src/cache/dummy-data/client-resource.ts
  • packages/types/src/json.ts
  • packages/types/src/signIn.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
  • packages/clerk-js/src/test/core-fixtures.ts
  • packages/types/src/snapshots.ts
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/types/src/client.ts
.changeset/**

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Automated releases must use Changesets.

Files:

  • .changeset/client-trust-state.md
🧬 Code graph analysis (5)
packages/types/src/signIn.ts (2)
packages/types/src/signInCommon.ts (1)
  • SignInFirstFactor (74-85)
packages/types/src/json.ts (1)
  • SignInFirstFactorJSON (553-553)
packages/clerk-js/src/core/resources/SignIn.ts (2)
packages/types/src/signInCommon.ts (1)
  • SignInFirstFactor (74-85)
packages/shared/src/underscore.ts (2)
  • deepSnakeToCamel (111-111)
  • deepCamelToSnake (102-102)
packages/types/src/snapshots.ts (2)
packages/types/src/utils.ts (1)
  • Nullable (40-42)
packages/types/src/signIn.ts (1)
  • SignInJSON (94-110)
packages/clerk-js/src/core/resources/Client.ts (1)
packages/types/src/json.ts (1)
  • ClientTrustState (105-105)
packages/types/src/client.ts (1)
packages/types/src/json.ts (1)
  • ClientTrustState (105-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (7)
packages/types/src/signIn.ts (1)

105-106: Confirm server contract: optional vs required

untrusted_first_factors is modeled as required here, but the backend may omit it. If omission is possible, mark it optional (?:) to match wire shape, or ensure backend always sends []. Please verify with the backend PR.

If it can be omitted, change:

-  untrusted_first_factors: SignInFirstFactorJSON[];
+  untrusted_first_factors?: SignInFirstFactorJSON[];
packages/types/src/json.ts (1)

116-117: Optional client_trust_state is appropriate

Optionality reflects omission when unknown—LGTM.

packages/types/src/snapshots.ts (1)

38-41: Snapshot type updated correctly

Including untrusted_first_factors in the nullable set keeps snapshot shapes stable—LGTM.

packages/clerk-js/src/core/resources/Client.ts (4)

6-6: LGTM!

The import is correctly added alongside related types from @clerk/types.


91-91: LGTM!

The reset is consistent with the property's default value and follows the pattern of other properties in the destroy method.


141-141: LGTM!

The assignment correctly deserializes the field from JSON, with TypeScript ensuring type safety.


160-160: No changes needed—the conditional serialization is correct.

The conditional spread for clientTrustState is intentional and follows established patterns. The key difference from lastAuthenticationStrategy is that clientTrustState is declared as an optional property (clientTrustState?: ClientTrustState | undefined), whereas lastAuthenticationStrategy is a required property that defaults to null. The conditional spread correctly omits the field when clientTrustState is undefined, matching how optional fields should be serialized. The deserialization at line 141 mirrors this by directly assigning data.client_trust_state, preserving the field's optional semantics.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants