Skip to content

Commit

Permalink
Change registration on API v2 to flow with 3 steps
Browse files Browse the repository at this point in the history
The registration for new identities using API v2 is now split into 3
different steps:
1. start a flow, providing a principal to track progress on that flow
2. solve captcha
3. submit authn_method to tie the identity to

This PR is a preparation to making the step 2 dynamically remove step 2
on low load.
  • Loading branch information
frederikrothenberger committed Sep 26, 2024
1 parent 6bdccc8 commit d7503f1
Show file tree
Hide file tree
Showing 14 changed files with 817 additions and 150 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/canister-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -955,8 +955,23 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-didc
- name: 'Get latest release'
uses: actions/github-script@v7
id: latest-release-tag
with:
result-encoding: string
script: return (await github.rest.repos.getLatestRelease({owner:"dfinity", repo:"internet-identity"})).data.tag_name;
- name: "Check canister interface compatibility"
run: |
release="release-2024-09-17"
# 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 [ "${{ steps.latest-release-tag.outputs.result }}" == "$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
Expand Down
121 changes: 121 additions & 0 deletions allowed_breaking_change.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
diff --git a/src/internet_identity/internet_identity.did b/src/internet_identity/internet_identity.did
index 500ef4d6..eb75b075 100644
--- a/src/internet_identity/internet_identity.did
+++ b/src/internet_identity/internet_identity.did
@@ -435,6 +435,17 @@ type IdentityInfoError = variant {
InternalCanisterError: text;
};

+
+
+type IdentityRegisterError = variant {
+ // No more registrations are possible in this instance of the II service canister.
+ CanisterFull;
+ // The captcha check was not successful.
+ BadCaptcha;
+ // The metadata of the provided authentication method contains invalid entries.
+ InvalidMetadata: text;
+};
+
type AuthnMethodAddError = variant {
InvalidMetadata: text;
};
@@ -521,72 +532,6 @@ type SignedIdAlias = record {
id_dapp : principal;
};

-type IdRegNextStepResult = record {
- // The next step in the registration flow
- next_step: RegistrationFlowNextStep;
-};
-
-type IdRegStartError = variant {
- // The method was called anonymously, which is not supported.
- InvalidCaller;
- // Too many registrations. Please try again later.
- RateLimitExceeded;
- // A registration flow is already in progress.
- AlreadyInProgress;
-};
-
-// The next step in the registration flow:
-// - CheckCaptcha: supply the solution to the captcha using `check_captcha`
-// - Finish: finish the registration using `identity_registration_finish`
-type RegistrationFlowNextStep = variant {
- // Supply the captcha solution using check_captcha
- CheckCaptcha: record {
- captcha_png_base64: text;
- };
- // Finish the registration using identity_registration_finish
- Finish;
-};
-
-type CheckCaptchaArg = record {
- solution : text;
-};
-
-type CheckCaptchaError = variant {
- // The supplied solution was wrong. Try again with the new captcha.
- WrongSolution: record {
- new_captcha_png_base64: text;
- };
- // This call is unexpected, see next_step.
- UnexpectedCall: record {
- next_step: RegistrationFlowNextStep;
- };
- // No registration flow ongoing for the caller.
- NoRegistrationFlow;
-};
-
-type IdRegFinishArg = record {
- authn_method: AuthnMethodData;
-};
-
-type IdRegFinishResult = record {
- identity_number: nat64;
-};
-
-type IdRegFinishError = variant {
- // The configured maximum number of identities has been reached.
- IdentityLimitReached;
- // This call is unexpected, see next_step.
- UnexpectedCall: record {
- next_step: RegistrationFlowNextStep;
- };
- // No registration flow ongoing for the caller.
- NoRegistrationFlow;
- // The supplied authn_method is not valid.
- InvalidAuthnMethod: text;
- // Error while persisting the new identity.
- StorageError: text;
-};
-
service : (opt InternetIdentityInit) -> {
// Legacy identity management API
// ==============================
@@ -613,17 +558,16 @@ service : (opt InternetIdentityInit) -> {

// V2 Identity Management API
// ==========================
- // WARNING: The following methods are experimental and may ch 0ange in the future.
-
- // Starts the identity registration flow to create a new identity.
- identity_registration_start: () -> (variant {Ok: IdRegNextStepResult; Err: IdRegStartError;});
+ // WARNING: The following methods are experimental and may change in the future.

- // Check the captcha challenge
- // If successful, the registration can be finished with `identity_registration_finish`.
- check_captcha: (CheckCaptchaArg) -> (variant {Ok: IdRegNextStepResult; Err: CheckCaptchaError;});
+ // Creates a new captcha. The solution needs to be submitted using the
+ // `identity_register` call.
+ captcha_create: () -> (variant {Ok: Challenge; Err;});

- // Starts the identity registration flow to create a new identity.
- identity_registration_finish: (IdRegFinishArg) -> (variant {Ok: IdRegFinishResult; Err: IdRegFinishError;});
+ // Registers a new identity with the given authn_method.
+ // A valid captcha solution to a previously generated captcha (using create_captcha) must be provided.
+ // The sender needs to match the supplied authn_method.
+ identity_register: (AuthnMethodData, CaptchaResult, opt principal) -> (variant {Ok: IdentityNumber; Err: IdentityRegisterError;});

// Returns information about the authentication methods of the identity with the given number.
// Only returns the minimal information required for authentication without exposing any metadata such as aliases.
39 changes: 29 additions & 10 deletions src/canister_tests/src/api/internet_identity/api_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,54 @@ use pocket_ic::common::rest::RawEffectivePrincipal;
use pocket_ic::{call_candid, call_candid_as, query_candid, CallError, PocketIc};
use std::collections::HashMap;

pub fn captcha_create(
pub fn identity_registration_start(
env: &PocketIc,
canister_id: CanisterId,
) -> Result<Result<Challenge, ()>, CallError> {
call_candid(
sender: Principal,
) -> Result<Result<IdRegNextStepResult, IdRegStartError>, CallError> {
call_candid_as(
env,
canister_id,
RawEffectivePrincipal::None,
"captcha_create",
sender,
"identity_registration_start",
(),
)
.map(|(x,)| x)
}

pub fn identity_register(
pub fn check_captcha(
env: &PocketIc,
canister_id: CanisterId,
sender: Principal,
solution: String,
) -> Result<Result<IdRegNextStepResult, CheckCaptchaError>, CallError> {
call_candid_as(
env,
canister_id,
RawEffectivePrincipal::None,
sender,
"check_captcha",
(CheckCaptchaArg { solution },),
)
.map(|(x,)| x)
}

pub fn identity_registration_finish(
env: &PocketIc,
canister_id: CanisterId,
sender: Principal,
authn_method: &AuthnMethodData,
challenge_attempt: &ChallengeAttempt,
temp_key: Option<Principal>,
) -> Result<Result<IdentityNumber, IdentityRegisterError>, CallError> {
) -> Result<Result<IdRegFinishResult, IdRegFinishError>, CallError> {
call_candid_as(
env,
canister_id,
RawEffectivePrincipal::None,
sender,
"identity_register",
(authn_method, challenge_attempt, temp_key),
"identity_registration_finish",
(IdRegFinishArg {
authn_method: authn_method.clone(),
},),
)
.map(|(x,)| x)
}
Expand Down
94 changes: 75 additions & 19 deletions src/internet_identity/internet_identity.did
Original file line number Diff line number Diff line change
Expand Up @@ -435,17 +435,6 @@ type IdentityInfoError = variant {
InternalCanisterError: text;
};



type IdentityRegisterError = variant {
// No more registrations are possible in this instance of the II service canister.
CanisterFull;
// The captcha check was not successful.
BadCaptcha;
// The metadata of the provided authentication method contains invalid entries.
InvalidMetadata: text;
};

type AuthnMethodAddError = variant {
InvalidMetadata: text;
};
Expand Down Expand Up @@ -532,6 +521,72 @@ type SignedIdAlias = record {
id_dapp : principal;
};

type IdRegNextStepResult = record {
// The next step in the registration flow
next_step: RegistrationFlowNextStep;
};

type IdRegStartError = variant {
// The method was called anonymously, which is not supported.
InvalidCaller;
// Too many registrations. Please try again later.
RateLimitExceeded;
// A registration flow is already in progress.
AlreadyInProgress;
};

// The next step in the registration flow:
// - CheckCaptcha: supply the solution to the captcha using `check_captcha`
// - Finish: finish the registration using `identity_registration_finish`
type RegistrationFlowNextStep = variant {
// Supply the captcha solution using check_captcha
CheckCaptcha: record {
captcha_png_base64: text;
};
// Finish the registration using identity_registration_finish
Finish;
};

type CheckCaptchaArg = record {
solution : text;
};

type CheckCaptchaError = variant {
// The supplied solution was wrong. Try again with the new captcha.
WrongSolution: record {
new_captcha_png_base64: text;
};
// This call is unexpected, see next_step.
UnexpectedCall: record {
next_step: RegistrationFlowNextStep;
};
// No registration flow ongoing for the caller.
NoRegistrationFlow;
};

type IdRegFinishArg = record {
authn_method: AuthnMethodData;
};

type IdRegFinishResult = record {
identity_number: nat64;
};

type IdRegFinishError = variant {
// The configured maximum number of identities has been reached.
IdentityLimitReached;
// This call is unexpected, see next_step.
UnexpectedCall: record {
next_step: RegistrationFlowNextStep;
};
// No registration flow ongoing for the caller.
NoRegistrationFlow;
// The supplied authn_method is not valid.
InvalidAuthnMethod: text;
// Error while persisting the new identity.
StorageError: text;
};

service : (opt InternetIdentityInit) -> {
// Legacy identity management API
// ==============================
Expand All @@ -558,16 +613,17 @@ service : (opt InternetIdentityInit) -> {

// V2 Identity Management API
// ==========================
// WARNING: The following methods are experimental and may change in the future.
// WARNING: The following methods are experimental and may ch 0ange in the future.

// Starts the identity registration flow to create a new identity.
identity_registration_start: () -> (variant {Ok: IdRegNextStepResult; Err: IdRegStartError;});

// Creates a new captcha. The solution needs to be submitted using the
// `identity_register` call.
captcha_create: () -> (variant {Ok: Challenge; Err;});
// Check the captcha challenge
// If successful, the registration can be finished with `identity_registration_finish`.
check_captcha: (CheckCaptchaArg) -> (variant {Ok: IdRegNextStepResult; Err: CheckCaptchaError;});

// Registers a new identity with the given authn_method.
// A valid captcha solution to a previously generated captcha (using create_captcha) must be provided.
// The sender needs to match the supplied authn_method.
identity_register: (AuthnMethodData, CaptchaResult, opt principal) -> (variant {Ok: IdentityNumber; Err: IdentityRegisterError;});
// Starts the identity registration flow to create a new identity.
identity_registration_finish: (IdRegFinishArg) -> (variant {Ok: IdRegFinishResult; Err: IdRegFinishError;});

// Returns information about the authentication methods of the identity with the given number.
// Only returns the minimal information required for authentication without exposing any metadata such as aliases.
Expand Down
7 changes: 5 additions & 2 deletions src/internet_identity/src/anchor_management/registration.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::anchor_management::registration::captcha::Base64;
use crate::anchor_management::{activity_bookkeeping, post_operation_bookkeeping};
use crate::state;
use crate::state::temp_keys::TempKeyId;
Expand All @@ -12,6 +11,9 @@ use internet_identity_interface::internet_identity::types::*;

mod captcha;
mod rate_limit;
pub mod registration_flow_v2;

pub use captcha::Base64;

pub async fn create_challenge() -> Challenge {
let mut rng = captcha::make_rng().await;
Expand Down Expand Up @@ -65,7 +67,8 @@ pub fn register(
// The key is optional for backwards compatibility
temp_key: Option<Principal>,
) -> RegisterResponse {
rate_limit::process_rate_limit();
rate_limit::process_rate_limit()
.unwrap_or_else(|_| trap("rate limit reached, try again later"));
if let Err(()) = captcha::check_challenge(challenge_result) {
return RegisterResponse::BadChallenge;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use lazy_static::lazy_static;
use rand_core::{RngCore, SeedableRng};
use std::collections::{HashMap, HashSet};

#[derive(Clone, Debug)]
pub struct Base64(pub String);

lazy_static! {
Expand Down Expand Up @@ -143,7 +144,7 @@ pub fn check_challenge(res: ChallengeAttempt) -> Result<(), ()> {

/// Check whether the supplied CAPTCHA solution attempt matches the expected solution (after
/// normalizing ambiguous characters).
fn check_captcha_solution(solution_attempt: String, solution: String) -> Result<(), ()> {
pub fn check_captcha_solution(solution_attempt: String, solution: String) -> Result<(), ()> {
// avoid processing too many characters
if solution_attempt.len() > CAPTCHA_LENGTH {
return Err(());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::state;
use crate::state::RateLimitState;
use ic_cdk::api::time;
use ic_cdk::trap;
use internet_identity_interface::internet_identity::types::RateLimitConfig;
use std::cmp::min;

Expand All @@ -15,7 +14,7 @@ use std::cmp::min;
/// tokens have replenished.
/// There is a maximum of `max_tokens` tokens, when reached the tokens not increase any further.
/// This is the maximum number of calls that can be handled in a burst.
pub fn process_rate_limit() {
pub fn process_rate_limit() -> Result<(), ()> {
let config = state::persistent_state(|ps| ps.registration_rate_limit.clone());

state::registration_rate_limit_mut(|state_opt| {
Expand All @@ -35,8 +34,9 @@ pub fn process_rate_limit() {
if state.tokens > 0 {
state.tokens -= 1;
} else {
trap("rate limit reached, try again later");
return Err(());
}
Ok(())
})
}

Expand Down
Loading

0 comments on commit d7503f1

Please sign in to comment.