mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-09-13 03:55:58 +03:00
Implement login-with-device
This commit is contained in:
committed by
Mathijs van Veluw
parent
e9ec3741ae
commit
8d7b3db33d
148
src/db/models/auth_request.rs
Normal file
148
src/db/models/auth_request.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use crate::crypto::ct_eq;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
db_object! {
|
||||
#[derive(Debug, Identifiable, Queryable, Insertable, AsChangeset, Deserialize, Serialize)]
|
||||
#[diesel(table_name = auth_requests)]
|
||||
#[diesel(treat_none_as_null = true)]
|
||||
#[diesel(primary_key(uuid))]
|
||||
pub struct AuthRequest {
|
||||
pub uuid: String,
|
||||
pub user_uuid: String,
|
||||
pub organization_uuid: Option<String>,
|
||||
|
||||
pub request_device_identifier: String,
|
||||
pub device_type: i32, // https://github.com/bitwarden/server/blob/master/src/Core/Enums/DeviceType.cs
|
||||
|
||||
pub request_ip: String,
|
||||
pub response_device_id: Option<String>,
|
||||
|
||||
pub access_code: String,
|
||||
pub public_key: String,
|
||||
|
||||
pub enc_key: String,
|
||||
|
||||
pub master_password_hash: String,
|
||||
pub approved: Option<bool>,
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub response_date: Option<NaiveDateTime>,
|
||||
|
||||
pub authentication_date: Option<NaiveDateTime>,
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthRequest {
|
||||
pub fn new(
|
||||
user_uuid: String,
|
||||
request_device_identifier: String,
|
||||
device_type: i32,
|
||||
request_ip: String,
|
||||
access_code: String,
|
||||
public_key: String,
|
||||
) -> Self {
|
||||
let now = Utc::now().naive_utc();
|
||||
|
||||
Self {
|
||||
uuid: crate::util::get_uuid(),
|
||||
user_uuid,
|
||||
organization_uuid: None,
|
||||
|
||||
request_device_identifier,
|
||||
device_type,
|
||||
request_ip,
|
||||
response_device_id: None,
|
||||
access_code,
|
||||
public_key,
|
||||
enc_key: String::new(),
|
||||
master_password_hash: String::new(),
|
||||
approved: None,
|
||||
creation_date: now,
|
||||
response_date: None,
|
||||
authentication_date: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::db::DbConn;
|
||||
|
||||
use crate::api::EmptyResult;
|
||||
use crate::error::MapResult;
|
||||
|
||||
impl AuthRequest {
|
||||
pub async fn save(&mut self, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! { conn:
|
||||
sqlite, mysql {
|
||||
match diesel::replace_into(auth_requests::table)
|
||||
.values(AuthRequestDb::to_db(self))
|
||||
.execute(conn)
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
// Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
|
||||
Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::ForeignKeyViolation, _)) => {
|
||||
diesel::update(auth_requests::table)
|
||||
.filter(auth_requests::uuid.eq(&self.uuid))
|
||||
.set(AuthRequestDb::to_db(self))
|
||||
.execute(conn)
|
||||
.map_res("Error auth_request")
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}.map_res("Error auth_request")
|
||||
}
|
||||
postgresql {
|
||||
let value = AuthRequestDb::to_db(self);
|
||||
diesel::insert_into(auth_requests::table)
|
||||
.values(&value)
|
||||
.on_conflict(auth_requests::uuid)
|
||||
.do_update()
|
||||
.set(&value)
|
||||
.execute(conn)
|
||||
.map_res("Error saving auth_request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_by_uuid(uuid: &str, conn: &mut DbConn) -> Option<Self> {
|
||||
db_run! {conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::uuid.eq(uuid))
|
||||
.first::<AuthRequestDb>(conn)
|
||||
.ok()
|
||||
.from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_by_user(user_uuid: &str, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::user_uuid.eq(user_uuid))
|
||||
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn find_created_before(dt: &NaiveDateTime, conn: &mut DbConn) -> Vec<Self> {
|
||||
db_run! {conn: {
|
||||
auth_requests::table
|
||||
.filter(auth_requests::creation_date.lt(dt))
|
||||
.load::<AuthRequestDb>(conn).expect("Error loading auth_requests").from_db()
|
||||
}}
|
||||
}
|
||||
|
||||
pub async fn delete(&self, conn: &mut DbConn) -> EmptyResult {
|
||||
db_run! { conn: {
|
||||
diesel::delete(auth_requests::table.filter(auth_requests::uuid.eq(&self.uuid)))
|
||||
.execute(conn)
|
||||
.map_res("Error deleting auth request")
|
||||
}}
|
||||
}
|
||||
|
||||
pub fn check_access_code(&self, access_code: &str) -> bool {
|
||||
ct_eq(&self.access_code, access_code)
|
||||
}
|
||||
|
||||
pub async fn purge_expired_auth_requests(conn: &mut DbConn) {
|
||||
let expiry_time = Utc::now().naive_utc() - chrono::Duration::minutes(5); //after 5 minutes, clients reject the request
|
||||
for auth_request in Self::find_created_before(&expiry_time, conn).await {
|
||||
auth_request.delete(conn).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
|
||||
use crate::{crypto, CONFIG};
|
||||
use core::fmt;
|
||||
|
||||
db_object! {
|
||||
#[derive(Identifiable, Queryable, Insertable, AsChangeset)]
|
||||
@@ -225,3 +226,90 @@ impl Device {
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum DeviceType {
|
||||
Android = 0,
|
||||
Ios = 1,
|
||||
ChromeExtension = 2,
|
||||
FirefoxExtension = 3,
|
||||
OperaExtension = 4,
|
||||
EdgeExtension = 5,
|
||||
WindowsDesktop = 6,
|
||||
MacOsDesktop = 7,
|
||||
LinuxDesktop = 8,
|
||||
ChromeBrowser = 9,
|
||||
FirefoxBrowser = 10,
|
||||
OperaBrowser = 11,
|
||||
EdgeBrowser = 12,
|
||||
IEBrowser = 13,
|
||||
UnknownBrowser = 14,
|
||||
AndroidAmazon = 15,
|
||||
Uwp = 16,
|
||||
SafariBrowser = 17,
|
||||
VivaldiBrowser = 18,
|
||||
VivaldiExtension = 19,
|
||||
SafariExtension = 20,
|
||||
Sdk = 21,
|
||||
Server = 22,
|
||||
}
|
||||
|
||||
impl fmt::Display for DeviceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DeviceType::Android => write!(f, "Android"),
|
||||
DeviceType::Ios => write!(f, "iOS"),
|
||||
DeviceType::ChromeExtension => write!(f, "Chrome Extension"),
|
||||
DeviceType::FirefoxExtension => write!(f, "Firefox Extension"),
|
||||
DeviceType::OperaExtension => write!(f, "Opera Extension"),
|
||||
DeviceType::EdgeExtension => write!(f, "Edge Extension"),
|
||||
DeviceType::WindowsDesktop => write!(f, "Windows Desktop"),
|
||||
DeviceType::MacOsDesktop => write!(f, "MacOS Desktop"),
|
||||
DeviceType::LinuxDesktop => write!(f, "Linux Desktop"),
|
||||
DeviceType::ChromeBrowser => write!(f, "Chrome Browser"),
|
||||
DeviceType::FirefoxBrowser => write!(f, "Firefox Browser"),
|
||||
DeviceType::OperaBrowser => write!(f, "Opera Browser"),
|
||||
DeviceType::EdgeBrowser => write!(f, "Edge Browser"),
|
||||
DeviceType::IEBrowser => write!(f, "Internet Explorer"),
|
||||
DeviceType::UnknownBrowser => write!(f, "Unknown Browser"),
|
||||
DeviceType::AndroidAmazon => write!(f, "Android Amazon"),
|
||||
DeviceType::Uwp => write!(f, "UWP"),
|
||||
DeviceType::SafariBrowser => write!(f, "Safari Browser"),
|
||||
DeviceType::VivaldiBrowser => write!(f, "Vivaldi Browser"),
|
||||
DeviceType::VivaldiExtension => write!(f, "Vivaldi Extension"),
|
||||
DeviceType::SafariExtension => write!(f, "Safari Extension"),
|
||||
DeviceType::Sdk => write!(f, "SDK"),
|
||||
DeviceType::Server => write!(f, "Server"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceType {
|
||||
pub fn from_i32(value: i32) -> DeviceType {
|
||||
match value {
|
||||
0 => DeviceType::Android,
|
||||
1 => DeviceType::Ios,
|
||||
2 => DeviceType::ChromeExtension,
|
||||
3 => DeviceType::FirefoxExtension,
|
||||
4 => DeviceType::OperaExtension,
|
||||
5 => DeviceType::EdgeExtension,
|
||||
6 => DeviceType::WindowsDesktop,
|
||||
7 => DeviceType::MacOsDesktop,
|
||||
8 => DeviceType::LinuxDesktop,
|
||||
9 => DeviceType::ChromeBrowser,
|
||||
10 => DeviceType::FirefoxBrowser,
|
||||
11 => DeviceType::OperaBrowser,
|
||||
12 => DeviceType::EdgeBrowser,
|
||||
13 => DeviceType::IEBrowser,
|
||||
14 => DeviceType::UnknownBrowser,
|
||||
15 => DeviceType::AndroidAmazon,
|
||||
16 => DeviceType::Uwp,
|
||||
17 => DeviceType::SafariBrowser,
|
||||
18 => DeviceType::VivaldiBrowser,
|
||||
19 => DeviceType::VivaldiExtension,
|
||||
20 => DeviceType::SafariExtension,
|
||||
21 => DeviceType::Sdk,
|
||||
22 => DeviceType::Server,
|
||||
_ => DeviceType::UnknownBrowser,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
mod attachment;
|
||||
mod auth_request;
|
||||
mod cipher;
|
||||
mod collection;
|
||||
mod device;
|
||||
@@ -15,9 +16,10 @@ mod two_factor_incomplete;
|
||||
mod user;
|
||||
|
||||
pub use self::attachment::Attachment;
|
||||
pub use self::auth_request::AuthRequest;
|
||||
pub use self::cipher::Cipher;
|
||||
pub use self::collection::{Collection, CollectionCipher, CollectionUser};
|
||||
pub use self::device::Device;
|
||||
pub use self::device::{Device, DeviceType};
|
||||
pub use self::emergency_access::{EmergencyAccess, EmergencyAccessStatus, EmergencyAccessType};
|
||||
pub use self::event::{Event, EventType};
|
||||
pub use self::favorite::Favorite;
|
||||
|
@@ -286,6 +286,26 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
auth_requests (uuid) {
|
||||
uuid -> Text,
|
||||
user_uuid -> Text,
|
||||
organization_uuid -> Nullable<Text>,
|
||||
request_device_identifier -> Text,
|
||||
device_type -> Integer,
|
||||
request_ip -> Text,
|
||||
response_device_id -> Nullable<Text>,
|
||||
access_code -> Text,
|
||||
public_key -> Text,
|
||||
enc_key -> Text,
|
||||
master_password_hash -> Text,
|
||||
approved -> Nullable<Bool>,
|
||||
creation_date -> Timestamp,
|
||||
response_date -> Nullable<Timestamp>,
|
||||
authentication_date -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(attachments -> ciphers (cipher_uuid));
|
||||
joinable!(ciphers -> organizations (organization_uuid));
|
||||
joinable!(ciphers -> users (user_uuid));
|
||||
@@ -312,6 +332,7 @@ joinable!(groups_users -> groups (groups_uuid));
|
||||
joinable!(collections_groups -> collections (collections_uuid));
|
||||
joinable!(collections_groups -> groups (groups_uuid));
|
||||
joinable!(event -> users_organizations (uuid));
|
||||
joinable!(auth_requests -> users (user_uuid));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
attachments,
|
||||
@@ -335,4 +356,5 @@ allow_tables_to_appear_in_same_query!(
|
||||
groups_users,
|
||||
collections_groups,
|
||||
event,
|
||||
auth_requests,
|
||||
);
|
||||
|
@@ -286,6 +286,26 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
auth_requests (uuid) {
|
||||
uuid -> Text,
|
||||
user_uuid -> Text,
|
||||
organization_uuid -> Nullable<Text>,
|
||||
request_device_identifier -> Text,
|
||||
device_type -> Integer,
|
||||
request_ip -> Text,
|
||||
response_device_id -> Nullable<Text>,
|
||||
access_code -> Text,
|
||||
public_key -> Text,
|
||||
enc_key -> Text,
|
||||
master_password_hash -> Text,
|
||||
approved -> Nullable<Bool>,
|
||||
creation_date -> Timestamp,
|
||||
response_date -> Nullable<Timestamp>,
|
||||
authentication_date -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(attachments -> ciphers (cipher_uuid));
|
||||
joinable!(ciphers -> organizations (organization_uuid));
|
||||
joinable!(ciphers -> users (user_uuid));
|
||||
@@ -312,6 +332,7 @@ joinable!(groups_users -> groups (groups_uuid));
|
||||
joinable!(collections_groups -> collections (collections_uuid));
|
||||
joinable!(collections_groups -> groups (groups_uuid));
|
||||
joinable!(event -> users_organizations (uuid));
|
||||
joinable!(auth_requests -> users (user_uuid));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
attachments,
|
||||
@@ -335,4 +356,5 @@ allow_tables_to_appear_in_same_query!(
|
||||
groups_users,
|
||||
collections_groups,
|
||||
event,
|
||||
auth_requests,
|
||||
);
|
||||
|
@@ -286,6 +286,26 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
auth_requests (uuid) {
|
||||
uuid -> Text,
|
||||
user_uuid -> Text,
|
||||
organization_uuid -> Nullable<Text>,
|
||||
request_device_identifier -> Text,
|
||||
device_type -> Integer,
|
||||
request_ip -> Text,
|
||||
response_device_id -> Nullable<Text>,
|
||||
access_code -> Text,
|
||||
public_key -> Text,
|
||||
enc_key -> Text,
|
||||
master_password_hash -> Text,
|
||||
approved -> Nullable<Bool>,
|
||||
creation_date -> Timestamp,
|
||||
response_date -> Nullable<Timestamp>,
|
||||
authentication_date -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(attachments -> ciphers (cipher_uuid));
|
||||
joinable!(ciphers -> organizations (organization_uuid));
|
||||
joinable!(ciphers -> users (user_uuid));
|
||||
@@ -313,6 +333,7 @@ joinable!(groups_users -> groups (groups_uuid));
|
||||
joinable!(collections_groups -> collections (collections_uuid));
|
||||
joinable!(collections_groups -> groups (groups_uuid));
|
||||
joinable!(event -> users_organizations (uuid));
|
||||
joinable!(auth_requests -> users (user_uuid));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
attachments,
|
||||
@@ -336,4 +357,5 @@ allow_tables_to_appear_in_same_query!(
|
||||
groups_users,
|
||||
collections_groups,
|
||||
event,
|
||||
auth_requests,
|
||||
);
|
||||
|
Reference in New Issue
Block a user