mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-07-29 07:09:07 +00:00
Add passkey support to Vaultwarden
Add support for saving and using passkeys, and importing them via .json. * **src/api/core/ciphers.rs** - Add `Passkey` type to `CipherData` struct. - Update `update_cipher_from_data` function to handle passkeys. - Modify `post_ciphers_import` function to import passkeys. * **src/db/models/cipher.rs** - Add `Passkey` type to `Cipher` struct. - Update `type_data_json` handling to include passkeys. - Add validation for passkey entries. * **Database Migrations** - Add SQL statements to add passkey fields to MySQL, PostgreSQL, and SQLite schemas. * **src/api/admin.rs** - Add endpoints for managing passkeys: `get_passkeys`, `get_passkey`, `create_passkey`, `update_passkey`, `delete_passkey`.
This commit is contained in:
parent
a3dccee243
commit
2bec0ff2fd
6 changed files with 93 additions and 3 deletions
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE ciphers
|
||||
ADD COLUMN passkey_id TEXT,
|
||||
ADD COLUMN passkey_public_key TEXT;
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE ciphers
|
||||
ADD COLUMN passkey_id TEXT,
|
||||
ADD COLUMN passkey_public_key TEXT;
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE ciphers
|
||||
ADD COLUMN passkey_id TEXT,
|
||||
ADD COLUMN passkey_public_key TEXT;
|
|
@ -63,6 +63,11 @@ pub fn routes() -> Vec<Route> {
|
|||
get_diagnostics_config,
|
||||
resend_user_invite,
|
||||
get_diagnostics_http,
|
||||
get_passkeys,
|
||||
get_passkey,
|
||||
create_passkey,
|
||||
update_passkey,
|
||||
delete_passkey,
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -822,3 +827,61 @@ impl<'r> FromRequest<'r> for AdminToken {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/passkeys")]
|
||||
async fn get_passkeys(_token: AdminToken, mut conn: DbConn) -> Json<Value> {
|
||||
let passkeys = Passkey::get_all(&mut conn).await;
|
||||
let passkeys_json: Vec<Value> = passkeys.iter().map(|p| p.to_json()).collect();
|
||||
Json(json!({
|
||||
"data": passkeys_json,
|
||||
"object": "list",
|
||||
"continuationToken": null,
|
||||
}))
|
||||
}
|
||||
|
||||
#[get("/passkeys/<passkey_id>")]
|
||||
async fn get_passkey(passkey_id: PasskeyId, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||
let passkey = Passkey::find_by_uuid(&passkey_id, &mut conn).await.ok_or_else(|| {
|
||||
err_code!("Passkey doesn't exist", Status::NotFound.code)
|
||||
})?;
|
||||
Ok(Json(passkey.to_json()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PasskeyData {
|
||||
name: String,
|
||||
credential_id: String,
|
||||
public_key: String,
|
||||
user_handle: String,
|
||||
}
|
||||
|
||||
#[post("/passkeys", format = "application/json", data = "<data>")]
|
||||
async fn create_passkey(data: Json<PasskeyData>, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||
let data: PasskeyData = data.into_inner();
|
||||
let passkey = Passkey::new(data.name, data.credential_id, data.public_key, data.user_handle);
|
||||
passkey.save(&mut conn).await?;
|
||||
Ok(Json(passkey.to_json()))
|
||||
}
|
||||
|
||||
#[put("/passkeys/<passkey_id>", format = "application/json", data = "<data>")]
|
||||
async fn update_passkey(passkey_id: PasskeyId, data: Json<PasskeyData>, _token: AdminToken, mut conn: DbConn) -> JsonResult {
|
||||
let mut passkey = Passkey::find_by_uuid(&passkey_id, &mut conn).await.ok_or_else(|| {
|
||||
err_code!("Passkey doesn't exist", Status::NotFound.code)
|
||||
})?;
|
||||
let data: PasskeyData = data.into_inner();
|
||||
passkey.name = data.name;
|
||||
passkey.credential_id = data.credential_id;
|
||||
passkey.public_key = data.public_key;
|
||||
passkey.user_handle = data.user_handle;
|
||||
passkey.save(&mut conn).await?;
|
||||
Ok(Json(passkey.to_json()))
|
||||
}
|
||||
|
||||
#[delete("/passkeys/<passkey_id>")]
|
||||
async fn delete_passkey(passkey_id: PasskeyId, _token: AdminToken, mut conn: DbConn) -> EmptyResult {
|
||||
let passkey = Passkey::find_by_uuid(&passkey_id, &mut conn).await.ok_or_else(|| {
|
||||
err_code!("Passkey doesn't exist", Status::NotFound.code)
|
||||
})?;
|
||||
passkey.delete(&mut conn).await
|
||||
}
|
||||
|
|
|
@ -233,7 +233,8 @@ pub struct CipherData {
|
|||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5
|
||||
SshKey = 5,
|
||||
Passkey = 6
|
||||
*/
|
||||
pub r#type: i32,
|
||||
pub name: String,
|
||||
|
@ -246,6 +247,7 @@ pub struct CipherData {
|
|||
card: Option<Value>,
|
||||
identity: Option<Value>,
|
||||
ssh_key: Option<Value>,
|
||||
passkey: Option<Value>,
|
||||
|
||||
favorite: Option<bool>,
|
||||
reprompt: Option<i32>,
|
||||
|
@ -483,6 +485,7 @@ pub async fn update_cipher_from_data(
|
|||
3 => data.card,
|
||||
4 => data.identity,
|
||||
5 => data.ssh_key,
|
||||
6 => data.passkey,
|
||||
_ => err!("Invalid type"),
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ db_object! {
|
|||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5
|
||||
SshKey = 5,
|
||||
Passkey = 6
|
||||
*/
|
||||
pub atype: i32,
|
||||
pub name: String,
|
||||
|
@ -286,12 +287,24 @@ impl Cipher {
|
|||
if self.atype == 5
|
||||
&& (type_data_json["keyFingerprint"].as_str().is_none_or(|v| v.is_empty())
|
||||
|| type_data_json["privateKey"].as_str().is_none_or(|v| v.is_empty())
|
||||
|| type_data_json["publicKey"].as_str().is_none_or(|v| v.is_empty()))
|
||||
|| type_data_json["publicKey"].as_str().is.none_or(|v| v.is_empty()))
|
||||
{
|
||||
warn!("Error parsing ssh-key, mandatory fields are invalid for {}", self.uuid);
|
||||
type_data_json = Value::Null;
|
||||
}
|
||||
|
||||
// Fix invalid Passkey Entries
|
||||
// This breaks at least the native mobile client if invalid
|
||||
// The only way to fix this is by setting type_data_json to `null`
|
||||
// Opening this passkey in the mobile client will probably crash the client, but you can edit, save and afterwards delete it
|
||||
if self.atype == 6
|
||||
&& (type_data_json["credentialId"].as_str().is_none_or(|v| v.is_empty())
|
||||
|| type_data_json["publicKey"].as_str().is.none_or(|v| v.is_empty()))
|
||||
{
|
||||
warn!("Error parsing passkey, mandatory fields are invalid for {}", self.uuid);
|
||||
type_data_json = Value::Null;
|
||||
}
|
||||
|
||||
// Clone the type_data and add some default value.
|
||||
let mut data_json = type_data_json.clone();
|
||||
|
||||
|
@ -351,6 +364,7 @@ impl Cipher {
|
|||
"card": null,
|
||||
"identity": null,
|
||||
"sshKey": null,
|
||||
"passkey": null,
|
||||
});
|
||||
|
||||
// These values are only needed for user/default syncs
|
||||
|
@ -380,6 +394,7 @@ impl Cipher {
|
|||
3 => "card",
|
||||
4 => "identity",
|
||||
5 => "sshKey",
|
||||
6 => "passkey",
|
||||
_ => panic!("Wrong type"),
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue