Skip to content

Commit

Permalink
Change registration on API v2 to flow with 3 steps (#2629)
Browse files Browse the repository at this point in the history
* Change registration on API v2 to flow with 3 steps

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.

* 🤖 npm run generate auto-update

* Small review fixes

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 140a969 commit efc7537
Show file tree
Hide file tree
Showing 16 changed files with 895 additions and 172 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
59 changes: 45 additions & 14 deletions src/frontend/generated/internet_identity_idl.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ export const idlFactory = ({ IDL }) => {
const AuthnMethodSecuritySettingsReplaceError = IDL.Variant({
'AuthnMethodNotFound' : IDL.Null,
});
const CheckCaptchaArg = IDL.Record({ 'solution' : IDL.Text });
const RegistrationFlowNextStep = IDL.Variant({
'CheckCaptcha' : IDL.Record({ 'captcha_png_base64' : IDL.Text }),
'Finish' : IDL.Null,
});
const IdRegNextStepResult = IDL.Record({
'next_step' : RegistrationFlowNextStep,
});
const CheckCaptchaError = IDL.Variant({
'NoRegistrationFlow' : IDL.Null,
'UnexpectedCall' : IDL.Record({ 'next_step' : RegistrationFlowNextStep }),
'WrongSolution' : IDL.Record({ 'new_captcha_png_base64' : IDL.Text }),
});
const ChallengeKey = IDL.Text;
const Challenge = IDL.Record({
'png_base64' : IDL.Text,
Expand Down Expand Up @@ -283,15 +296,19 @@ export const idlFactory = ({ IDL }) => {
'space_available' : IDL.Nat64,
}),
});
const ChallengeResult = IDL.Record({
'key' : ChallengeKey,
'chars' : IDL.Text,
const IdRegFinishArg = IDL.Record({ 'authn_method' : AuthnMethodData });
const IdRegFinishResult = IDL.Record({ 'identity_number' : IDL.Nat64 });
const IdRegFinishError = IDL.Variant({
'NoRegistrationFlow' : IDL.Null,
'UnexpectedCall' : IDL.Record({ 'next_step' : RegistrationFlowNextStep }),
'InvalidAuthnMethod' : IDL.Text,
'IdentityLimitReached' : IDL.Null,
'StorageError' : IDL.Text,
});
const CaptchaResult = ChallengeResult;
const IdentityRegisterError = IDL.Variant({
'BadCaptcha' : IDL.Null,
'CanisterFull' : IDL.Null,
'InvalidMetadata' : IDL.Text,
const IdRegStartError = IDL.Variant({
'InvalidCaller' : IDL.Null,
'AlreadyInProgress' : IDL.Null,
'RateLimitExceeded' : IDL.Null,
});
const UserKey = PublicKey;
const PrepareIdAliasRequest = IDL.Record({
Expand All @@ -308,6 +325,10 @@ export const idlFactory = ({ IDL }) => {
'InternalCanisterError' : IDL.Text,
'Unauthorized' : IDL.Principal,
});
const ChallengeResult = IDL.Record({
'key' : ChallengeKey,
'chars' : IDL.Text,
});
const RegisterResponse = IDL.Variant({
'bad_challenge' : IDL.Null,
'canister_full' : IDL.Null,
Expand Down Expand Up @@ -411,9 +432,14 @@ export const idlFactory = ({ IDL }) => {
],
[],
),
'captcha_create' : IDL.Func(
[],
[IDL.Variant({ 'Ok' : Challenge, 'Err' : IDL.Null })],
'check_captcha' : IDL.Func(
[CheckCaptchaArg],
[
IDL.Variant({
'Ok' : IdRegNextStepResult,
'Err' : CheckCaptchaError,
}),
],
[],
),
'config' : IDL.Func([], [InternetIdentityInit], ['query']),
Expand Down Expand Up @@ -465,9 +491,14 @@ export const idlFactory = ({ IDL }) => {
],
[],
),
'identity_register' : IDL.Func(
[AuthnMethodData, CaptchaResult, IDL.Opt(IDL.Principal)],
[IDL.Variant({ 'Ok' : IdentityNumber, 'Err' : IdentityRegisterError })],
'identity_registration_finish' : IDL.Func(
[IdRegFinishArg],
[IDL.Variant({ 'Ok' : IdRegFinishResult, 'Err' : IdRegFinishError })],
[],
),
'identity_registration_start' : IDL.Func(
[],
[IDL.Variant({ 'Ok' : IdRegNextStepResult, 'Err' : IdRegStartError })],
[],
),
'init_salt' : IDL.Func([], [], []),
Expand Down
41 changes: 33 additions & 8 deletions src/frontend/generated/internet_identity_types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export interface Challenge {
}
export type ChallengeKey = string;
export interface ChallengeResult { 'key' : ChallengeKey, 'chars' : string }
export interface CheckCaptchaArg { 'solution' : string }
export type CheckCaptchaError = { 'NoRegistrationFlow' : null } |
{ 'UnexpectedCall' : { 'next_step' : RegistrationFlowNextStep } } |
{ 'WrongSolution' : { 'new_captcha_png_base64' : string } };
export type CredentialId = Uint8Array | number[];
export interface Delegation {
'pubkey' : PublicKey,
Expand Down Expand Up @@ -158,6 +162,17 @@ export interface IdAliasCredentials {
'rp_id_alias_credential' : SignedIdAlias,
'issuer_id_alias_credential' : SignedIdAlias,
}
export interface IdRegFinishArg { 'authn_method' : AuthnMethodData }
export type IdRegFinishError = { 'NoRegistrationFlow' : null } |
{ 'UnexpectedCall' : { 'next_step' : RegistrationFlowNextStep } } |
{ 'InvalidAuthnMethod' : string } |
{ 'IdentityLimitReached' : null } |
{ 'StorageError' : string };
export interface IdRegFinishResult { 'identity_number' : bigint }
export interface IdRegNextStepResult { 'next_step' : RegistrationFlowNextStep }
export type IdRegStartError = { 'InvalidCaller' : null } |
{ 'AlreadyInProgress' : null } |
{ 'RateLimitExceeded' : null };
export interface IdentityAnchorInfo {
'devices' : Array<DeviceWithUsage>,
'device_registration' : [] | [DeviceRegistrationInfo],
Expand All @@ -184,9 +199,6 @@ export type IdentityMetadataReplaceError = {
}
};
export type IdentityNumber = bigint;
export type IdentityRegisterError = { 'BadCaptcha' : null } |
{ 'CanisterFull' : null } |
{ 'InvalidMetadata' : string };
export interface InternetIdentityInit {
'assigned_user_number_range' : [] | [[bigint, bigint]],
'archive_config' : [] | [ArchiveConfig],
Expand Down Expand Up @@ -246,6 +258,10 @@ export interface RateLimitConfig {
export type RegisterResponse = { 'bad_challenge' : null } |
{ 'canister_full' : null } |
{ 'registered' : { 'user_number' : UserNumber } };
export type RegistrationFlowNextStep = {
'CheckCaptcha' : { 'captcha_png_base64' : string }
} |
{ 'Finish' : null };
export type SessionKey = PublicKey;
export interface SignedDelegation {
'signature' : Uint8Array | number[],
Expand Down Expand Up @@ -333,7 +349,11 @@ export interface _SERVICE {
{ 'Ok' : null } |
{ 'Err' : AuthnMethodSecuritySettingsReplaceError }
>,
'captcha_create' : ActorMethod<[], { 'Ok' : Challenge } | { 'Err' : null }>,
'check_captcha' : ActorMethod<
[CheckCaptchaArg],
{ 'Ok' : IdRegNextStepResult } |
{ 'Err' : CheckCaptchaError }
>,
'config' : ActorMethod<[], InternetIdentityInit>,
'create_challenge' : ActorMethod<[], Challenge>,
'deploy_archive' : ActorMethod<[Uint8Array | number[]], DeployArchiveResult>,
Expand Down Expand Up @@ -369,10 +389,15 @@ export interface _SERVICE {
{ 'Ok' : null } |
{ 'Err' : IdentityMetadataReplaceError }
>,
'identity_register' : ActorMethod<
[AuthnMethodData, CaptchaResult, [] | [Principal]],
{ 'Ok' : IdentityNumber } |
{ 'Err' : IdentityRegisterError }
'identity_registration_finish' : ActorMethod<
[IdRegFinishArg],
{ 'Ok' : IdRegFinishResult } |
{ 'Err' : IdRegFinishError }
>,
'identity_registration_start' : ActorMethod<
[],
{ 'Ok' : IdRegNextStepResult } |
{ 'Err' : IdRegStartError }
>,
'init_salt' : ActorMethod<[], undefined>,
'lookup' : ActorMethod<[UserNumber], Array<DeviceData>>,
Expand Down
Loading

0 comments on commit efc7537

Please sign in to comment.