main
1# frozen_string_literal: true
2
3class Client < ApplicationRecord
4 RESPONSE_TYPES = %w[code token].freeze
5 audited
6 has_secure_password
7 has_many :authorizations, dependent: :delete_all
8 attribute :redirect_uris, :string, array: true
9 enum token_endpoint_auth_method: {
10 client_secret_basic: 0,
11 client_secret_post: 1,
12 client_secret_none: 2,
13 }
14
15 validates :redirect_uris, presence: true
16 validates :jwks_uri, format: { with: URI_REGEX }, allow_blank: true
17 validates :logo_uri, format: { with: URI_REGEX }, allow_blank: true
18 validates :name, presence: true
19 validates_each :redirect_uris do |record, _attr, value|
20 invalid_uri = Array(value).find { |x| !x.match?(URI_REGEX) }
21 record.errors[:redirect_uris] << 'is invalid.' if invalid_uri
22 end
23
24 after_initialize do
25 self.password = SecureRandom.base58(24) unless password_digest
26 end
27
28 def grant_types
29 [
30 :authorization_code,
31 :refresh_token,
32 :client_credentials,
33 :password,
34 'urn:ietf:params:oauth:grant-type:saml2-bearer'
35 ]
36 end
37
38 def access_token
39 transaction do
40 Token
41 .active.where(subject: self, audience: self)
42 .update_all(revoked_at: Time.current)
43 Token.create!(subject: self, audience: self, token_type: :access)
44 end
45 end
46
47 def revoke(token)
48 token.revoke! if token.issued_to?(self)
49 end
50
51 def valid_redirect_uri?(redirect_uri)
52 redirect_uris.include? redirect_uri
53 end
54
55 def valid_response_type?(response_type)
56 RESPONSE_TYPES.include?(response_type)
57 end
58
59 def redirect_url_for(user, oauth)
60 sha256 = oauth[:code_challenge_method] == 'S256'
61 authorization = authorizations.create!(
62 user: user,
63 challenge: oauth[:code_challenge],
64 challenge_method: sha256 ? :sha256 : :plain
65 )
66
67 if oauth[:response_type] == 'code'
68 redirect_url(code: authorization.code, state: oauth[:state]) do
69 oauth[:redirect_uri]
70 end
71 elsif oauth[:response_type] == 'token'
72 access_token, = authorization.issue_tokens_to(
73 self, token_types: [:access]
74 )
75 redirect_url(
76 access_token: access_token.to_jwt,
77 token_type: 'Bearer',
78 expires_in: 5.minutes.to_i,
79 scope: :admin,
80 state: oauth[:state]
81 ) do
82 oauth[:redirect_uri]
83 end
84 else
85 redirect_url(error: 'unsupported_response_type', state: state)
86 end
87 end
88
89 def redirect_url(fragments = {})
90 redirect_uri = block_given? ? yield : redirect_uris[0]
91 return unless valid_redirect_uri?(redirect_uri)
92
93 URI.parse(
94 "#{redirect_uri}#" + fragments.map do |(key, value)|
95 "#{key}=#{value}" if value.present?
96 end.compact.join("&")
97 ).to_s
98 end
99end