mirror of
https://github.com/dani-garcia/vaultwarden.wiki.git
synced 2026-10-23 01:48:09 +03:00
User key id (#7693)
Co-authored-by: Timshel <timshel@users.noreply.github.com>
This commit is contained in:
1 parent
8719f86fcf
commit
9c8aa2359f
13 files changed
+74
-5
No files matched your search
Whitespace-only changes.
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN key_id TEXT;
|
||||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN key_id TEXT;
|
||||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN key_id TEXT;
|
||||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD COLUMN key_id TEXT;
|
||||||
@@ -21,8 +21,9 @@ use crate::{
|
|||||||
DbConn, DbPool,
|
DbConn, DbPool,
|
||||||
models::{
|
models::{
|
||||||
AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest,
|
AuthRequest, AuthRequestId, Cipher, CipherId, Device, DeviceId, DeviceType, DeviceWithAuthRequest,
|
||||||
EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, Membership, MembershipId,
|
EmergencyAccess, EmergencyAccessId, EventType, Folder, FolderId, Invitation, KeyId, Membership,
|
||||||
OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId, UserKdfType,
|
MembershipId, OrgPolicy, OrgPolicyType, Organization, OrganizationId, Send, SendId, User, UserId,
|
||||||
|
UserKdfType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mail,
|
mail,
|
||||||
@@ -46,6 +47,7 @@ pub fn routes() -> Vec<rocket::Route> {
|
|||||||
post_set_password,
|
post_set_password,
|
||||||
post_kdf,
|
post_kdf,
|
||||||
post_rotatekey,
|
post_rotatekey,
|
||||||
|
post_user_key,
|
||||||
post_sstamp,
|
post_sstamp,
|
||||||
post_email_token,
|
post_email_token,
|
||||||
post_email,
|
post_email,
|
||||||
@@ -1025,6 +1027,23 @@ async fn post_rotatekey(data: Json<KeyData>, headers: Headers, conn: DbConn, nt:
|
|||||||
save_result
|
save_result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct KeyIdData {
|
||||||
|
user_key_id: KeyId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/accounts/key-management/user-key-id", data = "<data>")]
|
||||||
|
async fn post_user_key(data: Json<KeyIdData>, headers: Headers, conn: DbConn) -> EmptyResult {
|
||||||
|
let mut user = headers.user;
|
||||||
|
if user.key_id.is_some() {
|
||||||
|
err_code!("Unexpected data", Status::UnprocessableEntity.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.key_id = Some(data.into_inner().user_key_id);
|
||||||
|
user.save(&conn).await
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/accounts/security-stamp", data = "<data>")]
|
#[post("/accounts/security-stamp", data = "<data>")]
|
||||||
async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
async fn post_sstamp(data: Json<PasswordOrOtpData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> EmptyResult {
|
||||||
let data: PasswordOrOtpData = data.into_inner();
|
let data: PasswordOrOtpData = data.into_inner();
|
||||||
|
|||||||
+23
-2
@@ -6,6 +6,7 @@ use rocket::{
|
|||||||
Route,
|
Route,
|
||||||
form::{Form, FromForm},
|
form::{Form, FromForm},
|
||||||
fs::TempFile,
|
fs::TempFile,
|
||||||
|
http::Status,
|
||||||
serde::json::Json,
|
serde::json::Json,
|
||||||
};
|
};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -21,8 +22,8 @@ use crate::{
|
|||||||
DbConn, DbPool,
|
DbConn, DbPool,
|
||||||
models::{
|
models::{
|
||||||
Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup,
|
Archive, Attachment, AttachmentId, Cipher, CipherId, Collection, CollectionCipher, CollectionGroup,
|
||||||
CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, Membership,
|
CollectionId, CollectionUser, EventType, Favorite, Folder, FolderCipher, FolderId, Group, KeyId,
|
||||||
MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId,
|
Membership, MembershipType, OrgPolicy, OrgPolicyType, OrganizationId, RepromptType, Send, UserId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
util::{NumberOrString, deser_opt_nonempty_str, save_temp_file},
|
util::{NumberOrString, deser_opt_nonempty_str, save_temp_file},
|
||||||
@@ -198,6 +199,7 @@ async fn sync(data: SyncData, headers: Headers, client_version: Option<ClientVer
|
|||||||
"sends": sends_json,
|
"sends": sends_json,
|
||||||
"userDecryption": {
|
"userDecryption": {
|
||||||
"masterPasswordUnlock": master_password_unlock,
|
"masterPasswordUnlock": master_password_unlock,
|
||||||
|
"userKeyId": headers.user.key_id,
|
||||||
},
|
},
|
||||||
"object": "sync"
|
"object": "sync"
|
||||||
})))
|
})))
|
||||||
@@ -260,6 +262,10 @@ pub struct CipherData {
|
|||||||
|
|
||||||
key: Option<String>,
|
key: Option<String>,
|
||||||
|
|
||||||
|
pub encrypted_for: UserId, // Added in web-v2025.6.0
|
||||||
|
// Added in web-v2025.8.1, Optional for compat
|
||||||
|
pub encrypted_by_key_id: Option<KeyId>,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Login = 1,
|
Login = 1,
|
||||||
SecureNote = 2,
|
SecureNote = 2,
|
||||||
@@ -333,6 +339,10 @@ async fn post_ciphers_create(
|
|||||||
) -> JsonResult {
|
) -> JsonResult {
|
||||||
let mut data: ShareCipherData = data.into_inner();
|
let mut data: ShareCipherData = data.into_inner();
|
||||||
|
|
||||||
|
if data.cipher.encrypted_for != headers.user.uuid {
|
||||||
|
err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
|
||||||
|
}
|
||||||
|
|
||||||
// This check is usually only needed in update_cipher_from_data(), but we
|
// This check is usually only needed in update_cipher_from_data(), but we
|
||||||
// need it here as well to avoid creating an empty cipher in the call to
|
// need it here as well to avoid creating an empty cipher in the call to
|
||||||
// cipher.save() below.
|
// cipher.save() below.
|
||||||
@@ -362,6 +372,17 @@ async fn post_ciphers_create(
|
|||||||
async fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
async fn post_ciphers(data: Json<CipherData>, headers: Headers, conn: DbConn, nt: Notify<'_>) -> JsonResult {
|
||||||
let mut data: CipherData = data.into_inner();
|
let mut data: CipherData = data.into_inner();
|
||||||
|
|
||||||
|
if data.encrypted_for != headers.user.uuid {
|
||||||
|
err_code!("Invalid user cipher", Status::UnprocessableEntity.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cipher_key_id) = &data.encrypted_by_key_id
|
||||||
|
&& let Some(user_key_id) = &headers.user.key_id
|
||||||
|
&& cipher_key_id != user_key_id
|
||||||
|
{
|
||||||
|
err_code!("Invalid key cipher", Status::UnprocessableEntity.code);
|
||||||
|
}
|
||||||
|
|
||||||
// The web/browser clients set this field to null as expected, but the
|
// The web/browser clients set this field to null as expected, but the
|
||||||
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
|
// mobile clients seem to set the invalid value `0001-01-01T00:00:00`,
|
||||||
// which results in a warning message being logged. This field isn't
|
// which results in a warning message being logged. This field isn't
|
||||||
|
|||||||
@@ -39,4 +39,4 @@ pub use self::sso_auth::{OIDCAuthenticatedUser, OIDCCodeResponseError, SsoAuth};
|
|||||||
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
pub use self::two_factor::{TwoFactor, TwoFactorType};
|
||||||
pub use self::two_factor_duo_context::TwoFactorDuoContext;
|
pub use self::two_factor_duo_context::TwoFactorDuoContext;
|
||||||
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
pub use self::two_factor_incomplete::TwoFactorIncomplete;
|
||||||
pub use self::user::{Invitation, SsoUser, User, UserId, UserKdfType, UserStampException};
|
pub use self::user::{Invitation, KeyId, SsoUser, User, UserId, UserKdfType, UserStampException};
|
||||||
@@ -69,6 +69,8 @@ pub struct User {
|
|||||||
pub avatar_color: Option<String>,
|
pub avatar_color: Option<String>,
|
||||||
|
|
||||||
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
|
pub external_id: Option<String>, // Todo: Needs to be removed in the future, this is not used anymore.
|
||||||
|
|
||||||
|
pub key_id: Option<KeyId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Identifiable, Queryable, Insertable)]
|
#[derive(Identifiable, Queryable, Insertable)]
|
||||||
@@ -154,6 +156,8 @@ impl User {
|
|||||||
avatar_color: None,
|
avatar_color: None,
|
||||||
|
|
||||||
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
|
external_id: None, // Todo: Needs to be removed in the future, this is not used anymore.
|
||||||
|
|
||||||
|
key_id: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +531,26 @@ impl Invitation {
|
|||||||
#[from(forward)]
|
#[from(forward)]
|
||||||
pub struct UserId(String);
|
pub struct UserId(String);
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone,
|
||||||
|
Debug,
|
||||||
|
DieselNewType,
|
||||||
|
FromForm,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
AsRef,
|
||||||
|
Deref,
|
||||||
|
Display,
|
||||||
|
From,
|
||||||
|
UuidFromParam,
|
||||||
|
)]
|
||||||
|
#[deref(forward)]
|
||||||
|
#[from(forward)]
|
||||||
|
pub struct KeyId(String);
|
||||||
|
|
||||||
impl SsoUser {
|
impl SsoUser {
|
||||||
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
pub async fn save(&self, conn: &DbConn) -> EmptyResult {
|
||||||
db_run! { conn:
|
db_run! { conn:
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ table! {
|
|||||||
api_key -> Nullable<Text>,
|
api_key -> Nullable<Text>,
|
||||||
avatar_color -> Nullable<Text>,
|
avatar_color -> Nullable<Text>,
|
||||||
external_id -> Nullable<Text>,
|
external_id -> Nullable<Text>,
|
||||||
|
key_id -> Nullable<Text>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user