Skip to content

Commit bc68efc

Browse files
authored
Add sessions to device registration state (dfinity#3219)
* Add sessions to device registration state. * Add sessions to device registration state. * Add sessions to device registration state. * Add sessions to device registration state. * Add sessions to device registration state. * Declaration files. * Don't rename state variable
1 parent c3869d2 commit bc68efc

8 files changed

Lines changed: 169 additions & 116 deletions

File tree

src/frontend/src/lib/generated/internet_identity_idl.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ export const idlFactory = ({ IDL }) => {
142142
});
143143
const AuthnMethodAddError = IDL.Variant({ 'InvalidMetadata' : IDL.Text });
144144
const AuthnMethodConfirmationError = IDL.Variant({
145+
'InternalCanisterError' : IDL.Text,
145146
'RegistrationModeOff' : IDL.Null,
147+
'Unauthorized' : IDL.Principal,
146148
'NoAuthnMethodToConfirm' : IDL.Null,
147149
'WrongCode' : IDL.Record({ 'retries_left' : IDL.Nat8 }),
148150
});

src/frontend/src/lib/generated/internet_identity_types.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ export interface AuthnMethodConfirmationCode {
5454
'confirmation_code' : string,
5555
'expiration' : Timestamp,
5656
}
57-
export type AuthnMethodConfirmationError = { 'RegistrationModeOff' : null } |
57+
export type AuthnMethodConfirmationError = {
58+
'InternalCanisterError' : string
59+
} |
60+
{ 'RegistrationModeOff' : null } |
61+
{ 'Unauthorized' : Principal } |
5862
{ 'NoAuthnMethodToConfirm' : null } |
5963
{ 'WrongCode' : { 'retries_left' : number } };
6064
export interface AuthnMethodData {

src/internet_identity/internet_identity.did

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ type AuthnMethodRegisterError = variant {
505505
};
506506

507507
type AuthnMethodConfirmationError = variant {
508+
Unauthorized : principal;
509+
InternalCanisterError : text;
508510
// Wrong confirmation code entered. Retry with correct code.
509511
WrongCode : record {
510512
retries_left : nat8;

src/internet_identity/src/anchor_management/tentative_device_registration.rs

Lines changed: 88 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
use crate::anchor_management::add_device;
21
use crate::authz_utils::IdentityUpdateError;
3-
use crate::state::RegistrationState::{DeviceRegistrationModeActive, DeviceTentativelyAdded};
2+
use crate::state::RegistrationState::{
3+
DeviceRegistrationModeActive, DeviceTentativelyAdded, SessionTentativelyAdded,
4+
SessionTentativelyConfirmed,
5+
};
46
use crate::state::TentativeDeviceRegistration;
5-
use crate::storage::anchor::Anchor;
67
use crate::{secs_to_nanos, state};
78
use candid::{CandidType, Principal};
89
use ic_cdk::api::time;
910
use ic_cdk::{call, trap};
10-
use internet_identity_interface::archive::types::Operation;
1111
use internet_identity_interface::internet_identity::types::*;
1212
use std::collections::{hash_map, HashMap};
1313
use TentativeDeviceRegistrationError::{AnotherDeviceTentativelyAdded, DeviceRegistrationModeOff};
@@ -77,7 +77,7 @@ pub fn exit_device_registration_mode(anchor_number: AnchorNumber) {
7777
}
7878

7979
pub struct TentativeRegistrationInfo {
80-
pub verification_code: DeviceVerificationCode,
80+
pub verification_code: DeviceConfirmationCode,
8181
pub device_registration_timeout: Timestamp,
8282
}
8383

@@ -96,35 +96,42 @@ impl From<IdentityUpdateError> for TentativeDeviceRegistrationError {
9696

9797
pub async fn add_tentative_device(
9898
anchor_number: AnchorNumber,
99-
device_data: DeviceData,
99+
tentative_device: DeviceData,
100100
) -> Result<TentativeRegistrationInfo, TentativeDeviceRegistrationError> {
101-
let verification_code = new_verification_code().await;
101+
let confirmation_code = new_confirmation_code().await;
102102
let now = time();
103103

104104
state::tentative_device_registrations_mut(|registrations| {
105105
state::lookup_tentative_device_registration_mut(|lookup| {
106106
prune_expired_tentative_device_registrations(registrations, lookup);
107107

108-
match registrations.get_mut(&anchor_number) {
109-
None => Err(DeviceRegistrationModeOff),
110-
Some(TentativeDeviceRegistration { expiration, .. }) if *expiration <= now => {
108+
// Get registration else throw error
109+
let registration = registrations
110+
.get_mut(&anchor_number)
111+
.ok_or(DeviceRegistrationModeOff)?;
112+
113+
match registration {
114+
// Make sure registration isn't expired
115+
TentativeDeviceRegistration { expiration, .. } if *expiration <= now => {
111116
Err(DeviceRegistrationModeOff)
112117
}
113-
Some(TentativeDeviceRegistration {
114-
state: DeviceTentativelyAdded { .. },
118+
// Add tentative session if registration mode is active
119+
TentativeDeviceRegistration {
120+
state: DeviceRegistrationModeActive,
115121
..
116-
}) => Err(AnotherDeviceTentativelyAdded),
117-
Some(registration) => {
122+
} => {
118123
registration.state = DeviceTentativelyAdded {
119-
tentative_device: device_data,
124+
tentative_device,
120125
failed_attempts: 0,
121-
verification_code: verification_code.clone(),
126+
confirmation_code: confirmation_code.clone(),
122127
};
123128
Ok(TentativeRegistrationInfo {
124129
device_registration_timeout: registration.expiration,
125-
verification_code,
130+
verification_code: confirmation_code,
126131
})
127132
}
133+
// Else return error
134+
_ => Err(AnotherDeviceTentativelyAdded),
128135
}
129136
})
130137
})
@@ -135,82 +142,80 @@ pub enum VerifyTentativeDeviceError {
135142
WrongCode { retries_left: u8 },
136143
DeviceRegistrationModeOff,
137144
NoDeviceToVerify,
138-
IdentityUpdateError(IdentityUpdateError),
139-
}
140-
141-
impl From<IdentityUpdateError> for VerifyTentativeDeviceError {
142-
fn from(err: IdentityUpdateError) -> Self {
143-
VerifyTentativeDeviceError::IdentityUpdateError(err)
144-
}
145-
}
146-
147-
/// Verifies the tentative device using the submitted `user_verification_code` and returns
148-
/// a result of [VerifyTentativeDeviceResponse]. [VerifyTentativeDeviceResponse] is used both as
149-
/// a success and an error type because it corresponds to the candid variant unifying success and
150-
/// error cases. See `authenticated_anchor_operation` for more details on how the [Result] is handled.
151-
pub fn verify_tentative_device(
152-
anchor: &mut Anchor,
153-
anchor_number: AnchorNumber,
154-
user_verification_code: DeviceVerificationCode,
155-
) -> Result<((), Operation), VerifyTentativeDeviceError> {
156-
match get_verified_device(anchor_number, user_verification_code) {
157-
Ok(device) => {
158-
let operation = add_device(anchor, device);
159-
Ok(((), operation))
160-
}
161-
Err(err) => Err(err),
162-
}
163145
}
164146

165-
/// Checks the device verification code for a tentative device.
166-
/// If valid, returns the device to be added and exits device registration mode
167-
/// If invalid, returns the appropriate error to send to the client and increases failed attempts. Exits device registration mode if there are no retries left.
168-
fn get_verified_device(
147+
/// Confirm the tentative device or session using the submitted `user_confirmation_code`.
148+
///
149+
/// # Returns
150+
/// [`Some(DeviceData)`] if a device (or [`None`] if a session) has been confirmed
151+
///
152+
/// # Errors
153+
/// Returns an error if either there's no tentative device (or session) or the code is incorrect.
154+
pub fn confirm_tentative_device_or_session(
169155
anchor_number: AnchorNumber,
170-
user_verification_code: DeviceVerificationCode,
171-
) -> Result<DeviceData, VerifyTentativeDeviceError> {
156+
user_confirmation_code: DeviceConfirmationCode,
157+
) -> Result<Option<DeviceData>, VerifyTentativeDeviceError> {
172158
state::tentative_device_registrations_mut(|registrations| {
173159
state::lookup_tentative_device_registration_mut(|lookup| {
174160
prune_expired_tentative_device_registrations(registrations, lookup);
175161

176-
// Check if the registration exists
177-
if !registrations.contains_key(&anchor_number) {
178-
return Err(VerifyTentativeDeviceError::DeviceRegistrationModeOff);
179-
}
162+
// Get registration else throw error
163+
let registration = registrations
164+
.get_mut(&anchor_number)
165+
.ok_or(VerifyTentativeDeviceError::DeviceRegistrationModeOff)?;
166+
167+
let (should_remove, response) = match &mut registration.state {
168+
DeviceRegistrationModeActive | SessionTentativelyConfirmed { .. } => {
169+
(false, Err(VerifyTentativeDeviceError::NoDeviceToVerify))
170+
}
171+
DeviceTentativelyAdded {
172+
failed_attempts,
173+
confirmation_code,
174+
tentative_device,
175+
} => {
176+
if user_confirmation_code == *confirmation_code {
177+
// Verification successful - return device to be added and remove the registration
178+
(true, Ok(Some(tentative_device.clone())))
179+
} else {
180+
// Increment failed attempts counter
181+
*failed_attempts += 1;
180182

181-
// Check the state and prepare the response without keeping a mutable reference
182-
let (should_remove, response) =
183-
match &mut registrations.get_mut(&anchor_number).unwrap().state {
184-
DeviceRegistrationModeActive => {
185-
(false, Err(VerifyTentativeDeviceError::NoDeviceToVerify))
183+
// Remove registration if max attempts reached
184+
(
185+
*failed_attempts >= MAX_DEVICE_REGISTRATION_ATTEMPTS,
186+
Err(VerifyTentativeDeviceError::WrongCode {
187+
retries_left: (MAX_DEVICE_REGISTRATION_ATTEMPTS - *failed_attempts),
188+
}),
189+
)
186190
}
187-
DeviceTentativelyAdded {
188-
failed_attempts,
189-
verification_code,
190-
tentative_device,
191-
} => {
192-
if user_verification_code == *verification_code {
193-
// Verification successful - we'll remove the registration
194-
(true, Ok(tentative_device.clone()))
195-
} else {
196-
// Increment failed attempts counter
197-
*failed_attempts += 1;
191+
}
192+
SessionTentativelyAdded {
193+
failed_attempts,
194+
confirmation_code,
195+
tentative_session,
196+
} => {
197+
if user_confirmation_code == *confirmation_code {
198+
let principal = *tentative_session;
198199

199-
// Check if max attempts reached
200-
let should_remove =
201-
*failed_attempts >= MAX_DEVICE_REGISTRATION_ATTEMPTS;
200+
// Verification successful - we'll confirm the session and keep the registration
201+
registration.state = SessionTentativelyConfirmed {
202+
tentative_session: principal,
203+
};
204+
(false, Ok(None))
205+
} else {
206+
// Increment failed attempts counter
207+
*failed_attempts += 1;
202208

203-
(
204-
should_remove,
205-
Err(VerifyTentativeDeviceError::WrongCode {
206-
retries_left: (MAX_DEVICE_REGISTRATION_ATTEMPTS
207-
- *failed_attempts),
208-
}),
209-
)
210-
}
209+
// Remove registration if max attempts reached
210+
(
211+
*failed_attempts >= MAX_DEVICE_REGISTRATION_ATTEMPTS,
212+
Err(VerifyTentativeDeviceError::WrongCode {
213+
retries_left: (MAX_DEVICE_REGISTRATION_ATTEMPTS - *failed_attempts),
214+
}),
215+
)
211216
}
212-
};
213-
217+
}
218+
};
214219
// Now handle removal if needed, after we're done with the mutable borrow
215220
if should_remove {
216221
if let Some(TentativeDeviceRegistration {
@@ -221,14 +226,13 @@ fn get_verified_device(
221226
lookup.remove(&reg_id);
222227
}
223228
}
224-
225229
response
226230
})
227231
})
228232
}
229233

230-
/// Return a decimal representation of a random `u32` to be used as verification code
231-
async fn new_verification_code() -> DeviceVerificationCode {
234+
/// Return a decimal representation of a random `u32` to be used as confirmation code
235+
async fn new_confirmation_code() -> DeviceConfirmationCode {
232236
let res: Vec<u8> = match call(Principal::management_canister(), "raw_rand", ()).await {
233237
Ok((res,)) => res,
234238
Err((_, err)) => trap(&format!("failed to get randomness: {err}")),

src/internet_identity/src/main.rs

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,26 @@ async fn add_tentative_device(
123123
#[update]
124124
fn verify_tentative_device(
125125
anchor_number: AnchorNumber,
126-
user_verification_code: DeviceVerificationCode,
126+
user_confirmation_code: DeviceConfirmationCode,
127127
) -> VerifyTentativeDeviceResponse {
128-
let result = anchor_operation_with_authz_check(anchor_number, |anchor| {
129-
tentative_device_registration::verify_tentative_device(
130-
anchor,
131-
anchor_number,
132-
user_verification_code,
133-
)
134-
});
135-
match result {
136-
Ok(()) => VerifyTentativeDeviceResponse::Verified,
128+
let (mut anchor, authorization_key) = check_authorization(anchor_number)
129+
.unwrap_or_else(|err| trap(&format!("{} could not be authenticated.", err.principal)));
130+
match tentative_device_registration::confirm_tentative_device_or_session(
131+
anchor_number,
132+
user_confirmation_code,
133+
) {
134+
Ok(maybe_confirmed_device) => {
135+
if let Some(confirmed_device) = maybe_confirmed_device {
136+
// Add device to anchor with bookkeeping if it has been confirmed
137+
anchor_management::activity_bookkeeping(&mut anchor, &authorization_key);
138+
let operation = anchor_management::add_device(&mut anchor, confirmed_device);
139+
if let Err(err) = state::storage_borrow_mut(|storage| storage.update(anchor)) {
140+
trap(&format!("{err}"));
141+
}
142+
anchor_management::post_operation_bookkeeping(anchor_number, operation);
143+
}
144+
VerifyTentativeDeviceResponse::Verified
145+
}
137146
Err(err) => match err {
138147
VerifyTentativeDeviceError::DeviceRegistrationModeOff => {
139148
VerifyTentativeDeviceResponse::DeviceRegistrationModeOff
@@ -144,10 +153,6 @@ fn verify_tentative_device(
144153
VerifyTentativeDeviceError::WrongCode { retries_left } => {
145154
VerifyTentativeDeviceResponse::WrongCode { retries_left }
146155
}
147-
VerifyTentativeDeviceError::IdentityUpdateError(err) => {
148-
// Legacy API traps instead of returning an error.
149-
trap(String::from(err).as_str())
150-
}
151156
},
152157
}
153158
}
@@ -926,19 +931,37 @@ mod v2_api {
926931
identity_number: IdentityNumber,
927932
confirmation_code: String,
928933
) -> Result<(), AuthnMethodConfirmationError> {
929-
let response = verify_tentative_device(identity_number, confirmation_code);
930-
match response {
931-
VerifyTentativeDeviceResponse::Verified => Ok(()),
932-
VerifyTentativeDeviceResponse::WrongCode { retries_left } => {
933-
Err(AuthnMethodConfirmationError::WrongCode { retries_left })
934-
}
935-
VerifyTentativeDeviceResponse::DeviceRegistrationModeOff => {
936-
Err(AuthnMethodConfirmationError::RegistrationModeOff)
937-
}
938-
VerifyTentativeDeviceResponse::NoDeviceToVerify => {
939-
Err(AuthnMethodConfirmationError::NoAuthnMethodToConfirm)
940-
}
934+
let (mut anchor, authorization_key) = check_authorization(identity_number)
935+
.map_err(|err| AuthnMethodConfirmationError::Unauthorized(err.principal))?;
936+
937+
let maybe_confirmed_device =
938+
tentative_device_registration::confirm_tentative_device_or_session(
939+
identity_number,
940+
confirmation_code,
941+
)
942+
.map_err(|err| match err {
943+
VerifyTentativeDeviceError::WrongCode { retries_left } => {
944+
AuthnMethodConfirmationError::WrongCode { retries_left }
945+
}
946+
VerifyTentativeDeviceError::DeviceRegistrationModeOff => {
947+
AuthnMethodConfirmationError::RegistrationModeOff
948+
}
949+
VerifyTentativeDeviceError::NoDeviceToVerify => {
950+
AuthnMethodConfirmationError::NoAuthnMethodToConfirm
951+
}
952+
})?;
953+
954+
if let Some(confirmed_device) = maybe_confirmed_device {
955+
// Add device to anchor with bookkeeping if it has been confirmed
956+
anchor_management::activity_bookkeeping(&mut anchor, &authorization_key);
957+
let operation = anchor_management::add_device(&mut anchor, confirmed_device);
958+
state::storage_borrow_mut(|storage| storage.update(anchor)).map_err(|err| {
959+
AuthnMethodConfirmationError::InternalCanisterError(err.to_string())
960+
})?;
961+
anchor_management::post_operation_bookkeeping(identity_number, operation);
941962
}
963+
964+
Ok(())
942965
}
943966

944967
#[query]

0 commit comments

Comments
 (0)