zaikio-oauth_client 0.7.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32f871e3c1b27ab91ce3bbaa35d4ac460cc12d93401c1b73d4dd618cd83d1f00
4
- data.tar.gz: 1ce0459fdb769c4f5c7e31287cccc16d6fce74a819557dfa1c5c6cd3120991ee
3
+ metadata.gz: c3caeb6fd8b46df684ae2ead61a41965d33d0c8447576ca3dd4472e035917732
4
+ data.tar.gz: 7efeeb1977f82515ac60e41dfeec76a2e03867cead09e4e7bdee225f9e2d7051
5
5
  SHA512:
6
- metadata.gz: 567ac3217d0b63498fc6d18292c4b1fcdd6d8407c2341daae177388e2e68104bfd0aab00990606099b7e9f5d10dc13e7572586271eb0f76c8c6200520db81f81
7
- data.tar.gz: b9366a697539fa31138e2ddc8b9f02f9dd6ed6cd11113bbc8ed3c1732301044395b19d033eda14d5b7bb7ad7c32f0836be6e89a000b2f7dd6270f05385846283
6
+ metadata.gz: 49c66d7cee9b78180b46f5f75318e9cdab0e0ea04cf7bf349c09990ac337dc367e32fcf15f70a3728fa524a53ea3a5771e0c436b9902d4f7a023ad72a0b21adb
7
+ data.tar.gz: b82883c6b3b85dabe9759266bd5c5f4b338bb9917c3bd60696412e4651a23c45e73e296a600258aa7072ad5c417dd728742afecdba8a5f14b66a673ff836e0f4
data/README.md CHANGED
@@ -118,6 +118,19 @@ redirect_to zaikio_oauth_client.new_session_path(force_login: true)
118
118
  redirect_to zaikio_oauth_client.new_session_path(state: "something-my-app-uses")
119
119
  ```
120
120
 
121
+ You can also send them to the [Subscription Redirect
122
+ flow](https://docs.zaikio.com/api/directory/guides/subscriptions/redirect-flow/), which
123
+ behaves & redirects back like a regular Organization flow except it additionally sets up a
124
+ subscription for the organization:
125
+
126
+ ```ruby
127
+ # Require them to select a plan themselves...
128
+ redirect_to zaikio_oauth_client.new_subscription_path
129
+
130
+ # Or preselect a plan for them
131
+ redirect_to zaikio_oauth_client.new_subscription_path(plan: "free")
132
+ ```
133
+
121
134
  #### Session handling
122
135
 
123
136
  The Zaikio gem engine will set a cookie for the user after a successful OAuth flow: `cookies.encrypted[:zaikio_person_id]`.
@@ -174,6 +187,11 @@ class ApplicationController < ActionController::Base
174
187
 
175
188
  main_app.root_path
176
189
  end
190
+
191
+ def error_path_for(error_code, description: nil)
192
+ # Handle error
193
+ main_app.root_path
194
+ end
177
195
  end
178
196
  ```
179
197
 
@@ -254,7 +272,7 @@ To release a new version of the gem:
254
272
  - Update the version in `lib/zaikio/oauth_client/version.rb`
255
273
  - Update `CHANGELOG.md` to include the new version and its release date
256
274
  - Commit and push your changes
257
- - Create a [new release on GitHub](https://github.com/zaikio/zaikio-directory-models/releases/new)
275
+ - Create a [new release on GitHub](https://github.com/zaikio/zaikio-oauth_client/releases/new)
258
276
  - CircleCI will build the Gem package and push it Rubygems for you
259
277
 
260
278
  ## License
@@ -0,0 +1,28 @@
1
+ module Zaikio
2
+ module OAuthClient
3
+ class SubscriptionsController < ConnectionsController
4
+ def new
5
+ opts = params.permit(:client_name, :state, :plan, :organization_id)
6
+ opts[:redirect_with_error] = 1
7
+ opts[:state] ||= cookies.encrypted[:state] = SecureRandom.urlsafe_base64(32)
8
+
9
+ plan = opts.delete(:plan)
10
+ organization_id = opts.delete(:organization_id)
11
+
12
+ subscription_scope = if organization_id.present?
13
+ "Org/#{organization_id}.subscription_create"
14
+ else
15
+ "Org.subscription_create"
16
+ end
17
+
18
+ subscription_scope << ".#{plan}" if plan.present?
19
+
20
+ redirect_to oauth_client.auth_code.authorize_url(
21
+ redirect_uri: approve_url(opts.delete(:client_name)),
22
+ scope: subscription_scope,
23
+ **opts
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -78,15 +78,19 @@ module Zaikio
78
78
  attributes.slice("token", "refresh_token")
79
79
  ).refresh!
80
80
 
81
- access_token = self.class.build_from_access_token(
81
+ destroy
82
+
83
+ self.class.build_from_access_token(
82
84
  refreshed_token,
83
85
  requested_scopes: requested_scopes
84
- )
86
+ ).tap(&:save!)
87
+ end
88
+ rescue OAuth2::Error => e
89
+ raise unless e.code == "invalid_grant"
85
90
 
86
- transaction { destroy if access_token.save! }
91
+ destroy
87
92
 
88
- access_token
89
- end
93
+ nil
90
94
  end
91
95
  end
92
96
  end
@@ -0,0 +1,4 @@
1
+ de:
2
+ zaikio:
3
+ oauth_client:
4
+ error_occured: "Beim Login ist ein Fehler aufgetreten: %{error} %{description}. Bitte versuche es nochmal."
@@ -1,6 +1,7 @@
1
1
  en:
2
2
  zaikio:
3
+ oauth_client:
4
+ error_occured: "An error occurred during login: %{error} %{description}. Please try again."
3
5
  forms:
4
6
  optional: Optional
5
7
  learn_more: Learn more
6
-
data/config/routes.rb CHANGED
@@ -1,15 +1,17 @@
1
1
  Zaikio::OAuthClient::Engine.routes.draw do
2
- sessions_controller = Zaikio::OAuthClient.configuration.sessions_controller_name
3
- connections_controller = Zaikio::OAuthClient.configuration.connections_controller_name
2
+ config = Zaikio::OAuthClient.configuration
4
3
 
5
- # People
6
- get "(/:client_name)/sessions/new", action: :new, controller: sessions_controller, as: :new_session
7
- get "(/:client_name)/sessions/approve", action: :approve, controller: sessions_controller, as: :approve_session
8
- delete "(/:client_name)/session", action: :destroy, controller: sessions_controller, as: :session
4
+ scope path: "(/:client_name)" do
5
+ # People
6
+ get "/sessions/new", action: :new, controller: config.sessions_controller_name, as: :new_session
7
+ get "/sessions/approve", action: :approve, controller: config.sessions_controller_name, as: :approve_session
8
+ delete "/session", action: :destroy, controller: config.sessions_controller_name, as: :session
9
9
 
10
- # Organizations
11
- get "(/:client_name)/connections/new", action: :new,
12
- controller: connections_controller, as: :new_connection
13
- get "(/:client_name)/connections/approve", action: :approve,
14
- controller: connections_controller, as: :approve_connection
10
+ # Organizations
11
+ get "/connections/new", action: :new, controller: config.connections_controller_name, as: :new_connection
12
+ get "/connections/approve", action: :approve, controller: config.connections_controller_name, as: :approve_connection
13
+
14
+ # Subscriptions
15
+ get "/subscriptions/new", action: :new, controller: config.subscriptions_controller_name, as: :new_subscription
16
+ end
15
17
  end
@@ -1,5 +1,6 @@
1
1
  require "oauth2"
2
2
 
3
+ require "zaikio/oauth_client/error"
3
4
  require "zaikio/oauth_client/engine"
4
5
  require "zaikio/oauth_client/configuration"
5
6
  require "zaikio/oauth_client/authenticatable"
@@ -57,34 +58,61 @@ module Zaikio
57
58
  end
58
59
  end
59
60
 
60
- def get_access_token(client_name: nil, bearer_type: "Person", bearer_id: nil, scopes: nil) # rubocop:disable Metrics/MethodLength
61
- client_name ||= self.client_name
62
- client_config = client_config_for(client_name)
61
+ # Finds the best possible access token, using the DB or an API call
62
+ # * If the token has expired, it will be refreshed using the refresh_token flow
63
+ # (if this fails, we fallback to getting a new token using client_credentials)
64
+ # * If the token does not exist, we'll get a new one using the client_credentials flow
65
+ def get_access_token(bearer_id:, client_name: nil, bearer_type: "Person", scopes: nil)
66
+ client_config = client_config_for(client_name || self.client_name)
63
67
  scopes ||= client_config.default_scopes_for(bearer_type)
64
68
 
65
- access_token = Zaikio::AccessToken.where(audience: client_config.client_name)
66
- .usable(
67
- bearer_type: bearer_type,
68
- bearer_id: bearer_id,
69
- requested_scopes: scopes
70
- )
71
- .first
72
-
73
- if access_token.blank?
74
- access_token = Zaikio::AccessToken.build_from_access_token(
75
- client_config.token_by_client_credentials(
69
+ token = find_usable_access_token(client_name: client_config.client_name,
70
+ bearer_type: bearer_type,
71
+ bearer_id: bearer_id,
72
+ requested_scopes: scopes)
73
+
74
+ token = token.refresh! if token&.expired?
75
+
76
+ token ||= fetch_new_token(client_config: client_config,
77
+ bearer_type: bearer_type,
78
+ bearer_id: bearer_id,
79
+ scopes: scopes)
80
+ token
81
+ end
82
+
83
+ # Finds the best usable access token. Note that this token may have expired and
84
+ # would require refreshing.
85
+ def find_usable_access_token(client_name:, bearer_type:, bearer_id:, requested_scopes:)
86
+ configuration.logger.debug "Try to fetch token for client_name: #{client_name}, "\
87
+ "bearer #{bearer_type}/#{bearer_id}, requested_scopes: #{requested_scopes}"
88
+
89
+ fetch_access_token = lambda {
90
+ Zaikio::AccessToken
91
+ .where(audience: client_name)
92
+ .usable(
76
93
  bearer_type: bearer_type,
77
94
  bearer_id: bearer_id,
78
- scopes: scopes
79
- ),
80
- requested_scopes: scopes
81
- )
82
- access_token.save!
83
- elsif access_token&.expired?
84
- access_token = access_token.refresh!
95
+ requested_scopes: requested_scopes
96
+ )
97
+ .first
98
+ }
99
+
100
+ if configuration.logger.respond_to?(:silence)
101
+ configuration.logger.silence { fetch_access_token.call }
102
+ else
103
+ fetch_access_token.call
85
104
  end
105
+ end
86
106
 
87
- access_token
107
+ def fetch_new_token(client_config:, bearer_type:, bearer_id:, scopes:)
108
+ Zaikio::AccessToken.build_from_access_token(
109
+ client_config.token_by_client_credentials(
110
+ bearer_type: bearer_type,
111
+ bearer_id: bearer_id,
112
+ scopes: scopes
113
+ ),
114
+ requested_scopes: scopes
115
+ ).tap(&:save!)
88
116
  end
89
117
 
90
118
  def get_plain_scopes(scopes)
@@ -5,7 +5,9 @@ module Zaikio
5
5
 
6
6
  def new
7
7
  opts = params.permit(:client_name, :show_signup, :force_login, :state)
8
+ opts[:redirect_with_error] = 1
8
9
  client_name = opts.delete(:client_name)
10
+ opts[:state] ||= cookies.encrypted[:state] = SecureRandom.urlsafe_base64(32)
9
11
 
10
12
  redirect_to oauth_client.auth_code.authorize_url(
11
13
  redirect_uri: approve_url(client_name),
@@ -15,6 +17,21 @@ module Zaikio
15
17
  end
16
18
 
17
19
  def approve
20
+ if params[:error].present?
21
+ redirect_to send(
22
+ respond_to?(:error_path_for) ? :error_path_for : :default_error_path_for,
23
+ params[:error],
24
+ description: params[:error_description]
25
+ ) and return
26
+ end
27
+
28
+ if cookies.encrypted[:state].present? && params[:state] != cookies.encrypted[:state]
29
+ return redirect_to send(
30
+ respond_to?(:error_path_for) ? :error_path_for : :default_error_path_for,
31
+ "invalid_state"
32
+ )
33
+ end
34
+
18
35
  access_token = create_access_token
19
36
 
20
37
  origin = cookies.encrypted[:origin]
@@ -31,6 +48,7 @@ module Zaikio
31
48
  def destroy
32
49
  access_token_id = cookies.encrypted[:zaikio_access_token_id]
33
50
  cookies.delete :zaikio_access_token_id
51
+ cookies.delete :state
34
52
 
35
53
  redirect_to send(
36
54
  respond_to?(:after_destroy_path_for) ? :after_destroy_path_for : :default_after_destroy_path_for,
@@ -87,6 +105,16 @@ module Zaikio
87
105
 
88
106
  main_app.root_path
89
107
  end
108
+
109
+ def default_error_path_for(error_code, description: nil)
110
+ raise Zaikio::OAuthClient::InvalidScopesError, description if error_code == "invalid_scope"
111
+
112
+ unless error_code == "access_denied"
113
+ flash[:alert] = I18n.t("zaikio.oauth_client.error_occured", error: error_code, description: description)
114
+ end
115
+
116
+ main_app.root_path
117
+ end
90
118
  end
91
119
  end
92
120
  end
@@ -13,19 +13,25 @@ module Zaikio
13
13
  }.freeze
14
14
 
15
15
  attr_accessor :host
16
- attr_writer :logger
17
16
  attr_reader :client_configurations, :environment, :around_auth_block,
18
- :sessions_controller_name, :connections_controller_name
17
+ :sessions_controller_name, :connections_controller_name, :subscriptions_controller_name
19
18
 
20
19
  def initialize
21
20
  @client_configurations = {}
22
21
  @around_auth_block = nil
23
22
  @sessions_controller_name = "sessions"
24
23
  @connections_controller_name = "connections"
24
+ @subscriptions_controller_name = "subscriptions"
25
+ Zaikio::AccessToken.logger = logger
25
26
  end
26
27
 
27
28
  def logger
28
- @logger ||= Logger.new($stdout)
29
+ @logger ||= ActiveSupport::Logger.new($stdout)
30
+ end
31
+
32
+ def logger=(logger)
33
+ @logger = logger
34
+ Zaikio::AccessToken.logger = @logger
29
35
  end
30
36
 
31
37
  def register_client(name)
@@ -58,6 +64,10 @@ module Zaikio
58
64
  @connections_controller_name = "/#{name}"
59
65
  end
60
66
 
67
+ def subscriptions_controller_name=(name)
68
+ @subscriptions_controller_name = "/#{name}"
69
+ end
70
+
61
71
  private
62
72
 
63
73
  def host_for(environment)
@@ -0,0 +1,5 @@
1
+ module Zaikio
2
+ module OAuthClient
3
+ class InvalidScopesError < StandardError; end
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  module Zaikio
2
2
  module OAuthClient
3
- VERSION = "0.7.2".freeze
3
+ VERSION = "0.11.0".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zaikio-oauth_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zaikio GmbH
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-29 00:00:00.000000000 Z
11
+ date: 2021-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -144,11 +144,13 @@ files:
144
144
  - Rakefile
145
145
  - app/controllers/zaikio/oauth_client/connections_controller.rb
146
146
  - app/controllers/zaikio/oauth_client/sessions_controller.rb
147
+ - app/controllers/zaikio/oauth_client/subscriptions_controller.rb
147
148
  - app/helpers/zaikio/application_helper.rb
148
149
  - app/jobs/zaikio/application_job.rb
149
150
  - app/jobs/zaikio/cleanup_access_tokens_job.rb
150
151
  - app/models/zaikio/access_token.rb
151
152
  - config/initializers/inflections.rb
153
+ - config/locales/de.yml
152
154
  - config/locales/en.yml
153
155
  - config/routes.rb
154
156
  - db/migrate/20190426155505_enable_postgres_extensions_for_uuids.rb
@@ -161,6 +163,7 @@ files:
161
163
  - lib/zaikio/oauth_client/client_configuration.rb
162
164
  - lib/zaikio/oauth_client/configuration.rb
163
165
  - lib/zaikio/oauth_client/engine.rb
166
+ - lib/zaikio/oauth_client/error.rb
164
167
  - lib/zaikio/oauth_client/test_helper.rb
165
168
  - lib/zaikio/oauth_client/version.rb
166
169
  homepage: https://github.com/zaikio/zaikio-oauth_client