mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-07-29 23:29:09 +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,
|
get_diagnostics_config,
|
||||||
resend_user_invite,
|
resend_user_invite,
|
||||||
get_diagnostics_http,
|
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,
|
SecureNote = 2,
|
||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4,
|
Identity = 4,
|
||||||
SshKey = 5
|
SshKey = 5,
|
||||||
|
Passkey = 6
|
||||||
*/
|
*/
|
||||||
pub r#type: i32,
|
pub r#type: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -246,6 +247,7 @@ pub struct CipherData {
|
||||||
card: Option<Value>,
|
card: Option<Value>,
|
||||||
identity: Option<Value>,
|
identity: Option<Value>,
|
||||||
ssh_key: Option<Value>,
|
ssh_key: Option<Value>,
|
||||||
|
passkey: Option<Value>,
|
||||||
|
|
||||||
favorite: Option<bool>,
|
favorite: Option<bool>,
|
||||||
reprompt: Option<i32>,
|
reprompt: Option<i32>,
|
||||||
|
@ -483,6 +485,7 @@ pub async fn update_cipher_from_data(
|
||||||
3 => data.card,
|
3 => data.card,
|
||||||
4 => data.identity,
|
4 => data.identity,
|
||||||
5 => data.ssh_key,
|
5 => data.ssh_key,
|
||||||
|
6 => data.passkey,
|
||||||
_ => err!("Invalid type"),
|
_ => err!("Invalid type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ db_object! {
|
||||||
SecureNote = 2,
|
SecureNote = 2,
|
||||||
Card = 3,
|
Card = 3,
|
||||||
Identity = 4,
|
Identity = 4,
|
||||||
SshKey = 5
|
SshKey = 5,
|
||||||
|
Passkey = 6
|
||||||
*/
|
*/
|
||||||
pub atype: i32,
|
pub atype: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -286,12 +287,24 @@ impl Cipher {
|
||||||
if self.atype == 5
|
if self.atype == 5
|
||||||
&& (type_data_json["keyFingerprint"].as_str().is_none_or(|v| v.is_empty())
|
&& (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["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);
|
warn!("Error parsing ssh-key, mandatory fields are invalid for {}", self.uuid);
|
||||||
type_data_json = Value::Null;
|
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.
|
// Clone the type_data and add some default value.
|
||||||
let mut data_json = type_data_json.clone();
|
let mut data_json = type_data_json.clone();
|
||||||
|
|
||||||
|
@ -351,6 +364,7 @@ impl Cipher {
|
||||||
"card": null,
|
"card": null,
|
||||||
"identity": null,
|
"identity": null,
|
||||||
"sshKey": null,
|
"sshKey": null,
|
||||||
|
"passkey": null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// These values are only needed for user/default syncs
|
// These values are only needed for user/default syncs
|
||||||
|
@ -380,6 +394,7 @@ impl Cipher {
|
||||||
3 => "card",
|
3 => "card",
|
||||||
4 => "identity",
|
4 => "identity",
|
||||||
5 => "sshKey",
|
5 => "sshKey",
|
||||||
|
6 => "passkey",
|
||||||
_ => panic!("Wrong type"),
|
_ => panic!("Wrong type"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue