Skip to content
Closed
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
4 changes: 2 additions & 2 deletions js/examples/nextjs/app/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,7 @@ export function DemoClient(): ReactElement {
app_id={APP_ID}
// allow empty string action as input
action={action ?? "test-action"}
session_id={shouldCreateSession ? "create" : undefined}
create_session={shouldCreateSession}
rp_context={widgetRpContext}
allow_legacy_proofs={shouldAllowLegacyProofs}
require_user_presence={requireUserPresence}
Expand All @@ -1982,7 +1982,7 @@ export function DemoClient(): ReactElement {
app_id={APP_ID}
// allow empty string action as input
action={action ?? "test-action"}
session_id={shouldCreateSession ? "create" : undefined}
create_session={shouldCreateSession}
rp_context={widgetRpContext}
allow_legacy_proofs={shouldAllowLegacyProofs}
require_user_presence={requireUserPresence}
Expand Down
17 changes: 15 additions & 2 deletions js/packages/core/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,19 @@ class IDKitInviteCodeBuilder {
// Entry points
// ─────────────────────────────────────────────────────────────────────────────

function sessionCreationOption(
config: IDKitRequestConfig,
): "create" | undefined {
if (
config.create_session !== undefined &&
typeof config.create_session !== "boolean"
) {
throw new Error("create_session must be a boolean");
}
if (config.session_id !== undefined) return config.session_id;
return config.create_session ? "create" : undefined;
}

/**
* Creates an IDKit verification request builder
*
Expand Down Expand Up @@ -960,7 +973,7 @@ function createRequestForNamespace(
package_name: options.package_name,
package_version: options.package_version,
action: String(config.action),
session_id: config.session_id,
session_id: sessionCreationOption(config),
rp_context: config.rp_context,
action_description: config.action_description,
bridge_url: config.bridge_url,
Expand Down Expand Up @@ -1026,7 +1039,7 @@ function createRequestWithInviteCodeForNamespace(
package_name: options.package_name,
package_version: options.package_version,
action: String(config.action),
session_id: config.session_id,
session_id: sessionCreationOption(config),
rp_context: config.rp_context,
action_description: config.action_description,
bridge_url: config.bridge_url,
Expand Down
58 changes: 54 additions & 4 deletions js/packages/core/src/transports/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,59 @@ describe("bridge raw JSON compatibility", () => {
});

describe("encrypted bridge lifecycle", () => {
it.each([false, true])(
"creates and validates a bound uniqueness proof over the encrypted bridge (invite=%s)",
async (invite) => {
it.each([IDKit.request, IDKit.requestWithInviteCode])(
"%s preserves ordinary uniqueness and rejects invalid session options",
async (request) => {
configureIDKitRuntime({
fetch: async (_url, init) =>
json({
request_id:
JSON.parse(init?.body as string).request_id ?? "request-id",
}),
});
for (const create_session of [undefined, false]) {
const result = await request({ ...config, create_session }).preset(
proofOfHuman(),
);
expect(result.getDebugReport().request_payload).toMatchObject({
proof_request: { proof_type: "uniqueness", session_id: null },
});
}
for (const create_session of ["true", 1, null]) {
expect(() =>
request({
...config,
create_session,
} as unknown as IDKitRequestConfig),
).toThrow("create_session must be a boolean");
}
for (const session_id of ["", SESSION, null]) {
await expect(
request({
...config,
create_session: true,
session_id,
} as unknown as IDKitRequestConfig).preset(proofOfHuman()),
).rejects.toThrow('Uniqueness session_id must be "create"');
}
await expect(
request({
...config,
create_session: true,
allow_legacy_proofs: true,
}).preset(proofOfHuman()),
).rejects.toThrow("allow_legacy_proofs: false");
},
);

it.each([
[false, { create_session: true }],
[true, { create_session: true }],
[false, { session_id: "create" as const }],
[true, { create_session: false, session_id: "create" as const }],
] as const)(
"creates and validates a bound uniqueness proof over the encrypted bridge (invite=%s, options=%j)",
async (invite, sessionOptions) => {
let pollBody: unknown = { status: "initialized" };
configureIDKitRuntime({
fetch: async (_url, init) => {
Expand All @@ -176,7 +226,7 @@ describe("encrypted bridge lifecycle", () => {
return json(pollBody);
},
});
const boundConfig = { ...config, session_id: "create" as const };
const boundConfig = { ...config, ...sessionOptions };
const request = await (invite
? IDKit.requestWithInviteCode(boundConfig).preset(proofOfHuman())
: IDKit.request(boundConfig).constraints({ type: "proof_of_human" }));
Expand Down
20 changes: 13 additions & 7 deletions js/packages/core/src/transports/native.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type { BuilderConfig } from "./native";
import { createNativeRequest, getWorldAppVerifyVersion } from "./native";
import { hashSignal } from "../lib/hashing";
import { setDebug } from "../lib/debug";
import { FIELD, SESSION, legacy, response } from "../conformance/cases";
import { FIELD, SESSION, config, legacy, response } from "../conformance/cases";
import { IDKit, proofOfHuman } from "../index";

const baseConfig: BuilderConfig = {
type: "request",
Expand Down Expand Up @@ -90,12 +91,17 @@ describe("native transport request lifecycle", () => {
it.each(["bound", "missing-session", "legacy"])(
"validates bound uniqueness through the native event channel (%s)",
async (variant) => {
const req = createNativeRequest(
{},
{ ...baseConfig, session_id: "create" },
{},
"",
);
(globalThis as any).window.WorldApp = {
supported_commands: [{ name: "verify", supported_versions: [2] }],
};
const req = await IDKit.request({
...config,
app_id: "app_staging_test",
create_session: true,
}).preset(proofOfHuman());
expect(req.getDebugReport().request_payload).toMatchObject({
proof_request: { proof_type: "uniqueness", session_id: "create" },
});
activeRequest = req;
const completion = req.pollUntilCompletion({ timeout: 1000 });
miniKitHandlers["miniapp-verify-action"]?.({
Expand Down
4 changes: 3 additions & 1 deletion js/packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export type IDKitRequestConfig = {
app_id: `app_${string}`;
/** Identifier for the action the user is performing. Should be left blank for [Sign in with Worldcoin](https://docs.world.org/id/sign-in). */
action: AbiEncodedValue | string;
/** Create a session bound to this uniqueness proof. Requires allow_legacy_proofs: false. */
/** Create a session bound to this uniqueness proof. Defaults to false; requires allow_legacy_proofs: false. */
create_session?: boolean;
/** @deprecated Use create_session instead. When supplied, this alias still requests session creation. */
session_id?: "create";
/** RP context for protocol-level proof requests (required) */
rp_context: RpContext;
Expand Down
6 changes: 4 additions & 2 deletions js/packages/react/src/__tests__/hooks.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ describe("request/session hooks", () => {
expect(requestMock).toHaveBeenCalledWith({
app_id: "app_test",
action: "test-action",
create_session: undefined,
session_id: undefined,
rp_context: baseRpContext,
action_description: undefined,
Expand Down Expand Up @@ -205,7 +206,7 @@ describe("request/session hooks", () => {
useRequest({
app_id: "app_test",
action: "signup",
session_id: "create",
create_session: true,
rp_context: baseRpContext,
allow_legacy_proofs: false,
preset: { type: "SelfieCheck" },
Expand All @@ -216,7 +217,7 @@ describe("request/session hooks", () => {
await waitFor(() => expect(result.current.isSuccess).toBe(true));

expect(request).toHaveBeenCalledWith(
expect.objectContaining({ action: "signup", session_id: "create" }),
expect.objectContaining({ action: "signup", create_session: true }),
);
expect(result.current.result).toEqual(response);
},
Expand Down Expand Up @@ -337,6 +338,7 @@ describe("request/session hooks", () => {
expect(requestMock).toHaveBeenCalledWith({
app_id: "app_test",
action: "test-action",
create_session: undefined,
session_id: undefined,
rp_context: baseRpContext,
action_description: undefined,
Expand Down
1 change: 1 addition & 0 deletions js/packages/react/src/hooks/useIDKitInviteCodeRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function useIDKitInviteCodeRequest(
const builder = IDKit.requestWithInviteCode({
app_id: config.app_id,
action: config.action,
create_session: config.create_session,
session_id: config.session_id,
rp_context: config.rp_context,
action_description: config.action_description,
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 @@ -13,6 +13,7 @@ export function useIDKitRequest(
const builder = IDKit.request({
app_id: config.app_id,
action: config.action,
create_session: config.create_session,
session_id: config.session_id,
rp_context: config.rp_context,
action_description: config.action_description,
Expand Down
25 changes: 24 additions & 1 deletion kotlin/bindings/src/main/kotlin/com/worldcoin/idkit/IdKit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,32 @@ data class IDKitRequestConfig(
val returnTo: String? = null,
val environment: Environment? = null,
val connectUrlMode: ConnectUrlMode? = null,
/** Set to "create" to bind a new session to the uniqueness proof. */
/** Deprecated: use [createSession] instead. Only "create" is accepted. */
val sessionId: String? = null,
) {
/** Whether the uniqueness proof creates a session. */
val createSession: Boolean get() = sessionId == "create"

constructor(
appId: String,
action: String,
rpContext: RpContext,
actionDescription: String? = null,
bridgeUrl: String? = null,
allowLegacyProofs: Boolean = false,
requireUserPresence: Boolean = false,
overrideConnectBaseUrl: String? = null,
returnTo: String? = null,
environment: Environment? = null,
connectUrlMode: ConnectUrlMode? = null,
sessionId: String? = null,
createSession: Boolean,
) : this(
appId, action, rpContext, actionDescription, bridgeUrl, allowLegacyProofs,
requireUserPresence, overrideConnectBaseUrl, returnTo, environment, connectUrlMode,
sessionId ?: if (createSession) "create" else null,
)

internal fun toNative(): NativeIDKitRequestConfig =
NativeIDKitRequestConfig(
appId = appId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class IDKitTests {
val proofRequest = payload.proofRequest!!
assertEquals(1u, proofRequest.version)
assertEquals("uniqueness", proofRequest.proofType)
assertNull(proofRequest.sessionId)
assertEquals("rp_1234567890abcdef", proofRequest.rpId)
assertEquals(1_700_000_000u, proofRequest.createdAt)
assertEquals(1_700_003_600u, proofRequest.expiresAt)
Expand Down Expand Up @@ -197,22 +198,27 @@ class IDKitTests {

@Test
fun `uniqueness requests optionally create a session without legacy fallback`() {
for (sessionId in listOf(null, "create")) {
for ((sessionId, createSession, expectedSessionId) in listOf(
Triple(null, false, null), Triple(null, true, "create"),
Triple("create", false, "create"), Triple("create", true, "create"),
)) {
val config = IDKitRequestConfig(
appId = "app_staging_1234567890abcdef",
action = "test-action",
rpContext = sampleRpContext(),
sessionId = sessionId,
createSession = createSession,
)
assertEquals(expectedSessionId != null, config.copy().createSession)
val payload = IDKit.createBridgePayloadFromPresets(
config, identityCheck(attributes = listOf(IdentityAttribute.MinimumAge(18u))),
)
val proofRequest = payload.proofRequest!!
assertEquals("test-action", payload.action)
assertEquals("uniqueness", proofRequest.proofType)
assertEquals(IDKit.hashSignal("test-action"), proofRequest.action)
assertEquals(sessionId, proofRequest.sessionId)
assertEquals(sessionId == null, payload.allowLegacyProofs)
assertEquals(expectedSessionId, proofRequest.sessionId)
assertEquals(expectedSessionId == null, payload.allowLegacyProofs)
}
}

Expand Down
10 changes: 7 additions & 3 deletions swift/Sources/IDKit/IDKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ public struct IDKitRequestConfig {
public let returnTo: String?
public let environment: Environment?
public let connectUrlMode: ConnectUrlMode?
/// Set to "create" to bind a new session to the uniqueness proof.
/// Deprecated: use `createSession` instead. Only "create" is accepted.
public let sessionId: String?
/// Bind a new session to the uniqueness proof.
public let createSession: Bool

public init(
appId: String,
Expand All @@ -39,7 +41,8 @@ public struct IDKitRequestConfig {
returnTo: String? = nil,
environment: Environment? = nil,
connectUrlMode: ConnectUrlMode? = nil,
sessionId: String? = nil
sessionId: String? = nil,
createSession: Bool = false
) {
self.appId = appId
self.action = action
Expand All @@ -53,6 +56,7 @@ public struct IDKitRequestConfig {
self.environment = environment
self.connectUrlMode = connectUrlMode
self.sessionId = sessionId
self.createSession = createSession

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize the legacy Swift alias into createSession

When an existing caller constructs IDKitRequestConfig(..., sessionId: "create"), this property remains at its default false, even though native prioritizes sessionId and sends "create". Any UI, logging, or control flow that reads the newly public config.createSession therefore reports the opposite of the request's actual behavior for the supported legacy alias; normalize the stored value in the initializer or derive it from sessionId, as the Kotlin wrapper does.

Useful? React with 👍 / 👎.

}

fileprivate var native: IdKitRequestConfig {
Expand All @@ -70,7 +74,7 @@ public struct IDKitRequestConfig {
returnTo: returnTo,
environment: environment,
connectUrlMode: connectUrlMode,
sessionId: sessionId
sessionId: sessionId ?? (createSession ? "create" : nil)
)
}
}
Expand Down
13 changes: 9 additions & 4 deletions swift/Tests/IDKitTests/IDKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func bridgeRequestPayloadIdentityCheck() throws {
let proofRequest = try #require(payload.proofRequest)
#expect(proofRequest.version == 1)
#expect(proofRequest.proofType == "uniqueness")
#expect(proofRequest.sessionId == nil)
#expect(proofRequest.rpId == "rp_1234567890abcdef")
#expect(proofRequest.createdAt == 1_700_000_000)
#expect(proofRequest.expiresAt == 1_700_003_600)
Expand Down Expand Up @@ -185,12 +186,16 @@ func bridgeRequestPayloadFromConstraints() throws {

@Test("uniqueness requests optionally create a session without legacy fallback")
func uniquenessSessionPayload() throws {
for sessionId in [nil, "create"] as [String?] {
for (sessionId, createSession, expectedSessionId) in [
(nil, false, nil), (nil, true, "create"),
("create", false, "create"), ("create", true, "create")
] as [(String?, Bool, String?)] {
let config = IDKitRequestConfig(
appId: "app_staging_1234567890abcdef",
action: "test-action",
rpContext: try sampleRpContext(),
sessionId: sessionId
sessionId: sessionId,
createSession: createSession
)
let payload = try IDKit.createBridgePayloadFromPresets(
config: config, preset: identityCheck(attributes: [.minimumAge(18)])
Expand All @@ -199,8 +204,8 @@ func uniquenessSessionPayload() throws {
#expect(payload.action == "test-action")
#expect(proofRequest.proofType == "uniqueness")
#expect(proofRequest.action == IDKit.hashSignal("test-action"))
#expect(proofRequest.sessionId == sessionId)
#expect(payload.allowLegacyProofs == (sessionId == nil))
#expect(proofRequest.sessionId == expectedSessionId)
#expect(payload.allowLegacyProofs == (expectedSessionId == nil))
}
}

Expand Down
Loading