1use crate::{
5 crypto::aes::EncryptedMasterKey,
6 internal::{
7 self,
8 rest::{
9 self,
10 json::{Base64Standard, PublicKey},
11 Authorization, IronCoreRequest,
12 },
13 user_api::{DeviceName, Jwt, UserId},
14 IronOxideErr, RequestAuth, RequestErrorCode,
15 },
16};
17use serde::{Deserialize, Serialize};
18use std::convert::TryFrom;
19use time::OffsetDateTime;
20
21use crate::internal::auth_v2::AuthV2Builder;
22#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
23pub struct EncryptedPrivateKey(#[serde(with = "Base64Standard")] pub Vec<u8>);
24
25impl From<EncryptedMasterKey> for EncryptedPrivateKey {
26 fn from(enc_master_key: EncryptedMasterKey) -> Self {
27 EncryptedPrivateKey(enc_master_key.bytes().to_vec())
28 }
29}
30
31impl From<EncryptedPrivateKey> for internal::user_api::EncryptedPrivateKey {
32 fn from(resp_encrypt_priv_key: EncryptedPrivateKey) -> Self {
33 internal::user_api::EncryptedPrivateKey(resp_encrypt_priv_key.0)
34 }
35}
36
37impl TryFrom<EncryptedPrivateKey> for EncryptedMasterKey {
38 type Error = IronOxideErr;
39
40 fn try_from(value: EncryptedPrivateKey) -> Result<Self, Self::Error> {
41 EncryptedMasterKey::new_from_slice(&value.0)
42 }
43}
44
45pub mod user_verify {
46 use crate::internal::user_api::UserResult;
47 use std::convert::TryInto;
48
49 use super::*;
50
51 #[derive(Debug, PartialEq, Eq, Deserialize)]
52 #[serde(rename_all = "camelCase")]
53 pub struct UserVerifyResponse {
54 pub(crate) id: String,
55 status: usize,
56 pub(crate) segment_id: usize,
57 pub(crate) user_private_key: EncryptedPrivateKey,
58 pub(crate) user_master_public_key: PublicKey,
59 pub(crate) needs_rotation: bool,
60 }
61
62 pub async fn user_verify(
63 jwt: &Jwt,
64 request: &IronCoreRequest,
65 ) -> Result<Option<UserVerifyResponse>, IronOxideErr> {
66 request
67 .get_with_empty_result_jwt_auth(
68 "users/verify?returnKeys=true",
69 RequestErrorCode::UserVerify,
70 &Authorization::JwtAuth(jwt),
71 )
72 .await
73 }
74
75 impl TryFrom<UserVerifyResponse> for UserResult {
76 type Error = IronOxideErr;
77
78 fn try_from(body: UserVerifyResponse) -> Result<Self, Self::Error> {
79 Ok(UserResult {
80 account_id: body.id.try_into()?,
81 segment_id: body.segment_id,
82 user_public_key: body.user_master_public_key.try_into()?,
83 needs_rotation: body.needs_rotation,
84 })
85 }
86 }
87
88 #[cfg(test)]
89 mod tests {
90 use super::*;
91 use crate::internal;
92 use galvanic_assert::{matchers::*, *};
93 use recrypt::prelude::*;
94
95 #[test]
96 fn user_verify_resp_to_result() -> Result<(), IronOxideErr> {
97 let r = recrypt::api::Recrypt::new();
98 let (_, r_pub) = r.generate_key_pair()?;
99
100 let priv_key: EncryptedPrivateKey = EncryptedPrivateKey(vec![1u8; 60]);
102 let pub_key: PublicKey = r_pub.into();
103
104 let t_account_id: UserId = UserId::unsafe_from_string("valid_user_id".to_string());
105 let t_segment_id: usize = 200;
106 let t_user_public_key: internal::PublicKey = r_pub.into();
107 let t_needs_rotation = true;
108
109 let resp = UserVerifyResponse {
110 id: t_account_id.id().to_string(),
111 status: 100,
112 segment_id: t_segment_id,
113 user_private_key: priv_key,
114 user_master_public_key: pub_key,
115 needs_rotation: t_needs_rotation,
116 };
117 let result: UserResult = resp.try_into().unwrap();
118
119 assert_that!(
120 &result,
121 has_structure!(UserResult {
122 account_id: eq(t_account_id.clone()),
123 segment_id: eq(t_segment_id),
124 user_public_key: eq(t_user_public_key.clone()),
125 needs_rotation: eq(t_needs_rotation)
126 })
127 );
128 Ok(())
129 }
130 }
131}
132
133pub mod user_get {
134 use super::*;
135 use crate::internal::group_api::GroupId;
136
137 #[derive(Debug, PartialEq, Eq, Deserialize)]
138 #[serde(rename_all = "camelCase")]
139 pub struct CurrentUserResponse {
140 pub(in crate::internal) current_key_id: u64,
141 pub(in crate::internal) id: UserId,
142 pub(in crate::internal) status: usize,
143 pub(in crate::internal) segment_id: usize,
144 pub(in crate::internal) user_master_public_key: PublicKey,
145 pub(in crate::internal) user_private_key: EncryptedPrivateKey,
146 pub(in crate::internal) needs_rotation: bool,
147 pub(in crate::internal) groups_needing_rotation: Vec<GroupId>,
148 }
149
150 pub async fn get_curr_user(auth: &RequestAuth) -> Result<CurrentUserResponse, IronOxideErr> {
151 auth.request
152 .get(
153 "users/current",
154 RequestErrorCode::UserGetCurrent,
155 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
156 )
157 .await
158 }
159}
160
161pub mod user_update_private_key {
163 use super::*;
164 use internal::{rest::json::AugmentationFactor, user_api::UserUpdatePrivateKeyResult};
165
166 #[derive(Debug, Serialize)]
167 #[serde(rename_all = "camelCase")]
168 pub struct UserUpdatePrivateKey {
169 user_private_key: EncryptedPrivateKey,
170 augmentation_factor: AugmentationFactor,
171 }
172
173 #[derive(Debug, PartialEq, Eq, Deserialize)]
174 #[serde(rename_all = "camelCase")]
175 pub struct UserUpdatePrivateKeyResponse {
176 current_key_id: u64,
177 user_private_key: EncryptedPrivateKey,
178 needs_rotation: bool,
179 }
180
181 impl From<UserUpdatePrivateKeyResponse> for UserUpdatePrivateKeyResult {
182 fn from(resp: UserUpdatePrivateKeyResponse) -> Self {
183 UserUpdatePrivateKeyResult {
185 user_master_private_key: resp.user_private_key.into(),
186 needs_rotation: resp.needs_rotation,
187 }
188 }
189 }
190
191 pub async fn update_private_key(
192 auth: &RequestAuth,
193 user_id: UserId,
194 user_key_id: u64,
195 new_encrypted_private_key: EncryptedPrivateKey,
196 augmenting_key: AugmentationFactor,
197 ) -> Result<UserUpdatePrivateKeyResponse, IronOxideErr> {
198 auth.request
199 .put(
200 &format!(
201 "users/{}/keys/{}",
202 rest::url_encode(user_id.id()),
203 user_key_id
204 ),
205 &UserUpdatePrivateKey {
206 user_private_key: new_encrypted_private_key,
207 augmentation_factor: augmenting_key,
208 },
209 RequestErrorCode::UserKeyUpdate,
210 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
211 )
212 .await
213 }
214}
215
216pub mod user_create {
217 use crate::internal::{user_api::UserCreateResult, TryInto};
218
219 use super::*;
220
221 #[derive(Debug, PartialEq, Eq, Deserialize)]
222 #[serde(rename_all = "camelCase")]
223 pub struct UserCreateResponse {
224 id: String,
225 status: usize,
226 segment_id: usize,
227 pub user_private_key: EncryptedPrivateKey,
228 pub user_master_public_key: PublicKey,
229 needs_rotation: bool,
230 }
231
232 #[derive(Debug, Serialize)]
233 #[serde(rename_all = "camelCase")]
234 struct UserCreateReq {
235 user_public_key: PublicKey,
236 user_private_key: EncryptedPrivateKey,
237 needs_rotation: bool,
238 }
239
240 pub async fn user_create(
241 jwt: &Jwt,
242 user_public_key: PublicKey,
243 encrypted_user_private_key: EncryptedPrivateKey,
244 needs_rotation: bool,
245 request: IronCoreRequest,
246 ) -> Result<UserCreateResponse, IronOxideErr> {
247 let req_body = UserCreateReq {
248 user_private_key: encrypted_user_private_key,
249 user_public_key,
250 needs_rotation,
251 };
252 request
253 .post_jwt_auth(
254 "users",
255 &req_body,
256 RequestErrorCode::UserCreate,
257 &Authorization::JwtAuth(jwt),
258 )
259 .await
260 }
261 impl TryFrom<UserCreateResponse> for UserCreateResult {
262 type Error = IronOxideErr;
263
264 fn try_from(resp: UserCreateResponse) -> Result<Self, Self::Error> {
265 Ok(UserCreateResult {
266 id: resp.id,
267 user_public_key: resp.user_master_public_key.try_into()?,
268 needs_rotation: resp.needs_rotation,
269 })
270 }
271 }
272}
273
274pub mod user_update {
275 use crate::internal::{user_api::UserCreateResult, TryInto};
276
277 use super::*;
278
279 #[derive(Debug, PartialEq, Eq, Deserialize)]
280 #[serde(rename_all = "camelCase")]
281 pub struct UserUpdateResponse {
282 id: String,
283 status: usize,
284 segment_id: usize,
285 pub user_private_key: EncryptedPrivateKey,
286 pub user_master_public_key: PublicKey,
287 needs_rotation: bool,
288 }
289
290 #[derive(Debug, Serialize)]
291 #[serde(rename_all = "camelCase")]
292 struct UserUpdateReq {
293 user_private_key: Option<EncryptedPrivateKey>,
294 }
295
296 pub async fn user_update(
297 auth: &RequestAuth,
298 user_id: &UserId,
299 encrypted_user_private_key: Option<EncryptedPrivateKey>,
300 ) -> Result<UserUpdateResponse, IronOxideErr> {
301 let req_body = UserUpdateReq {
302 user_private_key: encrypted_user_private_key,
303 };
304 auth.request
305 .put(
306 &format!("users/{}", rest::url_encode(user_id.id())),
307 &req_body,
308 RequestErrorCode::UserUpdate,
309 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
310 )
311 .await
312 }
313
314 impl TryFrom<UserUpdateResponse> for UserCreateResult {
315 type Error = IronOxideErr;
316
317 fn try_from(resp: UserUpdateResponse) -> Result<Self, Self::Error> {
318 Ok(UserCreateResult {
319 id: resp.id,
320 user_public_key: resp.user_master_public_key.try_into()?,
321 needs_rotation: resp.needs_rotation,
322 })
323 }
324 }
325}
326
327pub mod user_key_list {
328 use super::*;
329
330 #[derive(Deserialize)]
331 #[serde(rename_all = "camelCase")]
332 pub struct UserPublicKey {
333 pub id: String,
334 pub user_master_public_key: crate::internal::rest::json::PublicKey,
335 }
336 #[derive(Deserialize)]
337 pub struct UserKeyListResponse {
338 pub(crate) result: Vec<UserPublicKey>,
339 }
340
341 pub async fn user_key_list_request(
342 auth: &RequestAuth,
343 users: &[UserId],
344 ) -> Result<UserKeyListResponse, IronOxideErr> {
345 let user_ids: Vec<&str> = users.iter().map(UserId::id).collect();
346 if !user_ids.is_empty() {
347 auth.request
348 .get_with_query_params(
349 "users",
350 &[("id".into(), rest::url_encode(&user_ids.join(",")))],
351 RequestErrorCode::UserKeyList,
352 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
353 )
354 .await
355 } else {
356 Ok(UserKeyListResponse { result: vec![] })
357 }
358 }
359}
360
361pub mod device_add {
362 use crate::internal::{
363 rest::json::TransformKey,
364 user_api::{requests::PublicKey, DeviceAdd, DeviceId, Jwt},
365 };
366
367 use super::*;
368
369 #[derive(Debug, Serialize)]
370 #[serde(rename_all = "camelCase")]
371 pub struct DeviceAddReq {
372 pub timestamp: u64,
373 pub device: Device,
374 #[serde(with = "Base64Standard")]
375 pub signature: Vec<u8>,
376 pub user_public_key: PublicKey,
377 }
378
379 #[derive(Debug, Serialize)]
380 #[serde(rename_all = "camelCase")]
381 pub struct Device {
382 pub transform_key: TransformKey,
383 pub name: Option<DeviceName>,
384 }
385
386 #[derive(Debug, Deserialize)]
387 #[serde(rename_all = "camelCase")]
388 pub struct DeviceAddResponse {
389 #[serde(rename = "id")]
390 pub device_id: DeviceId,
391 pub name: Option<DeviceName>,
392 #[serde(with = "time::serde::rfc3339")]
393 pub created: OffsetDateTime,
394 #[serde(with = "time::serde::rfc3339")]
395 pub updated: OffsetDateTime,
396 }
397
398 pub(crate) async fn user_device_add(
399 jwt: &Jwt,
400 device_add: &DeviceAdd,
401 name: &Option<DeviceName>,
402 request: &IronCoreRequest,
403 ) -> Result<DeviceAddResponse, IronOxideErr> {
404 let req_body: DeviceAddReq = DeviceAddReq {
405 timestamp: rest::as_unix_timestamp_millis(device_add.signature_ts) as u64,
406 user_public_key: device_add.user_public_key.clone().into(),
407 signature: device_add.signature.clone().into(),
408 device: Device {
409 transform_key: device_add.transform_key.clone().into(),
410 name: name.clone(),
411 },
412 };
413 request
414 .post_jwt_auth(
415 "users/devices",
416 &req_body,
417 RequestErrorCode::UserDeviceAdd,
418 &Authorization::JwtAuth(jwt),
419 )
420 .await
421 }
422}
423
424pub mod device_list {
425 use time::OffsetDateTime;
426
427 use crate::internal::user_api::{DeviceId, DeviceName, UserDevice};
428
429 use super::*;
430
431 #[derive(Debug, PartialEq, Eq, Deserialize)]
432 #[serde(rename_all = "camelCase")]
433 pub struct DeviceListItem {
434 #[serde(rename = "id")]
435 device_id: DeviceId,
436 name: Option<DeviceName>,
437 #[serde(with = "time::serde::rfc3339")]
438 created: OffsetDateTime,
439 #[serde(with = "time::serde::rfc3339")]
440 updated: OffsetDateTime,
441 is_current_device: bool,
442 }
443
444 #[derive(Debug, PartialEq, Eq, Deserialize)]
445 pub struct DeviceListResponse {
446 pub(in crate::internal) result: Vec<DeviceListItem>,
447 }
448
449 pub async fn device_list(auth: &RequestAuth) -> Result<DeviceListResponse, IronOxideErr> {
450 auth.request
451 .get(
452 &format!("users/{}/devices", rest::url_encode(&auth.account_id().0)),
453 RequestErrorCode::UserDeviceList,
454 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
455 )
456 .await
457 }
458
459 impl From<DeviceListItem> for UserDevice {
460 fn from(resp: DeviceListItem) -> Self {
461 UserDevice {
462 id: resp.device_id,
463 name: resp.name,
464 created: resp.created,
465 last_updated: resp.updated,
466 is_current_device: resp.is_current_device,
467 }
468 }
469 }
470}
471
472pub mod device_delete {
473 use super::*;
474 use crate::{internal::user_api::DeviceId, IronOxideErr};
475
476 #[derive(Deserialize)]
477 pub struct DeviceDeleteResponse {
478 pub(crate) id: DeviceId,
479 }
480
481 pub async fn device_delete(
482 auth: &RequestAuth,
483 device_id: &DeviceId,
484 ) -> Result<DeviceDeleteResponse, IronOxideErr> {
485 auth.request
486 .delete_with_no_body(
487 &format!(
488 "users/{}/devices/{}",
489 rest::url_encode(&auth.account_id().0),
490 device_id.0
491 ),
492 RequestErrorCode::UserDeviceDelete,
493 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
494 )
495 .await
496 }
497
498 pub async fn device_delete_current(
499 auth: &RequestAuth,
500 ) -> Result<DeviceDeleteResponse, IronOxideErr> {
501 auth.request
502 .delete_with_no_body(
503 &format!(
504 "users/{}/devices/current",
505 rest::url_encode(&auth.account_id().0)
506 ),
507 RequestErrorCode::UserDeviceDelete,
508 AuthV2Builder::new(auth, OffsetDateTime::now_utc()),
509 )
510 .await
511 }
512}