|
121 | 121 | end |
122 | 122 | end |
123 | 123 | end |
| 124 | + |
| 125 | + # rubocop:disable RSpec/MultipleExpectations |
| 126 | + describe "GET #options_for_get" do |
| 127 | + before do |
| 128 | + user.webauthn_credentials.create!( |
| 129 | + external_id: "second-factor-id", |
| 130 | + name: "Second Factor Key", |
| 131 | + public_key: "pk", |
| 132 | + sign_count: 0 |
| 133 | + ) |
| 134 | + |
| 135 | + # rubocop:disable RSpec/AnyInstance |
| 136 | + allow_any_instance_of(described_class) |
| 137 | + .to receive(:set_resource) do |instance| |
| 138 | + instance.instance_variable_set(:@resource, user) |
| 139 | + end |
| 140 | + # rubocop:enable RSpec/AnyInstance |
| 141 | + end |
| 142 | + |
| 143 | + it "returns authentication options and stores the challenge in the session" do |
| 144 | + get options_for_get_account_second_factor_webauthn_credentials_path |
| 145 | + |
| 146 | + expect(response).to have_http_status(:ok) |
| 147 | + expect(response.media_type).to eq("application/json") |
| 148 | + |
| 149 | + body = response.parsed_body |
| 150 | + expect(body).to include("challenge") |
| 151 | + expect(body["userVerification"]).to eq("discouraged") |
| 152 | + |
| 153 | + expect(body["allowCredentials"].size).to eq(1) |
| 154 | + |
| 155 | + expect(session[:two_factor_authentication_challenge]).to be_present |
| 156 | + end |
| 157 | + end |
| 158 | + |
| 159 | + describe "GET #options_for_create" do |
| 160 | + context "when user is authenticated" do |
| 161 | + before do |
| 162 | + sign_in user, scope: :account |
| 163 | + end |
| 164 | + |
| 165 | + it "returns security key creation options and stores the challenge in the session" do |
| 166 | + get options_for_create_account_second_factor_webauthn_credentials_path |
| 167 | + |
| 168 | + expect(response).to have_http_status(:ok) |
| 169 | + expect(response.media_type).to eq("application/json") |
| 170 | + |
| 171 | + body = response.parsed_body |
| 172 | + expect(body).to include("challenge") |
| 173 | + expect(body.dig("user", "name")).to eq(user.email) |
| 174 | + |
| 175 | + expect(body.dig("authenticatorSelection", "residentKey")).to eq("discouraged") |
| 176 | + expect(body.dig("authenticatorSelection", "userVerification")).to eq("discouraged") |
| 177 | + |
| 178 | + expect(session[:webauthn_challenge]).to be_present |
| 179 | + end |
| 180 | + |
| 181 | + it "includes existing first-factor credentials in the excludeCredentials list" do |
| 182 | + user.webauthn_credentials.create!( |
| 183 | + external_id: "existing-id", |
| 184 | + name: "Existing Key", |
| 185 | + public_key: "pk", |
| 186 | + sign_count: 0 |
| 187 | + ) |
| 188 | + |
| 189 | + get options_for_create_account_second_factor_webauthn_credentials_path |
| 190 | + |
| 191 | + body = response.parsed_body |
| 192 | + exclude_credentials = body["excludeCredentials"] || [] |
| 193 | + |
| 194 | + expect(exclude_credentials.size).to eq(user.webauthn_credentials.count) |
| 195 | + end |
| 196 | + end |
| 197 | + |
| 198 | + context "when user is not authenticated" do |
| 199 | + it "redirects to the sign-in page" do |
| 200 | + get options_for_create_account_second_factor_webauthn_credentials_path |
| 201 | + |
| 202 | + expect(response).to redirect_to(new_account_session_path) |
| 203 | + end |
| 204 | + end |
| 205 | + end |
| 206 | + # rubocop:enable RSpec/MultipleExpectations |
124 | 207 | end |
0 commit comments