An unofficial, mostly Bitwarden-compatible API server written in Ruby (Sinatra and ActiveRecord)
at master 112 lines 3.6 kB view raw
1# 2# Copyright (c) 2017 joshua stein <jcs@jcs.org> 3# 4# Permission to use, copy, modify, and distribute this software for any 5# purpose with or without fee is hereby granted, provided that the above 6# copyright notice and this permission notice appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15# 16 17require "rotp" 18 19class User < DBModel 20 self.table_name = "users" 21 #set_primary_key "uuid" 22 23 DEFAULT_KDF_TYPE = Bitwarden::KDF::PBKDF2 24 25 before_create :generate_uuid_primary_key 26 before_validation :generate_security_stamp 27 28 has_many :ciphers, 29 foreign_key: :user_uuid, 30 inverse_of: :user, 31 dependent: :destroy 32 has_many :folders, 33 foreign_key: :user_uuid, 34 inverse_of: :user, 35 dependent: :destroy 36 has_many :devices, 37 foreign_key: :user_uuid, 38 inverse_of: :user, 39 dependent: :destroy 40 41 def decrypt_data_with_master_password_key(data, mk) 42 # self.key is random data encrypted with the key of (password,email), so 43 # create that key and decrypt the random data to get the original 44 # encryption key, then use that key to decrypt the data 45 encKey = Bitwarden.decrypt(self.key, mk) 46 Bitwarden.decrypt(data, encKey) 47 end 48 49 def encrypt_data_with_master_password_key(data, mk) 50 # self.key is random data encrypted with the key of (password,email), so 51 # create that key and decrypt the random data to get the original 52 # encryption key, then use that key to encrypt the data 53 encKey = Bitwarden.decrypt(self.key, mk) 54 Bitwarden.encrypt(data, encKey) 55 end 56 57 def has_password_hash?(hash) 58 self.password_hash.timingsafe_equal_to(hash) 59 end 60 61 def to_hash 62 { 63 "Id" => self.uuid, 64 "Name" => self.name, 65 "Email" => self.email, 66 "EmailVerified" => self.email_verified, 67 "Premium" => self.premium, 68 "MasterPasswordHint" => self.password_hint, 69 "Culture" => self.culture, 70 "TwoFactorEnabled" => self.two_factor_enabled?, 71 "Key" => self.key, 72 "PrivateKey" => nil, 73 "SecurityStamp" => self.security_stamp, 74 "Organizations" => [], 75 "Object" => "profile" 76 } 77 end 78 79 def two_factor_enabled? 80 self.totp_secret.present? 81 end 82 83 def update_master_password(old_pwd, new_pwd, 84 new_kdf_iterations = self.kdf_iterations) 85 # original random encryption key must be preserved, just re-encrypted with 86 # a new key derived from the new password 87 88 orig_key = Bitwarden.decrypt(self.key, 89 Bitwarden.makeKey(old_pwd, self.email, 90 Bitwarden::KDF::TYPES[self.kdf_type], self.kdf_iterations)) 91 92 self.key = Bitwarden.encrypt(orig_key, 93 Bitwarden.makeKey(new_pwd, self.email, 94 Bitwarden::KDF::TYPES[self.kdf_type], new_kdf_iterations)).to_s 95 96 self.password_hash = Bitwarden.hashPassword(new_pwd, self.email, 97 self.kdf_type, new_kdf_iterations) 98 self.kdf_iterations = new_kdf_iterations 99 self.security_stamp = SecureRandom.uuid 100 end 101 102 def verifies_totp_code?(code) 103 ROTP::TOTP.new(self.totp_secret).now == code.to_s 104 end 105 106protected 107 def generate_security_stamp 108 if self.security_stamp.blank? 109 self.security_stamp = SecureRandom.uuid 110 end 111 end 112end