Add PKCE support to remoteStorage OAuth
Implements the optional OAuth 2.0 Authorization Code grant with PKCE (RFC 7636) for remoteStorage, as described in the remoteStorage spec (section 10.1). - Advertise the authorization and token endpoints plus the supported code_challenge method (S256) in WebFinger, and bump the advertised spec version to draft-dejong-remotestorage-27 - Add RsAuthorizationCode: a short-lived (5 min), single-use authorization code stored only as a SHA256 digest - Add RsOauthManager services for issuing and exchanging codes, including S256 verification - Add a cross-origin POST /rs/oauth/token endpoint that exchanges a code (and code_verifier) for an access token, with CORS and no CSRF token - Consent dialog now carries response_type / code_challenge through the form and redirects with the code in the query string for the code flow - Remove the existing-token reuse shortcut so every authorization results in a distinct authorization (and token) Part of #254
This commit is contained in:
19 files changed
+827
-73
No files matched your search
@@ -0,0 +1,65 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe RsAuthorizationCode, type: :model do
|
||||
let(:user) { create :user }
|
||||
|
||||
def build_code(**attrs)
|
||||
RsAuthorizationCode.create!(
|
||||
user: user,
|
||||
client_id: "example.com",
|
||||
redirect_uri: "https://example.com",
|
||||
permissions: %w(documents:rw),
|
||||
code_challenge: "challenge",
|
||||
**attrs
|
||||
)
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
it "generates a raw code and stores only its digest" do
|
||||
code = build_code
|
||||
|
||||
expect(code.code).to be_present
|
||||
expect(code.code_digest).to eq(Digest::SHA256.hexdigest(code.code))
|
||||
end
|
||||
|
||||
it "sets a short expiry" do
|
||||
code = build_code
|
||||
|
||||
expect(code.expires_at).to be_within(5.seconds).of(5.minutes.from_now)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".find_by_code" do
|
||||
it "finds a code by its raw value" do
|
||||
code = build_code
|
||||
|
||||
expect(RsAuthorizationCode.find_by_code(code.code)).to eq(code)
|
||||
end
|
||||
|
||||
it "returns nil for an unknown code" do
|
||||
expect(RsAuthorizationCode.find_by_code("does-not-exist")).to be_nil
|
||||
end
|
||||
|
||||
it "returns nil for a blank code" do
|
||||
expect(RsAuthorizationCode.find_by_code(nil)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#expired?" do
|
||||
it "is false before the expiry" do
|
||||
expect(build_code).not_to be_expired
|
||||
end
|
||||
|
||||
it "is true after the expiry" do
|
||||
expect(build_code(expires_at: 1.minute.ago)).to be_expired
|
||||
end
|
||||
end
|
||||
|
||||
describe "#consume!" do
|
||||
it "destroys the record" do
|
||||
code = build_code
|
||||
|
||||
expect { code.consume! }.to change(RsAuthorizationCode, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user