An unofficial, mostly Bitwarden-compatible API server written in Ruby (Sinatra and ActiveRecord)
1require_relative "spec_helper.rb"
2
3describe "identity module" do
4 before do
5 User.all.delete_all
6 end
7
8 it "should return successful response to account creation" do
9 post "/api/accounts/register", {
10 :name => nil,
11 :email => "nobody@example.com",
12 :masterPasswordHash => Bitwarden.hashPassword("asdf",
13 "nobody@example.com", User::DEFAULT_KDF_TYPE,
14 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
15 :masterPasswordHint => nil,
16 :key => Bitwarden.makeEncKey(
17 Bitwarden.makeKey("adsf", "nobody@example.com", User::DEFAULT_KDF_TYPE,
18 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
19 ),
20 :kdf => Bitwarden::KDF::TYPE_IDS[User::DEFAULT_KDF_TYPE],
21 :kdfIterations => Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE],
22 }
23 last_response.status.must_equal 200
24 end
25
26 it "should not allow duplicate signups" do
27 2.times do |x|
28 post "/api/accounts/register", {
29 :name => nil,
30 :email => "nobody2@example.com",
31 :masterPasswordHash => Bitwarden.hashPassword("asdf",
32 "nobody2@example.com", User::DEFAULT_KDF_TYPE,
33 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
34 :masterPasswordHint => nil,
35 :key => Bitwarden.makeEncKey(
36 Bitwarden.makeKey("adsf", "nobody2@example.com",
37 User::DEFAULT_KDF_TYPE,
38 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE])
39 ),
40 :kdf => Bitwarden::KDF::TYPE_IDS[User::DEFAULT_KDF_TYPE],
41 :kdfIterations => Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE],
42 }
43 if x == 0
44 last_response.status.must_equal 200
45 else
46 last_response.status.wont_equal 200
47 end
48 end
49 end
50
51 it "validates required parameters" do
52 okh = {
53 :name => nil,
54 :email => "nobody3@example.com",
55 :masterPasswordHash => Bitwarden.hashPassword("asdf",
56 "nobody3@example.com", User::DEFAULT_KDF_TYPE,
57 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
58 :masterPasswordHint => nil,
59 :key => Bitwarden.makeEncKey(
60 Bitwarden.makeKey("adsf", "nobody3@example.com",
61 User::DEFAULT_KDF_TYPE,
62 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
63 ),
64 :kdf => Bitwarden::KDF::TYPE_IDS[User::DEFAULT_KDF_TYPE],
65 :kdfIterations => Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE],
66 }
67
68 post "/api/accounts/register", okh.merge({
69 :masterPasswordHash => "",
70 })
71 last_response.status.wont_equal 200
72
73 post "/api/accounts/register", okh.merge({
74 :key => "junk",
75 })
76 last_response.status.wont_equal 200
77
78 post "/api/accounts/register", okh.merge({
79 :kdf => 100,
80 })
81 last_response.status.wont_equal 200
82
83 post "/api/accounts/register", okh.merge({
84 :kdfIterations => 5,
85 })
86 last_response.status.wont_equal 200
87
88 post "/api/accounts/register", okh
89 last_response.status.must_equal 200
90 end
91
92 it "actually creates the user account and allows logging in" do
93 post "/api/accounts/register", {
94 :name => nil,
95 :email => "nobody4@example.com",
96 :masterPasswordHash => Bitwarden.hashPassword("asdf",
97 "nobody4@example.com", User::DEFAULT_KDF_TYPE,
98 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
99 :masterPasswordHint => nil,
100 :key => Bitwarden.makeEncKey(
101 Bitwarden.makeKey("adsf", "nobody4@example.com",
102 User::DEFAULT_KDF_TYPE,
103 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
104 ),
105 :kdf => Bitwarden::KDF::TYPE_IDS[User::DEFAULT_KDF_TYPE],
106 :kdfIterations => Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE],
107 }
108 last_response.status.must_equal 200
109
110 (u = User.find_by_email("nobody4@example.com")).wont_be_nil
111 u.uuid.wont_be_nil
112 u.password_hash.must_equal "uQOY5dffPoKCueMu3cMXl2KOL52NerIQlwCEpQ6mW6s="
113
114 post "/api/accounts/prelogin", {
115 :email => "nobody4@example.com",
116 }
117 last_response.status.must_equal 200
118 last_json_response["KdfIterations"].must_equal(
119 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE])
120
121 post "/identity/connect/token", {
122 :grant_type => "password",
123 :username => "nobody4@example.com",
124 :password => Bitwarden.hashPassword("asdf", "nobody4@example.com",
125 User::DEFAULT_KDF_TYPE,
126 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
127 :scope => "api offline_access",
128 :client_id => "browser",
129 :deviceType => 3,
130 :deviceIdentifier => SecureRandom.uuid,
131 :deviceName => "firefox",
132 :devicePushToken => ""
133 }
134
135 last_response.status.must_equal 200
136 (access_token = last_json_response["access_token"]).wont_be_nil
137
138 get "/api/sync", {}, {
139 "HTTP_AUTHORIZATION" => "Bearer #{access_token}",
140 }
141 last_response.status.must_equal 200
142 end
143
144 it "enforces token validity period" do
145 post "/api/accounts/register", {
146 :name => nil,
147 :email => "nobody5@example.com",
148 :masterPasswordHash => Bitwarden.hashPassword("asdf",
149 "nobody5@example.com", User::DEFAULT_KDF_TYPE,
150 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
151 :masterPasswordHint => nil,
152 :key => Bitwarden.makeEncKey(
153 Bitwarden.makeKey("adsf", "nobody5@example.com",
154 User::DEFAULT_KDF_TYPE,
155 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE])
156 ),
157 :kdf => Bitwarden::KDF::TYPE_IDS[User::DEFAULT_KDF_TYPE],
158 :kdfIterations => Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE],
159 }
160 last_response.status.must_equal 200
161
162 post "/identity/connect/token", {
163 :grant_type => "password",
164 :username => "nobody5@example.com",
165 :password => Bitwarden.hashPassword("asdf", "nobody5@example.com",
166 User::DEFAULT_KDF_TYPE,
167 Bitwarden::KDF::DEFAULT_ITERATIONS[User::DEFAULT_KDF_TYPE]),
168 :scope => "api offline_access",
169 :client_id => "browser",
170 :deviceType => 3,
171 :deviceIdentifier => SecureRandom.uuid,
172 :deviceName => "firefox",
173 :devicePushToken => ""
174 }
175
176 access_token = last_json_response["access_token"]
177
178 get "/api/sync", {}, {
179 "HTTP_AUTHORIZATION" => "Bearer #{access_token}",
180 }
181 last_response.status.must_equal 200
182
183 d = Device.find_by_access_token(access_token)
184 d.regenerate_tokens!(1)
185 d.save
186
187 get "/api/sync", {}, {
188 "HTTP_AUTHORIZATION" => "Bearer #{d.access_token}",
189 }
190 last_response.status.must_equal 200
191
192 sleep 2
193
194 get "/api/sync", {}, {
195 "HTTP_AUTHORIZATION" => "Bearer #{d.access_token}",
196 }
197 last_response.status.wont_equal 200
198 end
199end