/// 1. Run the HTTPS request as defined by [`HttpsRequest`] and let `response` be the result. /// 2. Provide `response` to the associated task as a [`CreateIdentityResponse`] and poll again. pubstruct CreateIdentityInstruction { /// The HTTPs request to be made. pub request: HttpsRequest,
}
/// Possible response to an [`CreateIdentityInstruction`]. #[derive(Name)] pubstruct CreateIdentityResponse { /// Result for the HTTPS request. pub result: HttpsResult,
}
/// Result of creating an identity. pubstruct CreateIdentityResult { /// Assigned identity. pub identity: ThreemaId,
/// Client key associated to the identity. pub client_key: ClientKey,
/// Assigned server group. pub server_group: ChatServerGroup,
}
/// Result of polling a [`CreateIdentityTask`]. pubtype CreateIdentityLoop = TaskLoop<CreateIdentityInstruction, CreateIdentityResult>;
/// Context for creating an identity. pubstruct CreateIdentityContext { /// Client info. pub client_info: ClientInfo,
/// Configuration used by the protocol. pub config: Rc<Config>,
#[derive(DebugVariantNames, VariantNames)] enum State {
Error(CspE2eProtocolError),
Init(InitState),
DirectoryChallenge(DirectoryChallengeState),
DirectoryCreate(DirectoryCreateState),
RegisterWorkChallenge(RegisterWorkChallengeState),
RegisterWork(RegisterWorkState),
Done,
} impl State { fn poll_init(context: &CreateIdentityContext, state: InitState) -> (Self, CreateIdentityLoop) { // Request an authentication challenge to create an identity let public_key = state.client_key.public_key();
debug!("Requesting challenge to create identity");
( Self::DirectoryChallenge(DirectoryChallengeState {
client_key: state.client_key,
response: None,
}),
CreateIdentityLoop::Instruction(CreateIdentityInstruction {
request: directory::create_identity_authentication_request(
&context.client_info,
&context.config.directory_server_url,
&context.flavor,
public_key,
),
}),
)
}
fn poll_directory_challenge(
context: &CreateIdentityContext,
state: DirectoryChallengeState,
) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> { // Ensure the caller provided the response let Some(response) = state.response else { return Err(CspE2eProtocolError::InvalidState(formatcp!( "{} result was not provided for '{}' state",
CreateIdentityResponse::NAME,
State::DIRECTORY_CHALLENGE,
)));
};
// Handle the authentication challenge and provide the final request to create an identity let authentication = directory::handle_authentication_challenge(&state.client_key, response.result)?; let public_key = state.client_key.public_key();
info!("Creating identity");
Ok(( Self::DirectoryCreate(DirectoryCreateState {
client_key: state.client_key,
response: None,
}),
CreateIdentityLoop::Instruction(CreateIdentityInstruction {
request: directory::create_identity_request(
&context.client_info,
&context.config.directory_server_url,
&context.flavor,
authentication,
public_key,
),
}),
))
}
fn poll_directory_create(
context: &CreateIdentityContext,
state: DirectoryCreateState,
) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> { // Ensure the caller provided the response let Some(response) = state.response else { return Err(CspE2eProtocolError::InvalidState(formatcp!( "{} result was not provided for '{}' state",
CreateIdentityResponse::NAME,
State::DIRECTORY_CREATE,
)));
};
// Handle the result let identity_info = directory::handle_create_identity_result(response.result)?;
info!(identity = ?identity_info.identity, "Identity created"); match &context.flavor {
Flavor::Consumer => Ok(( Self::Done,
CreateIdentityLoop::Done(CreateIdentityResult {
identity: identity_info.identity,
client_key: state.client_key,
server_group: identity_info.server_group,
}),
)),
// Request an authentication challenge to update the work properties. // // Note: This is necessary for work identities to appear in the work cockpit.
Flavor::Work(work_context) => {
debug!("Requesting challenge to update work properties"); let instruction = CreateIdentityLoop::Instruction(CreateIdentityInstruction {
request: directory::update_work_properties_authentication_request(
&context.client_info,
&context.config.directory_server_url,
work_context,
identity_info.identity,
),
});
Ok(( Self::RegisterWorkChallenge(RegisterWorkChallengeState {
client_key: state.client_key,
identity_info,
response: None,
}),
instruction,
))
},
}
}
fn poll_register_work_challenge(
context: &CreateIdentityContext,
state: RegisterWorkChallengeState,
) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> { // Ensure the caller provided the response let Some(response) = state.response else { return Err(CspE2eProtocolError::InvalidState(formatcp!( "{} result was not provided for '{}' state",
CreateIdentityResponse::NAME,
State::REGISTER_WORK_CHALLENGE,
)));
};
// Ensure we're in a work context let Flavor::Work(work_context) = &context.flavor else { let message = "Work context missing";
error!(message); return Err(CspE2eProtocolError::InternalError(message.into()));
};
// Handle the result let authentication = directory::handle_authentication_challenge(&state.client_key, response.result)?;
info!("Registering as work identity"); let instruction = CreateIdentityLoop::Instruction(CreateIdentityInstruction {
request: directory::update_work_properties_request(
&context.client_info,
&context.config.directory_server_url,
work_context,
state.identity_info.identity,
authentication,
),
});
Ok((
State::RegisterWork(RegisterWorkState {
client_key: state.client_key,
identity_info: state.identity_info,
response: None,
}),
instruction,
))
}
fn poll_register_work(
state: RegisterWorkState,
) -> Result<(Self, CreateIdentityLoop), CspE2eProtocolError> { // Ensure the caller provided the response let Some(response) = state.response else { return Err(CspE2eProtocolError::InvalidState(formatcp!( "{} result was not provided for '{}' state",
CreateIdentityResponse::NAME,
State::REGISTER_WORK,
)));
};
// Handle the result
directory::handle_update_work_properties_result(response.result)?;
info!("Registered as work identity");
Ok(( Self::Done,
CreateIdentityLoop::Done(CreateIdentityResult {
identity: state.identity_info.identity,
client_key: state.client_key,
server_group: state.identity_info.server_group,
}),
))
}
}
/// Task for creating a new identity. #[derive(Debug, Name)] pubstruct CreateIdentityTask {
state: State,
} impl CreateIdentityTask { /// Create a new task for creating a new identity. #[must_use] #[expect(clippy::new_without_default, reason = "Task pattern")] pubfn new() -> Self { Self {
state: State::Init(InitState {
client_key: ClientKey::random(),
}),
}
}
/// Poll to advance the state. /// /// # Errors /// /// Returns [`CspE2eProtocolError`] for all possible reasons. #[tracing::instrument(skip_all, fields(?self))] pubfn poll(
&mutself,
context: &CreateIdentityContext,
) -> Result<CreateIdentityLoop, CspE2eProtocolError> { let result = match mem::replace(
&mutself.state,
State::Error(CspE2eProtocolError::InvalidState(formatcp!( "{} in a transitional state",
CreateIdentityTask::NAME
))),
) {
State::Error(error) => Err(error),
State::Init(state) => Ok(State::poll_init(context, state)),
State::DirectoryChallenge(state) => State::poll_directory_challenge(context, state),
State::DirectoryCreate(state) => State::poll_directory_create(context, state),
State::RegisterWorkChallenge(state) => State::poll_register_work_challenge(context, state),
State::RegisterWork(state) => State::poll_register_work(state),
State::Done => Err(CspE2eProtocolError::InvalidState(formatcp!( "{} already done",
CreateIdentityTask::NAME
))),
}; match result {
Ok((state, instruction)) => { self.state = state;
debug!(state = ?self.state, "Changed state");
Ok(instruction)
},
Err(error) => { self.state = State::Error(error.clone());
debug!(state = ?self.state, "Changed state to error");
Err(error)
},
}
}
/// Possible results after handling a [`CreateIdentityInstruction`]. /// /// # Errors /// /// Returns [`CspE2eProtocolError`] for all possible reasons. #[tracing::instrument(skip_all, fields(?self))] pubfn response(&mutself, response: CreateIdentityResponse) -> Result<(), CspE2eProtocolError> { match &mutself.state {
State::DirectoryChallenge(state) => { let _ = state.response.insert(response);
Ok(())
},
State::DirectoryCreate(state) => { let _ = state.response.insert(response);
Ok(())
},
State::RegisterWorkChallenge(state) => { let _ = state.response.insert(response);
Ok(())
},
State::RegisterWork(state) => { let _ = state.response.insert(response);
Ok(())
},
_ => Err(CspE2eProtocolError::InvalidState(formatcp!( "Must be in any of the following states: {}, {}, {}, {}",
State::DIRECTORY_CHALLENGE,
State::DIRECTORY_CREATE,
State::REGISTER_WORK,
State::REGISTER_WORK_CHALLENGE,
))),
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.