Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions js/packages/core/src/__tests__/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -173,6 +202,7 @@ describe("IDKitRequest API", () => {
null,
null,
true,
false,
null,
null,
"production",
Expand Down
6 changes: 6 additions & 0 deletions js/packages/core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down
34 changes: 33 additions & 1 deletion js/packages/core/src/transports/native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
9 changes: 9 additions & 0 deletions js/packages/core/src/transports/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions js/packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down
41 changes: 41 additions & 0 deletions js/packages/react/src/__tests__/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 () => ({
Expand All @@ -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: [] },
}),
);
Expand 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,
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions js/packages/react/src/hooks/useIDKitRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
2 changes: 2 additions & 0 deletions js/packages/react/src/hooks/useIDKitSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
67 changes: 58 additions & 9 deletions kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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))
Expand Down
Loading
Loading