diff --git a/js/packages/core/src/__tests__/smoke.test.ts b/js/packages/core/src/__tests__/smoke.test.ts index b8614baa..6c3a4ea5 100644 --- a/js/packages/core/src/__tests__/smoke.test.ts +++ b/js/packages/core/src/__tests__/smoke.test.ts @@ -105,6 +105,35 @@ describe("IDKitRequest API", () => { ).toThrow("rp_context is required"); }); + it("should default require_user_presence to false in request config", () => { + const builder = IDKit.request({ + app_id: "app_staging_test", + action: "test-action", + rp_context: TEST_SESSION_CONFIG.rp_context, + allow_legacy_proofs: false, + }); + + expect((builder as any).config.require_user_presence).toBe(false); + }); + + it("should preserve require_user_presence in request and session configs", () => { + const requestBuilder = IDKit.request({ + app_id: "app_staging_test", + action: "test-action", + rp_context: TEST_SESSION_CONFIG.rp_context, + allow_legacy_proofs: false, + require_user_presence: true, + }); + + const sessionBuilder = IDKit.createSession({ + ...TEST_SESSION_CONFIG, + require_user_presence: true, + }); + + expect((requestBuilder as any).config.require_user_presence).toBe(true); + expect((sessionBuilder as any).config.require_user_presence).toBe(true); + }); + it("should reject malformed session_id values in proveSession", () => { expect(() => IDKit.proveSession( @@ -173,6 +202,7 @@ describe("IDKitRequest API", () => { null, null, true, + false, null, null, "production", diff --git a/js/packages/core/src/request.ts b/js/packages/core/src/request.ts index 303eacc5..aeb878a8 100644 --- a/js/packages/core/src/request.ts +++ b/js/packages/core/src/request.ts @@ -372,6 +372,7 @@ function createWasmBuilderFromConfig( config.action_description ?? null, config.bridge_url ?? null, config.allow_legacy_proofs ?? false, + config.require_user_presence ?? false, config.override_connect_base_url ?? null, config.return_to ?? null, config.environment ?? null, @@ -385,6 +386,7 @@ function createWasmBuilderFromConfig( rpContext, config.action_description ?? null, config.bridge_url ?? null, + config.require_user_presence ?? false, config.override_connect_base_url ?? null, config.return_to ?? null, config.environment ?? null, @@ -397,6 +399,7 @@ function createWasmBuilderFromConfig( rpContext, config.action_description ?? null, config.bridge_url ?? null, + config.require_user_presence ?? false, config.override_connect_base_url ?? null, config.return_to ?? null, config.environment ?? null, @@ -618,6 +621,7 @@ function createRequest(config: IDKitRequestConfig): IDKitBuilder { bridge_url: config.bridge_url, return_to: config.return_to, allow_legacy_proofs: config.allow_legacy_proofs, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, environment: config.environment, }); @@ -667,6 +671,7 @@ function createSession(config: IDKitSessionConfig): IDKitBuilder { action_description: config.action_description, bridge_url: config.bridge_url, return_to: config.return_to, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, environment: config.environment, }); @@ -728,6 +733,7 @@ function proveSession( action_description: config.action_description, bridge_url: config.bridge_url, return_to: config.return_to, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, environment: config.environment, }); diff --git a/js/packages/core/src/transports/native.test.ts b/js/packages/core/src/transports/native.test.ts index a390cc24..7c88a9e4 100644 --- a/js/packages/core/src/transports/native.test.ts +++ b/js/packages/core/src/transports/native.test.ts @@ -129,8 +129,40 @@ describe("native transport request lifecycle", () => { } }); + it("fails when user presence is required but not completed", async () => { + const req = createNativeRequest( + { payload: 1 }, + { ...baseConfig, require_user_presence: true }, + {}, + "", + ); + activeRequest = req; + + const completionPromise = req.pollUntilCompletion({ timeout: 1000 }); + + miniKitHandlers["miniapp-verify-action"]?.({ + status: "success", + protocol_version: "3.0", + verification_level: "orb", + signal_hash: "0xabc", + proof: "0x01", + merkle_root: "0x02", + nullifier_hash: "0x03", + }); + + await expect(completionPromise).resolves.toEqual({ + success: false, + error: IDKitErrorCodes.UserPresenceFailed, + }); + }); + it("preserves completed user presence on success", async () => { - const req = createNativeRequest({ payload: 1 }, baseConfig, {}, ""); + const req = createNativeRequest( + { payload: 1 }, + { ...baseConfig, require_user_presence: true }, + {}, + "", + ); activeRequest = req; const completionPromise = req.pollUntilCompletion({ timeout: 1000 }); diff --git a/js/packages/core/src/transports/native.ts b/js/packages/core/src/transports/native.ts index fcee9fb8..b0b031d3 100644 --- a/js/packages/core/src/transports/native.ts +++ b/js/packages/core/src/transports/native.ts @@ -76,6 +76,7 @@ export interface BuilderConfig { bridge_url?: string; return_to?: string; allow_legacy_proofs?: boolean; + require_user_presence?: boolean; override_connect_base_url?: string; environment?: string; } @@ -169,6 +170,14 @@ class NativeIDKitRequest implements IDKitRequest { const userPresenceCompleted = getUserPresenceCompleted(responsePayload); + if (config.require_user_presence === true && !userPresenceCompleted) { + this.complete({ + success: false, + error: IDKitErrorCodes.UserPresenceFailed, + }); + return; + } + this.complete({ success: true, result: nativeResultToIDKitResult( diff --git a/js/packages/core/src/types/config.ts b/js/packages/core/src/types/config.ts index 0fa77403..994ae987 100644 --- a/js/packages/core/src/types/config.ts +++ b/js/packages/core/src/types/config.ts @@ -58,6 +58,9 @@ export type IDKitRequestConfig = { */ allow_legacy_proofs: boolean; + /** Require World App to perform a user-presence check before verification. Defaults to false. */ + require_user_presence?: boolean; + /** Optional override for the connect base URL (e.g., for staging environments) */ override_connect_base_url?: string; @@ -84,6 +87,8 @@ export type IDKitSessionConfig = { bridge_url?: string; /** Optional deep-link callback URL appended as `return_to` on the connector URL. */ return_to?: string; + /** Require World App to perform a user-presence check before verification. Defaults to false. */ + require_user_presence?: boolean; /** Optional override for the connect base URL (e.g., for staging environments) */ override_connect_base_url?: string; diff --git a/js/packages/react/src/__tests__/hooks.test.tsx b/js/packages/react/src/__tests__/hooks.test.tsx index 381cd24e..0403b0b0 100644 --- a/js/packages/react/src/__tests__/hooks.test.tsx +++ b/js/packages/react/src/__tests__/hooks.test.tsx @@ -130,6 +130,7 @@ describe("request/session hooks", () => { bridge_url: undefined, return_to: undefined, allow_legacy_proofs: false, + require_user_presence: false, override_connect_base_url: undefined, environment: undefined, }); @@ -170,6 +171,7 @@ describe("request/session hooks", () => { rp_context: baseRpContext, action_description: undefined, bridge_url: undefined, + require_user_presence: false, override_connect_base_url: undefined, return_to: undefined, environment: undefined, @@ -211,6 +213,7 @@ describe("request/session hooks", () => { rp_context: baseRpContext, action_description: undefined, bridge_url: undefined, + require_user_presence: false, override_connect_base_url: undefined, return_to: undefined, environment: undefined, @@ -255,11 +258,46 @@ describe("request/session hooks", () => { bridge_url: undefined, return_to: "idkit://callback?step=proof", allow_legacy_proofs: false, + require_user_presence: false, override_connect_base_url: undefined, environment: undefined, }); }); + it("request hook forwards require_user_presence to core", async () => { + requestMock.mockReturnValue({ + preset: vi.fn(async () => + makeRequest(async () => ({ + type: "confirmed", + result: { proof: "ok" }, + })), + ), + }); + + const { result } = renderHook(() => + useIDKitRequest({ + app_id: "app_test", + action: "test-action", + rp_context: baseRpContext, + allow_legacy_proofs: false, + require_user_presence: true, + preset: { type: "OrbLegacy" }, + }), + ); + + act(() => { + result.current.open(); + }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(requestMock).toHaveBeenCalledWith( + expect.objectContaining({ require_user_presence: true }), + ); + }); + it("session hook forwards return_to to createSession", async () => { createSessionMock.mockReturnValue({ constraints: vi.fn(async () => ({ @@ -276,6 +314,7 @@ describe("request/session hooks", () => { app_id: "app_test", rp_context: baseRpContext, return_to: "idkit://callback?step=create", + require_user_presence: true, constraints: { all: [] }, }), ); @@ -293,6 +332,7 @@ describe("request/session hooks", () => { rp_context: baseRpContext, action_description: undefined, bridge_url: undefined, + require_user_presence: true, override_connect_base_url: undefined, return_to: "idkit://callback?step=create", environment: undefined, @@ -334,6 +374,7 @@ describe("request/session hooks", () => { rp_context: baseRpContext, action_description: undefined, bridge_url: undefined, + require_user_presence: false, override_connect_base_url: undefined, return_to: "idkit://callback?step=prove", environment: undefined, diff --git a/js/packages/react/src/hooks/useIDKitRequest.ts b/js/packages/react/src/hooks/useIDKitRequest.ts index 22ee05e8..916439ec 100644 --- a/js/packages/react/src/hooks/useIDKitRequest.ts +++ b/js/packages/react/src/hooks/useIDKitRequest.ts @@ -17,6 +17,7 @@ export function useIDKitRequest( bridge_url: config.bridge_url, return_to: config.return_to, allow_legacy_proofs: config.allow_legacy_proofs, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, environment: config.environment, }); diff --git a/js/packages/react/src/hooks/useIDKitSession.ts b/js/packages/react/src/hooks/useIDKitSession.ts index 9e826128..97bd300c 100644 --- a/js/packages/react/src/hooks/useIDKitSession.ts +++ b/js/packages/react/src/hooks/useIDKitSession.ts @@ -36,6 +36,7 @@ export function useIDKitSession( rp_context: config.rp_context, action_description: config.action_description, bridge_url: config.bridge_url, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, return_to: config.return_to, environment: config.environment, @@ -45,6 +46,7 @@ export function useIDKitSession( rp_context: config.rp_context, action_description: config.action_description, bridge_url: config.bridge_url, + require_user_presence: config.require_user_presence ?? false, override_connect_base_url: config.override_connect_base_url, return_to: config.return_to, environment: config.environment, diff --git a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt index 24e68a3b..04a71b77 100644 --- a/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt +++ b/kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt @@ -13,11 +13,10 @@ import uniffi.idkit_core.AppError // import uniffi.idkit_core.CredentialRequest // import uniffi.idkit_core.CredentialType import uniffi.idkit_core.IdKitBuilder -import uniffi.idkit_core.IdKitRequestConfig +import uniffi.idkit_core.IdKitRequestConfig as NativeIDKitRequestConfig import uniffi.idkit_core.IdKitRequestWrapper import uniffi.idkit_core.IdKitResult -// TODO: Re-enable when World ID 4.0 is live -// import uniffi.idkit_core.IdKitSessionConfig +import uniffi.idkit_core.IdKitSessionConfig as NativeIDKitSessionConfig import uniffi.idkit_core.Preset import uniffi.idkit_core.Signal import uniffi.idkit_core.StatusWrapper @@ -31,12 +30,62 @@ import uniffi.idkit_core.idkitResultToJson as nativeIdkitResultToJson // import uniffi.idkit_core.proveSession as nativeProveSession import uniffi.idkit_core.request as nativeRequest -typealias IDKitRequestConfig = IdKitRequestConfig -// TODO: Re-enable when World ID 4.0 is live -// typealias IDKitSessionConfig = IdKitSessionConfig typealias IDKitResult = IdKitResult typealias RpContext = uniffi.idkit_core.RpContext typealias Environment = uniffi.idkit_core.Environment +typealias ConnectUrlMode = uniffi.idkit_core.ConnectUrlMode + +data class IDKitRequestConfig( + val appId: String, + val action: String, + val rpContext: RpContext, + val actionDescription: String? = null, + val bridgeUrl: String? = null, + val allowLegacyProofs: Boolean = false, + val requireUserPresence: Boolean = false, + val overrideConnectBaseUrl: String? = null, + val returnTo: String? = null, + val environment: Environment? = null, + val connectUrlMode: ConnectUrlMode? = null, +) { + internal fun toNative(): NativeIDKitRequestConfig = + NativeIDKitRequestConfig( + appId = appId, + action = action, + rpContext = rpContext, + actionDescription = actionDescription, + bridgeUrl = bridgeUrl, + allowLegacyProofs = allowLegacyProofs, + requireUserPresence = requireUserPresence, + overrideConnectBaseUrl = overrideConnectBaseUrl, + returnTo = returnTo, + environment = environment, + connectUrlMode = connectUrlMode, + ) +} + +data class IDKitSessionConfig( + val appId: String, + val rpContext: RpContext, + val actionDescription: String? = null, + val bridgeUrl: String? = null, + val requireUserPresence: Boolean = false, + val overrideConnectBaseUrl: String? = null, + val returnTo: String? = null, + val environment: Environment? = null, +) { + internal fun toNative(): NativeIDKitSessionConfig = + NativeIDKitSessionConfig( + appId = appId, + rpContext = rpContext, + actionDescription = actionDescription, + bridgeUrl = bridgeUrl, + requireUserPresence = requireUserPresence, + overrideConnectBaseUrl = overrideConnectBaseUrl, + returnTo = returnTo, + environment = environment, + ) +} class IDKitClientError(message: String) : IllegalArgumentException(message) @@ -181,19 +230,19 @@ object IDKit { fun request(config: IDKitRequestConfig): IDKitBuilder { require(config.appId.isNotBlank()) { "app_id is required" } require(config.action.isNotBlank()) { "action is required" } - return IDKitBuilder(nativeRequest(config)) + return IDKitBuilder(nativeRequest(config.toNative())) } // TODO: Re-enable when World ID 4.0 is live // fun createSession(config: IDKitSessionConfig): IDKitBuilder { // require(config.appId.isNotBlank()) { "app_id is required" } - // return IDKitBuilder(nativeCreateSession(config)) + // return IDKitBuilder(nativeCreateSession(config.toNative())) // } // fun proveSession(sessionId: String, config: IDKitSessionConfig): IDKitBuilder { // require(sessionId.isNotBlank()) { "session_id is required" } // require(config.appId.isNotBlank()) { "app_id is required" } - // return IDKitBuilder(nativeProveSession(sessionId, config)) + // return IDKitBuilder(nativeProveSession(sessionId, config.toNative())) // } fun hashSignal(signal: String): String = hashSignalFfi(Signal.fromString(signal)) diff --git a/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt b/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt index 661f6610..8fec8d3c 100644 --- a/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt +++ b/kotlin/bindings/src/test/kotlin/com/worldcoin/idkit/IDKitTests.kt @@ -51,6 +51,7 @@ class IDKitTests { actionDescription = null, bridgeUrl = null, allowLegacyProofs = false, + requireUserPresence = false, overrideConnectBaseUrl = null, returnTo = null, environment = Environment.STAGING, @@ -62,6 +63,7 @@ class IDKitTests { // rpContext = sampleRpContext(), // actionDescription = null, // bridgeUrl = null, + // requireUserPresence = false, // overrideConnectBaseUrl = null, // returnTo = null, // environment = Environment.STAGING, diff --git a/rust/core/src/bridge.rs b/rust/core/src/bridge.rs index 7bb65249..301c468b 100644 --- a/rust/core/src/bridge.rs +++ b/rust/core/src/bridge.rs @@ -877,6 +877,8 @@ pub struct IDKitRequestConfig { /// - `true`: Accept both v3 and v4 proofs. Use during migration. /// - `false`: Only accept v4 proofs. Use after migration cutoff or for new apps. pub allow_legacy_proofs: bool, + /// Optional user-presence requirement. Defaults to false when omitted. + pub require_user_presence: Option, /// Optional override for the connect base URL (e.g., for staging environments) pub override_connect_base_url: Option, /// Optional deep-link callback URL appended as `return_to` on the connector URL @@ -901,6 +903,8 @@ pub struct IDKitSessionConfig { pub action_description: Option, /// Optional bridge URL (defaults to production) pub bridge_url: Option, + /// Optional user-presence requirement. Defaults to false when omitted. + pub require_user_presence: Option, /// Optional override for the connect base URL (e.g., for staging environments) pub override_connect_base_url: Option, /// Optional deep-link callback URL appended as `return_to` on the connector URL @@ -962,7 +966,7 @@ impl IDKitConfig { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: config.allow_legacy_proofs, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -987,7 +991,7 @@ impl IDKitConfig { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -1014,7 +1018,7 @@ impl IDKitConfig { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -1059,7 +1063,7 @@ impl IDKitConfig { legacy_signal: legacy_signal.unwrap_or_default(), bridge_url, allow_legacy_proofs: config.allow_legacy_proofs, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -1083,7 +1087,7 @@ impl IDKitConfig { legacy_signal: legacy_signal.unwrap_or_default(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -1109,7 +1113,7 @@ impl IDKitConfig { legacy_signal: legacy_signal.unwrap_or_default(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: config.require_user_presence.unwrap_or(false), override_connect_base_url: config.override_connect_base_url.clone(), return_to: config.return_to.clone(), environment: config.environment, @@ -1628,6 +1632,39 @@ mod tests { assert!(matches!(response, BridgeResponse::Error { .. })); } + #[cfg(feature = "ffi")] + #[test] + fn test_request_config_defaults_user_presence_requirement_to_false() { + let signature = "0x".to_string() + &"00".repeat(64) + "1b"; + let rp_context = RpContext::new( + "rp_1234567890abcdef", + "0x0000000000000000000000000000000000000000000000000000000000000001", + 1_700_000_000, + 1_700_003_600, + &signature, + ) + .unwrap(); + let config = IDKitConfig::Request(IDKitRequestConfig { + app_id: "app_test".to_string(), + action: "test-action".to_string(), + rp_context: std::sync::Arc::new(rp_context), + action_description: None, + bridge_url: None, + allow_legacy_proofs: false, + require_user_presence: None, + override_connect_base_url: None, + return_to: None, + environment: None, + connect_url_mode: None, + }); + + let params = config + .to_params(ConstraintNode::Any { any: Vec::new() }) + .unwrap(); + + assert!(!params.require_user_presence); + } + #[test] fn test_selfie_check_legacy_preset_serializes_face_verification_level() { let preset = crate::preset::Preset::selfie_check_legacy(Some("face-signal".to_string())); diff --git a/rust/core/src/wasm_bindings.rs b/rust/core/src/wasm_bindings.rs index 73058f67..0b0f53dd 100644 --- a/rust/core/src/wasm_bindings.rs +++ b/rust/core/src/wasm_bindings.rs @@ -478,6 +478,7 @@ enum IDKitConfigWasm { action_description: Option, bridge_url: Option, allow_legacy_proofs: bool, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -487,6 +488,7 @@ enum IDKitConfigWasm { rp_context: RpContext, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -497,6 +499,7 @@ enum IDKitConfigWasm { rp_context: RpContext, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -517,6 +520,7 @@ impl IDKitConfigWasm { action_description, bridge_url, allow_legacy_proofs, + require_user_presence, override_connect_base_url, return_to, environment, @@ -542,7 +546,7 @@ impl IDKitConfigWasm { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: *allow_legacy_proofs, - require_user_presence: false, + require_user_presence: *require_user_presence, override_connect_base_url: override_connect_base_url.clone(), return_to: return_to.clone(), @@ -557,6 +561,7 @@ impl IDKitConfigWasm { rp_context, action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -580,7 +585,7 @@ impl IDKitConfigWasm { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: *require_user_presence, override_connect_base_url: override_connect_base_url.clone(), return_to: return_to.clone(), @@ -596,6 +601,7 @@ impl IDKitConfigWasm { rp_context, action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -621,7 +627,7 @@ impl IDKitConfigWasm { legacy_signal: String::new(), bridge_url, allow_legacy_proofs: false, - require_user_presence: false, + require_user_presence: *require_user_presence, override_connect_base_url: override_connect_base_url.clone(), return_to: return_to.clone(), @@ -671,6 +677,7 @@ impl IDKitBuilderWasm { action_description: Option, bridge_url: Option, allow_legacy_proofs: bool, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -683,6 +690,7 @@ impl IDKitBuilderWasm { action_description, bridge_url, allow_legacy_proofs, + require_user_presence, override_connect_base_url, return_to, environment, @@ -698,6 +706,7 @@ impl IDKitBuilderWasm { rp_context: RpContextWasm, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -708,6 +717,7 @@ impl IDKitBuilderWasm { rp_context: rp_context.into_inner(), action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -724,6 +734,7 @@ impl IDKitBuilderWasm { rp_context: RpContextWasm, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -735,6 +746,7 @@ impl IDKitBuilderWasm { rp_context: rp_context.into_inner(), action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -947,6 +959,7 @@ pub fn request( action_description: Option, bridge_url: Option, allow_legacy_proofs: bool, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -958,6 +971,7 @@ pub fn request( action_description, bridge_url, allow_legacy_proofs, + require_user_presence, override_connect_base_url, return_to, environment, @@ -976,6 +990,7 @@ pub fn create_session( rp_context: RpContextWasm, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -985,6 +1000,7 @@ pub fn create_session( rp_context, action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -1004,6 +1020,7 @@ pub fn prove_session( rp_context: RpContextWasm, action_description: Option, bridge_url: Option, + require_user_presence: bool, override_connect_base_url: Option, return_to: Option, environment: Option, @@ -1014,6 +1031,7 @@ pub fn prove_session( rp_context, action_description, bridge_url, + require_user_presence, override_connect_base_url, return_to, environment, @@ -1269,6 +1287,8 @@ export interface IDKitSessionConfig { bridge_url?: string; /** Optional deep-link callback URL appended as `return_to` on the connector URL */ return_to?: string; + /** Require World App to perform a user-presence check before verification. Defaults to false. */ + require_user_presence?: boolean; } /** RpContext for proof requests */ @@ -1401,6 +1421,7 @@ export function createSession( rp_context: RpContextWasm, action_description?: string, bridge_url?: string, + require_user_presence?: boolean, override_connect_base_url?: string, return_to?: string, environment?: string @@ -1419,6 +1440,7 @@ export function proveSession( rp_context: RpContextWasm, action_description?: string, bridge_url?: string, + require_user_presence?: boolean, override_connect_base_url?: string, return_to?: string, environment?: string @@ -1443,6 +1465,7 @@ mod tests { action_description: None, bridge_url: None, allow_legacy_proofs: false, + require_user_presence: false, override_connect_base_url: None, return_to: Some("idkit://callback?step=request".to_string()), environment: None, @@ -1458,6 +1481,28 @@ mod tests { ); } + #[test] + fn request_params_preserve_user_presence_requirement() { + let config = IDKitConfigWasm::Request { + app_id: "app_staging_test".to_string(), + action: "test-action".to_string(), + rp_context: sample_rp_context(), + action_description: None, + bridge_url: None, + allow_legacy_proofs: false, + require_user_presence: true, + override_connect_base_url: None, + return_to: None, + environment: None, + }; + + let params = config + .to_params(ConstraintNode::Any { any: Vec::new() }) + .expect("request params"); + + assert!(params.require_user_presence); + } + #[test] fn create_session_params_preserve_return_to() { let config = IDKitConfigWasm::CreateSession { @@ -1465,6 +1510,7 @@ mod tests { rp_context: sample_rp_context(), action_description: None, bridge_url: None, + require_user_presence: false, override_connect_base_url: None, return_to: Some("idkit://callback?step=create".to_string()), environment: None, @@ -1488,6 +1534,7 @@ mod tests { rp_context: sample_rp_context(), action_description: None, bridge_url: None, + require_user_presence: false, override_connect_base_url: None, return_to: Some("idkit://callback?step=prove".to_string()), environment: None, diff --git a/swift/Sources/IDKit/IDKit.swift b/swift/Sources/IDKit/IDKit.swift index 140d0a27..59510422 100644 --- a/swift/Sources/IDKit/IDKit.swift +++ b/swift/Sources/IDKit/IDKit.swift @@ -1,27 +1,127 @@ import Foundation -public typealias IDKitRequestConfig = IdKitRequestConfig -public typealias IDKitSessionConfig = IdKitSessionConfig public typealias IDKitResult = IdKitResult +/// Configuration for uniqueness proof requests. +public struct IDKitRequestConfig { + public let appId: String + public let action: String + public let rpContext: RpContext + public let actionDescription: String? + public let bridgeUrl: String? + public let allowLegacyProofs: Bool + public let requireUserPresence: Bool + public let overrideConnectBaseUrl: String? + public let returnTo: String? + public let environment: Environment? + public let connectUrlMode: ConnectUrlMode? + + public init( + appId: String, + action: String, + rpContext: RpContext, + actionDescription: String? = nil, + bridgeUrl: String? = nil, + allowLegacyProofs: Bool = false, + requireUserPresence: Bool = false, + overrideConnectBaseUrl: String? = nil, + returnTo: String? = nil, + environment: Environment? = nil, + connectUrlMode: ConnectUrlMode? = nil + ) { + self.appId = appId + self.action = action + self.rpContext = rpContext + self.actionDescription = actionDescription + self.bridgeUrl = bridgeUrl + self.allowLegacyProofs = allowLegacyProofs + self.requireUserPresence = requireUserPresence + self.overrideConnectBaseUrl = overrideConnectBaseUrl + self.returnTo = returnTo + self.environment = environment + self.connectUrlMode = connectUrlMode + } + + fileprivate var native: IdKitRequestConfig { + IdKitRequestConfig( + appId: appId, + action: action, + rpContext: rpContext, + actionDescription: actionDescription, + bridgeUrl: bridgeUrl, + allowLegacyProofs: allowLegacyProofs, + requireUserPresence: requireUserPresence, + overrideConnectBaseUrl: overrideConnectBaseUrl, + returnTo: returnTo, + environment: environment, + connectUrlMode: connectUrlMode + ) + } +} + +/// Configuration for session requests. +public struct IDKitSessionConfig { + public let appId: String + public let rpContext: RpContext + public let actionDescription: String? + public let bridgeUrl: String? + public let requireUserPresence: Bool + public let overrideConnectBaseUrl: String? + public let returnTo: String? + public let environment: Environment? + + public init( + appId: String, + rpContext: RpContext, + actionDescription: String? = nil, + bridgeUrl: String? = nil, + requireUserPresence: Bool = false, + overrideConnectBaseUrl: String? = nil, + returnTo: String? = nil, + environment: Environment? = nil + ) { + self.appId = appId + self.rpContext = rpContext + self.actionDescription = actionDescription + self.bridgeUrl = bridgeUrl + self.requireUserPresence = requireUserPresence + self.overrideConnectBaseUrl = overrideConnectBaseUrl + self.returnTo = returnTo + self.environment = environment + } + + fileprivate var native: IdKitSessionConfig { + IdKitSessionConfig( + appId: appId, + rpContext: rpContext, + actionDescription: actionDescription, + bridgeUrl: bridgeUrl, + requireUserPresence: requireUserPresence, + overrideConnectBaseUrl: overrideConnectBaseUrl, + returnTo: returnTo, + environment: environment + ) + } +} + /// Main entry point for IDKit Swift SDK. public enum IDKit { public static let version = "4.0.7" /// Creates a builder for uniqueness proof requests. public static func request(config: IDKitRequestConfig) -> IDKitBuilder { - IDKitBuilder(inner: IdKitBuilder.fromRequest(config: config)) + IDKitBuilder(inner: IdKitBuilder.fromRequest(config: config.native)) } // TODO: Re-enable when World ID 4.0 is live // /// Creates a builder for creating a new session. // public static func createSession(config: IDKitSessionConfig) -> IDKitBuilder { - // IDKitBuilder(inner: IdKitBuilder.fromCreateSession(config: config)) + // IDKitBuilder(inner: IdKitBuilder.fromCreateSession(config: config.native)) // } // /// Creates a builder for proving an existing session. // public static func proveSession(sessionId: String, config: IDKitSessionConfig) -> IDKitBuilder { - // IDKitBuilder(inner: IdKitBuilder.fromProveSession(sessionId: sessionId, config: config)) + // IDKitBuilder(inner: IdKitBuilder.fromProveSession(sessionId: sessionId, config: config.native)) // } /// Hashes a string signal to the canonical 0x-prefixed field element string. diff --git a/swift/Tests/IDKitTests/IDKitTests.swift b/swift/Tests/IDKitTests/IDKitTests.swift index bf659e46..5e74578e 100644 --- a/swift/Tests/IDKitTests/IDKitTests.swift +++ b/swift/Tests/IDKitTests/IDKitTests.swift @@ -50,6 +50,7 @@ func idkitEntrypoints() throws { actionDescription: nil, bridgeUrl: nil, allowLegacyProofs: false, + requireUserPresence: false, overrideConnectBaseUrl: nil, returnTo: nil, environment: nil, @@ -62,6 +63,7 @@ func idkitEntrypoints() throws { // rpContext: try sampleRpContext(), // actionDescription: nil, // bridgeUrl: nil, + // requireUserPresence: false, // overrideConnectBaseUrl: nil, // returnTo: nil, // environment: nil