Skip to content

Commit 98a968b

Browse files
committed
feat(rust): add IdentityAttribute enum
feat(rust): add IdentityCheck preset refactor(rust): make Preset::to_bridge_params outputs optional feat(rust): serialize identity attributes in bridge payload feat(rust): disable legacy fallback for IdentityCheck presets fix(rust): doc comment typos and missing IdentityCheck variant docs fix(rust): resolve clippy warnings in IdentityCheck preset code - rename to_bridge_params -> into_bridge_params (wrong_self_convention: consuming methods should use into_* prefix) - wrapped IdentityCheck in backticks in doc comment (doc_markdown).
1 parent 8dde6c5 commit 98a968b

4 files changed

Lines changed: 492 additions & 106 deletions

File tree

rust/core/src/bridge.rs

Lines changed: 99 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::{
66
crypto::{base64_decode, base64_encode, decrypt, encrypt},
77
error::{AppError, Error, Result},
88
types::{
9-
AppId, BridgeResponseV1, BridgeUrl, IDKitResult, ResponseItem, RpContext, VerificationLevel,
9+
AppId, BridgeResponseV1, BridgeUrl, IDKitResult, IdentityAttribute, ResponseItem,
10+
RpContext, VerificationLevel,
1011
},
1112
ConstraintNode, Signal,
1213
};
@@ -105,6 +106,11 @@ struct BridgeRequestPayload {
105106
#[serde(skip_serializing_if = "Option::is_none")]
106107
proof_request: Option<ProofRequest>,
107108

109+
/// Optional identity attribute filters for identity-attestation presets.
110+
/// Only present for World ID 4.0 identity check requests.
111+
#[serde(skip_serializing_if = "Option::is_none")]
112+
identity_attributes: Option<Vec<IdentityAttribute>>,
113+
108114
/// Whether to accept legacy (v3) proofs as fallback.
109115
/// - `true`: Accept both v3 and v4 proofs. Use during migration.
110116
/// - `false`: Only accept v4 proofs. Use after migration cutoff or for new apps.
@@ -259,6 +265,8 @@ pub struct BridgeConnectionParams {
259265
pub return_to: Option<String>,
260266
/// Optional environment override (defaults to Production when not specified)
261267
pub environment: Option<Environment>,
268+
/// Present only on World ID 4.0 requests created from `IdentityCheck` presets
269+
pub identity_attributes: Option<Vec<IdentityAttribute>>,
262270
}
263271

264272
/// A helper struct to cache the signal hashes of a request
@@ -416,6 +424,7 @@ pub fn build_request_payload(params: &BridgeConnectionParams) -> Result<serde_js
416424
action: action_str,
417425
action_description: params.action_description.clone(),
418426
proof_request,
427+
identity_attributes: params.identity_attributes.clone(),
419428
verification_level: params.legacy_verification_level,
420429
signal: legacy_signal_hash,
421430
allow_legacy_proofs: params.allow_legacy_proofs,
@@ -843,6 +852,7 @@ impl IDKitConfig {
843852
override_connect_base_url: config.override_connect_base_url.clone(),
844853
return_to: config.return_to.clone(),
845854
environment: config.environment,
855+
identity_attributes: None,
846856
})
847857
}
848858
Self::CreateSession(config) => {
@@ -867,6 +877,7 @@ impl IDKitConfig {
867877
override_connect_base_url: config.override_connect_base_url.clone(),
868878
return_to: config.return_to.clone(),
869879
environment: config.environment,
880+
identity_attributes: None,
870881
})
871882
}
872883
Self::ProveSession { session_id, config } => {
@@ -893,6 +904,7 @@ impl IDKitConfig {
893904
override_connect_base_url: config.override_connect_base_url.clone(),
894905
return_to: config.return_to.clone(),
895906
environment: config.environment,
907+
identity_attributes: None,
896908
})
897909
}
898910
}
@@ -911,7 +923,22 @@ impl IDKitConfig {
911923
});
912924
}
913925

914-
let (_constraints, legacy_verification_level, legacy_signal) = preset.to_bridge_params();
926+
let (constraints, legacy_verification_level, legacy_signal, identity_attributes) =
927+
preset.into_bridge_params();
928+
929+
// Default to Device so existing World App versions can still parse
930+
// the bridge payload. V4 requests use `proof_request` for real
931+
// credential selection; this field is only for v3 backwards compat.
932+
let legacy_verification_level =
933+
legacy_verification_level.unwrap_or(VerificationLevel::Device);
934+
let allow_legacy_proofs = if identity_attributes.is_some() {
935+
false
936+
} else {
937+
match self {
938+
Self::Request(config) => config.allow_legacy_proofs,
939+
Self::CreateSession(_) | Self::ProveSession { .. } => false,
940+
}
941+
};
915942

916943
match self {
917944
Self::Request(config) => {
@@ -927,16 +954,17 @@ impl IDKitConfig {
927954
kind: RequestKind::Uniqueness {
928955
action: config.action.clone(),
929956
},
930-
constraints: None,
957+
constraints,
931958
rp_context: (*config.rp_context).clone(),
932959
action_description: config.action_description.clone(),
933960
legacy_verification_level,
934961
legacy_signal: legacy_signal.unwrap_or_default(),
935962
bridge_url,
936-
allow_legacy_proofs: config.allow_legacy_proofs,
963+
allow_legacy_proofs,
937964
override_connect_base_url: config.override_connect_base_url.clone(),
938965
return_to: config.return_to.clone(),
939966
environment: config.environment,
967+
identity_attributes,
940968
})
941969
}
942970
Self::CreateSession(config) => {
@@ -950,7 +978,7 @@ impl IDKitConfig {
950978
Ok(BridgeConnectionParams {
951979
app_id,
952980
kind: RequestKind::CreateSession,
953-
constraints: None,
981+
constraints,
954982
rp_context: (*config.rp_context).clone(),
955983
action_description: config.action_description.clone(),
956984
legacy_verification_level,
@@ -960,6 +988,7 @@ impl IDKitConfig {
960988
override_connect_base_url: config.override_connect_base_url.clone(),
961989
return_to: config.return_to.clone(),
962990
environment: config.environment,
991+
identity_attributes,
963992
})
964993
}
965994
Self::ProveSession { session_id, config } => {
@@ -975,7 +1004,7 @@ impl IDKitConfig {
9751004
kind: RequestKind::ProveSession {
9761005
session_id: session_id.clone(),
9771006
},
978-
constraints: None,
1007+
constraints,
9791008
rp_context: (*config.rp_context).clone(),
9801009
action_description: config.action_description.clone(),
9811010
legacy_verification_level,
@@ -985,6 +1014,7 @@ impl IDKitConfig {
9851014
override_connect_base_url: config.override_connect_base_url.clone(),
9861015
return_to: config.return_to.clone(),
9871016
environment: config.environment,
1017+
identity_attributes,
9881018
})
9891019
}
9901020
}
@@ -1291,6 +1321,7 @@ mod tests {
12911321
signal: String::new(),
12921322
verification_level: VerificationLevel::Device,
12931323
proof_request: Some(proof_request),
1324+
identity_attributes: None,
12941325
allow_legacy_proofs: false,
12951326
environment: Environment::Production,
12961327
};
@@ -1304,6 +1335,55 @@ mod tests {
13041335
assert!(json.contains("allow_legacy_proofs"));
13051336
}
13061337

1338+
#[test]
1339+
fn test_build_request_payload_serializes_identity_attributes() {
1340+
let app_id = AppId::new("app_test").unwrap();
1341+
let rp_context = RpContext::new(
1342+
"rp_1234567890abcdef",
1343+
"0x0000000000000000000000000000000000000000000000000000000000000001",
1344+
1_700_000_000,
1345+
1_700_003_600,
1346+
&("0x".to_string() + &"00".repeat(64) + "1b"),
1347+
)
1348+
.unwrap();
1349+
let constraints = ConstraintNode::any(vec![
1350+
ConstraintNode::item(CredentialRequest::new(CredentialType::Passport, None)),
1351+
ConstraintNode::item(CredentialRequest::new(CredentialType::Mnc, None)),
1352+
]);
1353+
1354+
let params = BridgeConnectionParams {
1355+
app_id,
1356+
kind: RequestKind::Uniqueness {
1357+
action: "test-action".to_string(),
1358+
},
1359+
constraints: Some(constraints),
1360+
rp_context,
1361+
action_description: Some("Identity check".to_string()),
1362+
legacy_verification_level: VerificationLevel::Device,
1363+
legacy_signal: String::new(),
1364+
bridge_url: None,
1365+
allow_legacy_proofs: false,
1366+
override_connect_base_url: None,
1367+
return_to: None,
1368+
environment: Some(Environment::Production),
1369+
identity_attributes: Some(vec![
1370+
IdentityAttribute::MinimumAge(21),
1371+
IdentityAttribute::Nationality("JPN".to_string()),
1372+
]),
1373+
};
1374+
1375+
let payload = build_request_payload(&params).unwrap();
1376+
1377+
assert_eq!(
1378+
payload["identity_attributes"],
1379+
serde_json::json!([
1380+
{"type": "minimum_age", "value": 21},
1381+
{"type": "nationality", "value": "JPN"}
1382+
])
1383+
);
1384+
assert!(payload.get("proof_request").is_some());
1385+
}
1386+
13071387
#[test]
13081388
fn test_session_id_sdk_round_trip_uses_protocol_format() {
13091389
let session_id = SessionId::default();
@@ -1455,7 +1535,8 @@ mod tests {
14551535
#[test]
14561536
fn test_selfie_check_legacy_preset_serializes_face_verification_level() {
14571537
let preset = crate::preset::Preset::selfie_check_legacy(Some("face-signal".to_string()));
1458-
let (constraints, legacy_verification_level, legacy_signal) = preset.to_bridge_params();
1538+
let (constraints, legacy_verification_level, legacy_signal, _identity_attributes) =
1539+
preset.into_bridge_params();
14591540

14601541
let app_id = AppId::new("app_test").unwrap();
14611542
let signature = "0x".to_string() + &"00".repeat(64) + "1b";
@@ -1473,17 +1554,19 @@ mod tests {
14731554
kind: RequestKind::Uniqueness {
14741555
action: "test-action".to_string(),
14751556
},
1476-
constraints: Some(constraints),
1557+
constraints,
14771558
rp_context,
14781559
action_description: Some("Selfie check".to_string()),
1479-
legacy_verification_level,
1560+
legacy_verification_level: legacy_verification_level
1561+
.expect("this preset should return legacy_verification_level"),
14801562
legacy_signal: legacy_signal.unwrap_or_default(),
14811563
bridge_url: None,
14821564
allow_legacy_proofs: false,
14831565

14841566
override_connect_base_url: None,
14851567
return_to: None,
14861568
environment: Some(Environment::Production),
1569+
identity_attributes: None,
14871570
};
14881571

14891572
let payload = build_request_payload(&params).unwrap();
@@ -1493,7 +1576,8 @@ mod tests {
14931576
#[test]
14941577
fn test_device_legacy_preset_serializes_device_verification_level() {
14951578
let preset = crate::preset::Preset::device_legacy(Some("device-signal".to_string()));
1496-
let (constraints, legacy_verification_level, legacy_signal) = preset.to_bridge_params();
1579+
let (constraints, legacy_verification_level, legacy_signal, _identity_attributes) =
1580+
preset.into_bridge_params();
14971581

14981582
let app_id = AppId::new("app_test").unwrap();
14991583
let signature = "0x".to_string() + &"00".repeat(64) + "1b";
@@ -1511,17 +1595,19 @@ mod tests {
15111595
kind: RequestKind::Uniqueness {
15121596
action: "test-action".to_string(),
15131597
},
1514-
constraints: Some(constraints),
1598+
constraints,
15151599
rp_context,
15161600
action_description: Some("Device check".to_string()),
1517-
legacy_verification_level,
1601+
legacy_verification_level: legacy_verification_level
1602+
.expect("this preset should return legacy_verification_level"),
15181603
legacy_signal: legacy_signal.unwrap_or_default(),
15191604
bridge_url: None,
15201605
allow_legacy_proofs: false,
15211606

15221607
override_connect_base_url: None,
15231608
return_to: None,
15241609
environment: Some(Environment::Production),
1610+
identity_attributes: None,
15251611
};
15261612

15271613
let payload = build_request_payload(&params).unwrap();
@@ -1584,6 +1670,7 @@ mod tests {
15841670
override_connect_base_url: None,
15851671
return_to: None,
15861672
environment: None,
1673+
identity_attributes: None,
15871674
};
15881675

15891676
let payload = build_native_v1_payload(&params).unwrap();

0 commit comments

Comments
 (0)