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:
raucao committed 2026-11-03 09:17:43 +04:00
1 parent bf23931822
commit 8dd0bf10ce
19 files changed
+827 -73

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
class RsAuthorizationCode < ApplicationRecord
belongs_to :user
CODE_TTL = 5.minutes
serialize :permissions, coder: YAML
attr_accessor :code
before_validation :generate_code
scope :active, -> { where(expires_at: Time.current..) }
def self.find_by_code(raw_code)
return nil if raw_code.blank?
find_by(code_digest: Digest::SHA256.hexdigest(raw_code))
end
def expired?
expires_at <= Time.current
end
def consume!
destroy!
end
private
def generate_code
return if code_digest.present?
self.code = SecureRandom.urlsafe_base64(32)
self.code_digest = Digest::SHA256.hexdigest(code)
self.expires_at ||= CODE_TTL.from_now
end
end