padlock_auth 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59dffd185c05709fa2e69aff4bdd49ca4fa74c5ed8e88e830e6e04a9b4b6e02c
4
- data.tar.gz: d1e64abd17f3b8a134e14c8e641f5bafedb7988d1a2e33a33664d28c3aafc0a4
3
+ metadata.gz: d8d0c8061b83de8516e459e26ef96d6e6565f419045c64e7730c4736e7c7c344
4
+ data.tar.gz: 69972dfbcda669fd154c109ef4512d1719e6dafcc52dba780ce1ec80dd4efbeb
5
5
  SHA512:
6
- metadata.gz: 7dad9b84433640976cea93223f1617bf3d508792b216d3668b4393593aada478499edcd7cfb100d37a08c889e231633646f62403aa2d983641f2bc80e00bb995
7
- data.tar.gz: 71ac26355ff515992e6e72075defcddcc7cf42b5db82c71903fd762b053a8fe765e28fcd5d0827deb18c95a1229878a200f54a31b28e8884e7a379c10a428c37
6
+ metadata.gz: 63297a7bbed887a8120b3c2d7ddd6e33d8d23ddc83352d3d6141d92d23af6271f5c0d866061524649122e90cb67032f995126e5174a7cf7469f583f66674b0ce
7
+ data.tar.gz: f9d820e5872e376d05b5724bfbf675bf6597cc72065cff1ac56605cfec0a6de9bbd89f38b2e29b6bfcbd4784cac5095351f434a23d51ac60424da4b7a45ce93a
data/README.md CHANGED
@@ -239,6 +239,94 @@ puts response.body
239
239
 
240
240
  PadlockAuth will extract the username and password using the `from_basic_authorization` method and use them to generate an access token.
241
241
 
242
+ ### Securing an Action Cable Connection
243
+
244
+ You can use PadlockAuth to secure your Action Cable connections by verifying an access token during the connection process. The access token can be provided via the `"access_token"` or `"bearer_token"` parameters.
245
+
246
+ Not all access tokens provide a `subject` method, it is entirely dependent on the implementation.
247
+
248
+ Here’s an example implementation:
249
+
250
+ ```ruby
251
+ module ApplicationCable
252
+ class Connection < ActionCable::Connection::Base
253
+ identified_by :access_token_subject
254
+
255
+ def connect
256
+ self.access_token_subject = find_verified_subject
257
+ end
258
+
259
+ private
260
+
261
+ def find_verified_subject
262
+ if padlock_authorized? "websocket"
263
+ padlock_auth_token.subject
264
+ else
265
+ reject_unauthorized_connection
266
+ end
267
+ end
268
+ end
269
+ end
270
+ ```
271
+
272
+ #### Explanation
273
+
274
+ - `padlock_authorized?`: Checks if the access token is valid and optionally verifies the required scope (`"websocket"` in this example).
275
+ - `padlock_auth_token`: Provides access to the verified token, allowing you to retrieve attributes such as `subject`.
276
+
277
+ This ensures that only clients with valid access tokens can establish a WebSocket connection. The access token `sub` can then be used to identify the connected client throughout the session.
278
+
279
+ #### Example: Connecting to the Action Cable Connection via JS
280
+
281
+ ```js
282
+ import { createConsumer } from "@rails/actioncable"
283
+
284
+ // Use a function to dynamically generate the URL
285
+ createConsumer(getWebSocketURL)
286
+
287
+ function getWebSocketURL() {
288
+ const token = localStorage.get('access-token')
289
+ return `wss://example.com/cable?token=${token}`
290
+ }
291
+ ```
292
+
293
+ ### Securing an ActionCable Channel
294
+
295
+ PadlockAuth can secure individual Action Cable channels by verifying an access token when a client subscribes. The access token can be provided via the `"access_token"` or `"bearer_token"` parameters.
296
+
297
+ Here’s an example implementation:
298
+
299
+ ```ruby
300
+ class AuthorizedChannel < ApplicationCable::Channel
301
+ def subscribed
302
+ if padlock_authorized? "channel"
303
+ stream_from "authorized_stream"
304
+ else
305
+ reject
306
+ end
307
+ end
308
+
309
+ def unsubscribed
310
+ # Any cleanup needed when channel is unsubscribed
311
+ end
312
+ end
313
+ ```
314
+
315
+ #### Explanation
316
+
317
+ - `padlock_authorized?`: Validates the access token and ensures it includes the required scope (`"channel"` in this example).
318
+
319
+ By leveraging these features, PadlockAuth ensures only authenticated users with the proper access scope can subscribe and receive messages on the channel.
320
+
321
+ #### Example: Connecting to the Action Cable Channel via JS
322
+
323
+ ```js
324
+ import consumer from "./consumer"
325
+
326
+ const token = localStorage.get('access-token')
327
+ consumer.subscriptions.create({ channel: "AuthorizedChannel", access_token: token })
328
+ ```
329
+
242
330
  ## Installation
243
331
 
244
332
  Add this line to your application's Gemfile:
@@ -94,6 +94,10 @@ module PadlockAuth
94
94
  config.instance_variable_set(:@access_token_methods, methods.flatten.compact)
95
95
  end
96
96
 
97
+ def action_cable_methods(*methods)
98
+ config.instance_variable_set(:@action_cable_methods, methods.flatten.compact)
99
+ end
100
+
97
101
  # Calls to `padlock_authorize!` will raise an exception when authentication fails.
98
102
  #
99
103
  def raise_on_errors!
@@ -167,6 +171,13 @@ module PadlockAuth
167
171
  ]
168
172
  end
169
173
 
174
+ def action_cable_methods
175
+ @action_cable_methods ||= %i[
176
+ from_access_token_param
177
+ from_bearer_param
178
+ ]
179
+ end
180
+
170
181
  # @!attribute [r] handle_auth_errors
171
182
  #
172
183
  # How to handle authentication errors.
@@ -0,0 +1,71 @@
1
+ module PadlockAuth
2
+ module Rails
3
+ module ActionCableChannelHelpers
4
+ module TokenFactory
5
+ module_function
6
+
7
+ # Retreives the access token from the request using the configured methods.
8
+ def from_params(params, *methods)
9
+ methods.inject(nil) do |_, method|
10
+ method = self.method(method) if method.is_a?(Symbol)
11
+ credentials = method.call(params)
12
+ break credentials if credentials.present?
13
+ end
14
+ end
15
+
16
+ # Retreives the access token from the params, and builds an access token object.
17
+ def authenticate(params)
18
+ if (token = from_params(params, *PadlockAuth.config.action_cable_methods))
19
+ PadlockAuth.build_access_token(token)
20
+ end
21
+ end
22
+
23
+ # Extracts the access token from the `access_token` parameter.
24
+ #
25
+ # @param params [ActionDispatch::Request] params
26
+ #
27
+ # @return [String, nil] Access token
28
+ #
29
+ def from_access_token_param(params)
30
+ params[:access_token]
31
+ end
32
+
33
+ # Extracts the access token from the `bearer_token` parameter.
34
+ #
35
+ # @param params [ActiveSupport::HashWithIndifferentAccess] params
36
+ #
37
+ # @return [String, nil] Access token
38
+ #
39
+ def from_bearer_param(params)
40
+ params[:bearer_token]
41
+ end
42
+ end
43
+
44
+ # @!visibility public
45
+ #
46
+ # Authorize the request with the given scopes.
47
+ #
48
+ # If the request is not authorized, an error response will be rendered
49
+ # or an exception will be raised, depending on the configuration.
50
+ #
51
+ # @param scopes [Array<String>] Scopes required for the request, defaults to the default scopes.
52
+ # @return [Boolean] Whether the request is authorized.
53
+ #
54
+ def padlock_authorized?(*scopes)
55
+ padlock_auth_token&.acceptable?(scopes.presence || PadlockAuth.config.default_scopes)
56
+ end
57
+
58
+ # @!visibility public
59
+ #
60
+ # Retrieve the access token from the request.
61
+ #
62
+ # Does not check if the token is valid or matches the required scopes.
63
+ #
64
+ # @return [PadlockAuth::AbstractToken, nil] Access token
65
+ #
66
+ def padlock_auth_token
67
+ @padlock_auth_token ||= TokenFactory.authenticate(params)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -16,10 +16,14 @@ module PadlockAuth
16
16
  #
17
17
  # @param scopes [Array<String>] Scopes required for the request, defaults to the default scopes.
18
18
  #
19
- def padlock_authorize!(*scopes)
19
+ def padlock_authorize!(...)
20
+ padlock_render_error unless padlock_authorized?(...)
21
+ end
22
+
23
+ def padlock_authorized?(*scopes)
20
24
  @_padlock_auth_scopes = scopes.presence || PadlockAuth.config.default_scopes
21
25
 
22
- padlock_render_error unless valid_padlock_auth_token?
26
+ valid_padlock_auth_token?
23
27
  end
24
28
 
25
29
  # Default render options for unauthorized requests.
@@ -11,6 +11,10 @@ module PadlockAuth
11
11
  ActiveSupport.on_load(:action_controller) do
12
12
  include PadlockAuth::Rails::Helpers
13
13
  end
14
+ ActiveSupport.on_load(:action_cable) do
15
+ ActionCable::Connection::Base.include PadlockAuth::Rails::Helpers
16
+ ActionCable::Channel::Base.include PadlockAuth::Rails::ActionCableChannelHelpers
17
+ end
14
18
  end
15
19
 
16
20
  initializer "padlock_auth.i18n" do
@@ -1,4 +1,4 @@
1
1
  module PadlockAuth
2
2
  # PadlockAuth version.
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
data/lib/padlock_auth.rb CHANGED
@@ -40,7 +40,7 @@ module PadlockAuth
40
40
 
41
41
  # Rails-specific classes
42
42
  module Rails
43
- autoload :ConnectionHelpers, "padlock_auth/rails/connection_helpers"
43
+ autoload :ActionCableChannelHelpers, "padlock_auth/rails/action_cable_channel_helpers"
44
44
  autoload :Helpers, "padlock_auth/rails/helpers"
45
45
  autoload :TokenFactory, "padlock_auth/rails/token_factory"
46
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padlock_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Morrall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-10 00:00:00.000000000 Z
11
+ date: 2024-12-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -104,6 +104,7 @@ files:
104
104
  - lib/padlock_auth/http/invalid_token_response.rb
105
105
  - lib/padlock_auth/mixins/build_with.rb
106
106
  - lib/padlock_auth/mixins/hide_attribute.rb
107
+ - lib/padlock_auth/rails/action_cable_channel_helpers.rb
107
108
  - lib/padlock_auth/rails/helpers.rb
108
109
  - lib/padlock_auth/rails/token_factory.rb
109
110
  - lib/padlock_auth/railtie.rb