padlock_auth 0.1.1 → 0.2.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: 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