Skip to content

Commit

Permalink
API V2 Refactor (#2151)
Browse files Browse the repository at this point in the history
* API V2 Refactor

This refactors the existing API v2 to be consistent with new patterns
introduced in the issuer API:
* Use camel case variants
* Use `Result` return values

The `Err` variants will be extended in the future with more types. These
will be breaking changes. However, given we mark the API v2 as experimental
this should be ok.

In order to have CI pass, we have an exception in the form of a patch file
that undoes the breaking changes that we intentionally made. If we introduce
additional breaking changes, the patch will no longer apply and thus make the
check fail.

* 🤖 npm run generate auto-update

* Replace unwrap with expect

* Replace more unwraps with expect

* Replace even more unwraps with expect

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Frederik Rothenberger and github-actions[bot] authored Dec 20, 2023
1 parent ced109e commit 2d49049
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 396 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/canister-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,27 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# we need the history for the tags, but we don't need the files (except for the GitHub workflows / actions)
# --> sparse checkout of only the needed things
fetch-depth: 0
sparse-checkout: |
src/internet_identity/internet_identity.did
.github
.didc-release
allowed_breaking_change.patch
sparse-checkout-cone-mode: false
- uses: ./.github/actions/setup-didc
- name: "Check canister interface compatibility"
run: |
release="release-2023-12-15"
# undo the breaking changes that we _explicitly_ made
# remove after the next release
# if we accidentally introduced other breaking changes, the patch would no longer apply / fix them
# making this job fail.
if [ "$(git describe --tags --match="release-[0-9-]*" HEAD --abbrev=0)" == "$release" ]; then
echo "Rolling back intentionally made breaking changes $release"
git apply allowed_breaking_change.patch
fi
curl -sSL https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity.did -o internet_identity_previous.did
didc check src/internet_identity/internet_identity.did internet_identity_previous.did
25 changes: 25 additions & 0 deletions allowed_breaking_change.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
diff --git a/src/internet_identity/internet_identity.did b/src/internet_identity/internet_identity.did
index 09937178..e545e0ba 100644
--- a/src/internet_identity/internet_identity.did
+++ b/src/internet_identity/internet_identity.did
@@ -313,16 +313,16 @@ type PublicKeyAuthn = record {

// The authentication methods currently supported by II.
type AuthnMethod = variant {
- WebAuthn: WebAuthn;
- PubKey: PublicKeyAuthn;
+ webauthn: WebAuthn;
+ pubkey: PublicKeyAuthn;
};

// This describes whether an authentication method is "protected" or not.
// When protected, a authentication method can only be updated or removed if the
// user is authenticated with that very authentication method.
type AuthnMethodProtection = variant {
- Protected;
- Unprotected;
+ protected;
+ unprotected;
};

type AuthnMethodData = record {
12 changes: 6 additions & 6 deletions src/canister_tests/src/api/internet_identity/api_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::collections::HashMap;
pub fn captcha_create(
env: &StateMachine,
canister_id: CanisterId,
) -> Result<Option<CaptchaCreateResponse>, CallError> {
) -> Result<Result<Challenge, ()>, CallError> {
call_candid(env, canister_id, "captcha_create", ()).map(|(x,)| x)
}

Expand All @@ -18,7 +18,7 @@ pub fn identity_register(
authn_method: &AuthnMethodData,
challenge_attempt: &ChallengeAttempt,
temp_key: Option<Principal>,
) -> Result<Option<IdentityRegisterResponse>, CallError> {
) -> Result<Result<IdentityNumber, IdentityRegisterError>, CallError> {
call_candid_as(
env,
canister_id,
Expand All @@ -34,7 +34,7 @@ pub fn identity_info(
canister_id: CanisterId,
sender: Principal,
identity_number: IdentityNumber,
) -> Result<Option<IdentityInfoResponse>, CallError> {
) -> Result<Result<IdentityInfo, ()>, CallError> {
call_candid_as(
env,
canister_id,
Expand All @@ -51,7 +51,7 @@ pub fn identity_metadata_replace(
sender: Principal,
identity_number: IdentityNumber,
metadata: &HashMap<String, MetadataEntry>,
) -> Result<Option<IdentityMetadataReplaceResponse>, CallError> {
) -> Result<Result<(), ()>, CallError> {
call_candid_as(
env,
canister_id,
Expand All @@ -68,7 +68,7 @@ pub fn authn_method_add(
sender: Principal,
identity_number: IdentityNumber,
authn_method: &AuthnMethodData,
) -> Result<Option<AuthnMethodAddResponse>, CallError> {
) -> Result<Result<(), AuthnMethodAddError>, CallError> {
call_candid_as(
env,
canister_id,
Expand All @@ -85,7 +85,7 @@ pub fn authn_method_remove(
sender: Principal,
identity_number: IdentityNumber,
public_key: &PublicKey,
) -> Result<Option<AuthnMethodRemoveResponse>, CallError> {
) -> Result<Result<(), ()>, CallError> {
call_candid_as(
env,
canister_id,
Expand Down
24 changes: 0 additions & 24 deletions src/canister_tests/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,27 +684,3 @@ pub fn test_principal(n: u64) -> Principal {
bytes.push(0xfe); // internal marker for user test ids
Principal::from_slice(&bytes[..])
}

/// Macro to easily match a value against a pattern, and panic if the match fails.
///
/// This makes API v2 return types easier to handle.
/// API v2 calls all return variants, requiring a match on the result.
/// This macro allows to write the match in terms of the expected variant, with a fallback
/// on unexpected variants.
/// Example:
/// ```
/// use canister_tests::match_value;
/// match_value!(
/// api_v2::identity_info(&env, canister_id, principal, identity_number)?, // value
/// Some(IdentityInfoResponse::Ok(identity_info)) // expected pattern, with binding to identity_info
/// );
/// ```
#[macro_export]
#[rustfmt::skip] // cargo fmt seems to have a bug with this macro (it indents the panic! way too far)
macro_rules! match_value {
($target: expr, $pat: pat_param) => {
let $pat = $target else {
panic!("expected {}", stringify!($pat));
};
};
}
44 changes: 20 additions & 24 deletions src/frontend/generated/internet_identity_idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,17 @@ export const idlFactory = ({ IDL }) => {
});
const IdentityNumber = IDL.Nat64;
const AuthnMethodProtection = IDL.Variant({
'unprotected' : IDL.Null,
'protected' : IDL.Null,
'Protected' : IDL.Null,
'Unprotected' : IDL.Null,
});
const PublicKeyAuthn = IDL.Record({ 'pubkey' : PublicKey });
const WebAuthn = IDL.Record({
'pubkey' : PublicKey,
'credential_id' : CredentialId,
});
const PublicKeyAuthn = IDL.Record({ 'pubkey' : PublicKey });
const AuthnMethod = IDL.Variant({
'webauthn' : WebAuthn,
'pubkey' : PublicKeyAuthn,
'PubKey' : PublicKeyAuthn,
'WebAuthn' : WebAuthn,
});
const AuthnMethodData = IDL.Record({
'metadata' : MetadataMap,
Expand All @@ -89,17 +89,12 @@ export const idlFactory = ({ IDL }) => {
'authn_method' : AuthnMethod,
'purpose' : Purpose,
});
const AuthnMethodAddResponse = IDL.Variant({
'ok' : IDL.Null,
'invalid_metadata' : IDL.Text,
});
const AuthnMethodRemoveResponse = IDL.Variant({ 'ok' : IDL.Null });
const AuthnMethodAddError = IDL.Variant({ 'InvalidMetadata' : IDL.Text });
const ChallengeKey = IDL.Text;
const Challenge = IDL.Record({
'png_base64' : IDL.Text,
'challenge_key' : ChallengeKey,
});
const CaptchaCreateResponse = IDL.Variant({ 'ok' : Challenge });
const DeployArchiveResult = IDL.Variant({
'creation_in_progress' : IDL.Null,
'success' : IDL.Principal,
Expand Down Expand Up @@ -213,18 +208,15 @@ export const idlFactory = ({ IDL }) => {
'metadata' : MetadataMap,
'authn_method_registration' : IDL.Opt(AuthnMethodRegistrationInfo),
});
const IdentityInfoResponse = IDL.Variant({ 'ok' : IdentityInfo });
const IdentityMetadataReplaceResponse = IDL.Variant({ 'ok' : IDL.Null });
const ChallengeResult = IDL.Record({
'key' : ChallengeKey,
'chars' : IDL.Text,
});
const CaptchaResult = ChallengeResult;
const IdentityRegisterResponse = IDL.Variant({
'ok' : IdentityNumber,
'invalid_metadata' : IDL.Text,
'bad_captcha' : IDL.Null,
'canister_full' : IDL.Null,
const IdentityRegisterError = IDL.Variant({
'BadCaptcha' : IDL.Null,
'CanisterFull' : IDL.Null,
'InvalidMetadata' : IDL.Text,
});
const UserKey = PublicKey;
const PrepareIdAliasRequest = IDL.Record({
Expand Down Expand Up @@ -274,15 +266,19 @@ export const idlFactory = ({ IDL }) => {
),
'authn_method_add' : IDL.Func(
[IdentityNumber, AuthnMethodData],
[IDL.Opt(AuthnMethodAddResponse)],
[IDL.Variant({ 'Ok' : IDL.Null, 'Err' : AuthnMethodAddError })],
[],
),
'authn_method_remove' : IDL.Func(
[IdentityNumber, PublicKey],
[IDL.Opt(AuthnMethodRemoveResponse)],
[IDL.Variant({ 'Ok' : IDL.Null, 'Err' : IDL.Null })],
[],
),
'captcha_create' : IDL.Func(
[],
[IDL.Variant({ 'Ok' : Challenge, 'Err' : IDL.Null })],
[],
),
'captcha_create' : IDL.Func([], [IDL.Opt(CaptchaCreateResponse)], []),
'create_challenge' : IDL.Func([], [Challenge], []),
'deploy_archive' : IDL.Func([IDL.Vec(IDL.Nat8)], [DeployArchiveResult], []),
'enter_device_registration_mode' : IDL.Func([UserNumber], [Timestamp], []),
Expand Down Expand Up @@ -313,17 +309,17 @@ export const idlFactory = ({ IDL }) => {
'http_request_update' : IDL.Func([HttpRequest], [HttpResponse], []),
'identity_info' : IDL.Func(
[IdentityNumber],
[IDL.Opt(IdentityInfoResponse)],
[IDL.Variant({ 'Ok' : IdentityInfo, 'Err' : IDL.Null })],
[],
),
'identity_metadata_replace' : IDL.Func(
[IdentityNumber, MetadataMap],
[IDL.Opt(IdentityMetadataReplaceResponse)],
[IDL.Variant({ 'Ok' : IDL.Null, 'Err' : IDL.Null })],
[],
),
'identity_register' : IDL.Func(
[AuthnMethodData, CaptchaResult, IDL.Opt(IDL.Principal)],
[IDL.Opt(IdentityRegisterResponse)],
[IDL.Variant({ 'Ok' : IdentityNumber, 'Err' : IdentityRegisterError })],
[],
),
'init_salt' : IDL.Func([], [], []),
Expand Down
42 changes: 22 additions & 20 deletions src/frontend/generated/internet_identity_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,28 @@ export interface ArchiveInfo {
'archive_config' : [] | [ArchiveConfig],
'archive_canister' : [] | [Principal],
}
export type AuthnMethod = { 'webauthn' : WebAuthn } |
{ 'pubkey' : PublicKeyAuthn };
export type AuthnMethodAddResponse = { 'ok' : null } |
{ 'invalid_metadata' : string };
export type AuthnMethod = { 'PubKey' : PublicKeyAuthn } |
{ 'WebAuthn' : WebAuthn };
export type AuthnMethodAddError = { 'InvalidMetadata' : string };
export interface AuthnMethodData {
'metadata' : MetadataMap,
'protection' : AuthnMethodProtection,
'last_authentication' : [] | [Timestamp],
'authn_method' : AuthnMethod,
'purpose' : Purpose,
}
export type AuthnMethodProtection = { 'unprotected' : null } |
{ 'protected' : null };
export type AuthnMethodProtection = { 'Protected' : null } |
{ 'Unprotected' : null };
export interface AuthnMethodRegistrationInfo {
'expiration' : Timestamp,
'authn_method' : [] | [AuthnMethodData],
}
export type AuthnMethodRemoveResponse = { 'ok' : null };
export interface BufferedArchiveEntry {
'sequence_number' : bigint,
'entry' : Uint8Array | number[],
'anchor_number' : UserNumber,
'timestamp' : Timestamp,
}
export type CaptchaCreateResponse = { 'ok' : Challenge };
export type CaptchaResult = ChallengeResult;
export interface Challenge {
'png_base64' : string,
Expand Down Expand Up @@ -136,13 +133,10 @@ export interface IdentityInfo {
'metadata' : MetadataMap,
'authn_method_registration' : [] | [AuthnMethodRegistrationInfo],
}
export type IdentityInfoResponse = { 'ok' : IdentityInfo };
export type IdentityMetadataReplaceResponse = { 'ok' : null };
export type IdentityNumber = bigint;
export type IdentityRegisterResponse = { 'ok' : IdentityNumber } |
{ 'invalid_metadata' : string } |
{ 'bad_captcha' : null } |
{ 'canister_full' : null };
export type IdentityRegisterError = { 'BadCaptcha' : null } |
{ 'CanisterFull' : null } |
{ 'InvalidMetadata' : string };
export interface InternetIdentityInit {
'max_num_latest_delegation_origins' : [] | [bigint],
'assigned_user_number_range' : [] | [[bigint, bigint]],
Expand Down Expand Up @@ -239,13 +233,15 @@ export interface _SERVICE {
>,
'authn_method_add' : ActorMethod<
[IdentityNumber, AuthnMethodData],
[] | [AuthnMethodAddResponse]
{ 'Ok' : null } |
{ 'Err' : AuthnMethodAddError }
>,
'authn_method_remove' : ActorMethod<
[IdentityNumber, PublicKey],
[] | [AuthnMethodRemoveResponse]
{ 'Ok' : null } |
{ 'Err' : null }
>,
'captcha_create' : ActorMethod<[], [] | [CaptchaCreateResponse]>,
'captcha_create' : ActorMethod<[], { 'Ok' : Challenge } | { 'Err' : null }>,
'create_challenge' : ActorMethod<[], Challenge>,
'deploy_archive' : ActorMethod<[Uint8Array | number[]], DeployArchiveResult>,
'enter_device_registration_mode' : ActorMethod<[UserNumber], Timestamp>,
Expand All @@ -265,14 +261,20 @@ export interface _SERVICE {
'get_principal' : ActorMethod<[UserNumber, FrontendHostname], Principal>,
'http_request' : ActorMethod<[HttpRequest], HttpResponse>,
'http_request_update' : ActorMethod<[HttpRequest], HttpResponse>,
'identity_info' : ActorMethod<[IdentityNumber], [] | [IdentityInfoResponse]>,
'identity_info' : ActorMethod<
[IdentityNumber],
{ 'Ok' : IdentityInfo } |
{ 'Err' : null }
>,
'identity_metadata_replace' : ActorMethod<
[IdentityNumber, MetadataMap],
[] | [IdentityMetadataReplaceResponse]
{ 'Ok' : null } |
{ 'Err' : null }
>,
'identity_register' : ActorMethod<
[AuthnMethodData, CaptchaResult, [] | [Principal]],
[] | [IdentityRegisterResponse]
{ 'Ok' : IdentityNumber } |
{ 'Err' : IdentityRegisterError }
>,
'init_salt' : ActorMethod<[], undefined>,
'lookup' : ActorMethod<[UserNumber], Array<DeviceData>>,
Expand Down
Loading

0 comments on commit 2d49049

Please sign in to comment.