deacon-rails 1.0.0 → 1.1.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: b9b67729a41edf00bed6786b7b452d46280d312c38add74d0a8dba996c9110b2
4
- data.tar.gz: 2424fcf30dc687f72f2438a837db3b90560eda33c6f8a89a22ace6c756a4e682
3
+ metadata.gz: da8d6aadcc7b35006f1a904adda812bafc11bd4ae63295547447e5d905b8acbd
4
+ data.tar.gz: 4f7075550ef183de2d723857692b7f52e97ed7f16036d406bc1556ae7db87241
5
5
  SHA512:
6
- metadata.gz: d58bd949062b18345c26f8a706486d5deea5e9d5e956b36861113f9c8525fc5e9326bbb4199affd8a4b8ba0b4b61b03e87184648ae5db5f78b45fb799d0f1ed4
7
- data.tar.gz: 992cd18343847745069d576eefc7443854c6d8117edfc568fd1a5a3fa08fa3632200d6626fd313c47b8984e35219bf0ea1c2a795e8e3defe79431e72e38d1b6e
6
+ metadata.gz: c6298ba5b22ba7d1e27f84f114213740492f39c1f823e7cfe4e9f9e6a63e1cd0f862231d316f10c3cf96ee44b816366b4fff377325dbdcca471b8c7f060dd077
7
+ data.tar.gz: 5706b3a17954523dea7b58e84dc141b7042e5911c2cd4f287603bec5925d2280eb16d3a247e3149ac7861067b4b9b9b42a0b33d2ce293662802b263a72ec9e74
data/README.md CHANGED
@@ -40,31 +40,86 @@ Deacon only runs on the sites listed on Install. A listed domain covers its subd
40
40
 
41
41
  When you run `bin/rails server`, Install notices the widget on `localhost:3000` and lets you add it in one click. Chats on localhost don't count towards your plan.
42
42
 
43
- If you tell Deacon who is signed in, with the `window.deaconUser` script from [Deacon's install guide](https://heydeacon.com/agent/install.md), put that script above `<%= deacon_widget_tag %>` in the head, because the widget reads it as it starts.
43
+ ## Who is signed in
44
+
45
+ When someone is signed in to your app, the tag tells Deacon their id, email and name, so their conversations show who they are. To have Deacon trust that, rather than take the page's word for it, give the gem your secret in the `DEACON_SECRET` environment variable:
46
+
47
+ ```sh
48
+ DEACON_SECRET=deacon_identity_your_secret
49
+ ```
50
+
51
+ To make one, open Install in Deacon and use Make a secret under Verify who's signed in. You see it once. The key the gem signs with is the whole string exactly as Install shows it, `deacon_identity_` and all, not decoded or shortened. Keep it on the server: unlike the `pk_` key, anyone who has it can tell Deacon they are any of your users.
52
+
53
+ With the secret set, the gem does the rest:
54
+
55
+ - Above the widget's tag it prints `window.deaconUser`, with the user's id, email and name and a `token` function.
56
+ - When the chat opens, `token` posts to `/deacon/token`, a route the gem adds to your app. It answers with a token signed with `DEACON_SECRET` that names the user and this chat and lasts 10 minutes. With nobody signed in, or no secret, it answers 204 and signs nothing. Any other failure, such as a 404 or a 500, makes `token` throw, so Deacon can tell you the route is broken rather than take it for a sign-out. Its answers are never cached and carry no CORS headers, so only your own pages can read them.
57
+ - On a page where nobody is signed in, it prints `window.deaconUser = null` and `Deacon.identify(null)` instead, so signing out ends what Deacon knew about the last person, and the next person on that computer starts fresh.
58
+
59
+ If your layout sets `window.deaconUser` itself, from an earlier version of Deacon's install guide, take that script out: the tag prints it now.
60
+
61
+ ### Finding the user
62
+
63
+ The gem asks your controllers for `current_user`, which Devise and most sign-in gems provide. Rails 8's built-in authentication has no `current_user`, so tell the gem how to find the user with a lambda, which runs inside the controller:
64
+
65
+ ```ruby
66
+ Rails.application.config.deacon.current_user = -> { Current.user if authenticated? }
67
+ ```
68
+
69
+ Or give it the name of a method, such as `:current_member`. The id it sends is the user's `id`, so it must name one person across your whole app. The email is the user's `email`, or `email_address` with Rails 8's built-in authentication, and the name is their `name`, when they have one.
70
+
71
+ The token route's controller inherits your `ApplicationController`, so it finds the user just as your pages do. It skips `require_authentication` (Rails 8) and `authenticate_user!` (Devise), so someone who has signed out gets a 204 rather than a redirect to your sign-in page. If your `ApplicationController` requires sign-in under another name, skip that for the route too:
72
+
73
+ ```ruby
74
+ Rails.application.config.to_prepare do
75
+ Deacon::Rails::TokensController.skip_before_action :require_login
76
+ end
77
+ ```
78
+
79
+ ### CSRF
80
+
81
+ The route checks your app's CSRF token, as every other POST in your app does, and the `token` function sends it in the `X-CSRF-Token` header. The route changes nothing, and with no CORS headers another site couldn't read its answer, so exempting it would be safe too. Keeping the check means a page on another site can't get your server to sign anything at all, and nothing in your app is exempt from it.
82
+
83
+ ### Staging and development
84
+
85
+ Give staging its own Deacon team, with its own key and secret, or leave `DEACON_SECRET` unset there so the route answers 204. Never copy production's secret to staging or a laptop: whoever runs that server could sign a token for any of your users.
86
+
87
+ ### Signing in in the browser
88
+
89
+ If your app learns who is signed in in the browser, such as a React front end on a JSON API, the server sees nobody on every page, and the sign-out line would start everyone afresh on every page load. Turn it off, and tell Deacon from your own code with `Deacon.identify`, as [Deacon's install guide](https://heydeacon.com/agent/install.md) shows:
90
+
91
+ ```ruby
92
+ Rails.application.config.deacon.signed_in = false
93
+ ```
44
94
 
45
95
  ## Options
46
96
 
47
97
  Set them on `config.deacon`, in an initializer or in `config/application.rb`.
48
98
 
49
- | Option | Type | Default | What it does |
50
- | ------------ | ------- | ------------ | ------------ |
51
- | `key` | string | `DEACON_KEY` | Your Deacon key. |
52
- | `context` | hash | none | Details Deacon takes into account when it answers, the same on every page, such as `{ product: "api", docs_version: "3.2" }`. Up to 12 keys, each value a string, number or boolean. Anyone can read them in the page's HTML, so never put a secret here. |
53
- | `page_views` | boolean | `true` | The same script counts visits for Deacon's website analytics. Set this to `false` to send no page views or JavaScript errors from your app. Chat still works. |
99
+ | Option | Type | Default | What it does |
100
+ | -------------- | ----------------- | -------------- | ------------ |
101
+ | `key` | string | `DEACON_KEY` | Your Deacon key. |
102
+ | `context` | hash | none | Details Deacon takes into account when it answers, the same on every page, such as `{ product: "api", docs_version: "3.2" }`. Up to 12 keys, each value a string, number or boolean. Anyone can read them in the page's HTML, so never put a secret here. |
103
+ | `page_views` | boolean | `true` | The same script counts visits for Deacon's website analytics. Set this to `false` to send no page views or JavaScript errors from your app. Chat still works. |
104
+ | `signed_in` | boolean | `true` | Tell Deacon who is signed in, and sign them out of Deacon on pages where nobody is. Set this to `false` if your app signs people in in the browser. |
105
+ | `current_user` | symbol or lambda | `current_user` | How to find the signed-in user: the name of a method on your controllers, or a lambda that runs inside one. |
54
106
 
55
107
  ```ruby
56
108
  Rails.application.config.deacon.key = "pk_your_widget_key"
57
109
  Rails.application.config.deacon.context = { product: "api", docs_version: "3.2" }
58
110
  Rails.application.config.deacon.page_views = false
111
+ Rails.application.config.deacon.current_user = -> { Current.user if authenticated? }
59
112
  ```
60
113
 
114
+ The secret has no option, only `DEACON_SECRET`, so it stays out of your code.
115
+
61
116
  ## Turbo
62
117
 
63
- Turbo Drive swaps the `<body>` on each visit and keeps the `<head>`, so in the head the tag runs once. Deacon puts its chat on `<html>`, outside the body, so the widget stays in place as people move between pages.
118
+ Turbo Drive swaps the `<body>` on each visit and keeps the `<head>`, so in the head the tag runs once. Deacon puts its chat on `<html>`, outside the body, so the widget stays in place as people move between pages. The signed-in script is different on every page, so Turbo runs it on each visit, and a sign-in or sign-out without a full page load still reaches Deacon.
64
119
 
65
120
  ## Content Security Policy
66
121
 
67
- If your app gives each request a nonce, with `config.content_security_policy_nonce_generator`, the tag carries it. The policy also needs Deacon's address, `https://heydeacon.com`, in `script-src`, `connect-src` and `frame-src`, and `'unsafe-inline'` in `style-src`, because the widget writes its styles where a nonce can't reach. Keep the nonce to `script-src`, since a nonce in `style-src` makes browsers ignore `'unsafe-inline'`:
122
+ If your app gives each request a nonce, with `config.content_security_policy_nonce_generator`, the tag carries it, on the signed-in script too. With Turbo, also put `<%= csp_meta_tag %>` in the head, which Turbo reads to run the signed-in script again on each visit. The policy also needs Deacon's address, `https://heydeacon.com`, in `script-src`, `connect-src` and `frame-src`, and `'unsafe-inline'` in `style-src`, because the widget writes its styles where a nonce can't reach. Keep the nonce to `script-src`, since a nonce in `style-src` makes browsers ignore `'unsafe-inline'`:
68
123
 
69
124
  ```ruby
70
125
  Rails.application.configure do
@@ -92,7 +147,30 @@ The helper prints the same tag Install gives you:
92
147
  <script src="https://heydeacon.com/widget/v1/loader.js" data-key="pk_your_widget_key" async></script>
93
148
  ```
94
149
 
95
- It loads in the background without holding up the page. Works with Rails 8.0 and later, on Ruby 3.3 and later.
150
+ It loads in the background without holding up the page. Above it, when someone is signed in, goes the signed-in script, here spread over lines:
151
+
152
+ ```html
153
+ <script data-deacon-render="3f9c0a71d2e4b856">
154
+ window.deaconUser = {
155
+ id: "7",
156
+ email: "ana@studio.com",
157
+ name: "Ana Ruiz",
158
+ token: (nonce) => fetch("/deacon/token", {
159
+ method: "POST",
160
+ credentials: "same-origin",
161
+ headers: { "Content-Type": "application/json", "X-CSRF-Token": "…" },
162
+ body: JSON.stringify({ nonce }),
163
+ }).then((r) => {
164
+ if (r.status === 204) return "";
165
+ if (!r.ok) throw new Error("Deacon token route answered " + r.status);
166
+ return r.text();
167
+ }),
168
+ };
169
+ window.Deacon?.identify(window.deaconUser);
170
+ </script>
171
+ ```
172
+
173
+ Works with Rails 8.0 and later, on Ruby 3.3 and later.
96
174
 
97
175
  ## License
98
176
 
@@ -0,0 +1,52 @@
1
+ require "jwt"
2
+
3
+ module Deacon
4
+ module Rails
5
+ # POST /deacon/token, which the page's token function calls when the chat opens: a token
6
+ # saying who is signed in, signed with DEACON_SECRET, for Deacon to check. It inherits the
7
+ # app's ApplicationController, so it finds the user as the app's own pages do and checks the
8
+ # CSRF token as every other POST in the app does.
9
+ class TokensController < ::ApplicationController
10
+ # How long a token lasts. Deacon refuses one that lasts more than 15 minutes.
11
+ LIFETIME = 10 * 60
12
+
13
+ # The widget session's nonce, which the token carries so it proves who is signed in to that
14
+ # session and no other. Deacon's are hex; anything far from that isn't one.
15
+ NONCE_FORMAT = /\A[A-Za-z0-9_-]{1,128}\z/
16
+
17
+ MISSING_SECRET = "deacon-rails: DEACON_SECRET is missing, so the token route signs nobody " \
18
+ "and Deacon can't verify who is signed in. In Deacon, open Install (the " \
19
+ "button at the top of the dashboard) and make a secret under Verify who's " \
20
+ "signed in. It starts with deacon_identity_.".freeze
21
+
22
+ # The route answers for nobody signed in itself, with a 204, so the app's sign-in gates must
23
+ # not send that request to a sign-in page: Rails 8's built-in authentication's and Devise's.
24
+ skip_before_action :require_authentication, :authenticate_user!, raise: false
25
+
26
+ def create
27
+ # A token names one person, so no cache in front of the app may keep it for another.
28
+ response.headers["Cache-Control"] = "no-store, private"
29
+
30
+ nonce = request.request_parameters["nonce"]
31
+ return head(:bad_request) unless nonce.is_a?(String) && NONCE_FORMAT.match?(nonce)
32
+
33
+ user = CurrentUser.find(self)
34
+ return head(:no_content) unless user.is_a?(CurrentUser::Details)
35
+
36
+ # The key is the whole secret as Deacon showed it, deacon_identity_ and all.
37
+ secret = ENV["DEACON_SECRET"].to_s.strip
38
+
39
+ if secret.empty?
40
+ ::Rails.logger&.warn(MISSING_SECRET)
41
+ return head(:no_content)
42
+ end
43
+
44
+ now = Time.now.to_i
45
+ claims = { sub: user.id, email: user.email, name: user.name, nonce: nonce, iat: now,
46
+ exp: now + LIFETIME }
47
+
48
+ render plain: JWT.encode(claims.compact, secret, "HS256")
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ module Deacon
2
+ module Rails
3
+ # Who is signed in to the app, found the same way for the page and for the token route.
4
+ module CurrentUser
5
+ # The signed-in user as Deacon is told them: the id as a string, and the email and name
6
+ # when the user has them.
7
+ Details = Struct.new(:id, :email, :name)
8
+
9
+ # The signed-in user's Details, nil when nobody is signed in, or :unknown when the app has
10
+ # no way to say who is. config.deacon.current_user names a method on the controller, or is
11
+ # a lambda run inside it; without one, the controller's own current_user is used.
12
+ def self.find(controller)
13
+ lookup = ::Rails.application.config.deacon.current_user
14
+ lookup ||= :current_user if controller.respond_to?(:current_user, true)
15
+ return :unknown if lookup.nil?
16
+
17
+ user = case lookup
18
+ when Proc then controller.instance_exec(&lookup)
19
+ when Symbol, String then controller.send(lookup)
20
+ else
21
+ raise ArgumentError, "deacon-rails: config.deacon.current_user must be a method " \
22
+ "name or a lambda."
23
+ end
24
+
25
+ details(user) if user
26
+ end
27
+
28
+ # Rails 8's built-in authentication names the address email_address. A user with no id,
29
+ # such as a guest object or one never saved, names nobody, so it counts as nobody.
30
+ def self.details(user)
31
+ id = user.id.to_s
32
+ return if id.empty?
33
+
34
+ email = if user.respond_to?(:email) then user.email
35
+ elsif user.respond_to?(:email_address) then user.email_address
36
+ end
37
+ name = user.name if user.respond_to?(:name)
38
+
39
+ Details.new(id, email.to_s.presence, name.to_s.presence)
40
+ end
41
+ private_class_method :details
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module Deacon
2
+ module Rails
3
+ # What config.deacon holds. A Struct rather than ordered options, so a misspelt setting
4
+ # fails loudly at boot instead of being ignored.
5
+ Configuration = Struct.new(:key, :context, :page_views, :signed_in, :current_user)
6
+
7
+ # Adds config.deacon to the app, deacon_widget_tag to its views and POST /deacon/token to its
8
+ # routes. An engine rather than a plain Railtie, so the app loads the token route's
9
+ # controller with its own, after the ApplicationController it inherits.
10
+ class Engine < ::Rails::Engine
11
+ config.deacon = Configuration.new
12
+
13
+ initializer "deacon.view_helper" do
14
+ ActiveSupport.on_load(:action_view) { include Deacon::Rails::Helper }
15
+ end
16
+
17
+ # Ahead of the app's own routes, so a catch-all route of its own never answers instead.
18
+ initializer "deacon.token_route" do |app|
19
+ app.routes.prepend do
20
+ post "/deacon/token" => "deacon/rails/tokens#create", as: :deacon_token
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,5 +1,6 @@
1
1
  require "erb"
2
2
  require "json"
3
+ require "securerandom"
3
4
 
4
5
  module Deacon
5
6
  module Rails
@@ -25,18 +26,85 @@ module Deacon
25
26
 
26
27
  context = deacon_config.context
27
28
  page_views = deacon_config.page_views
29
+ signed_in = deacon_config.signed_in
28
30
  deacon_check_context(context)
29
- deacon_check_page_views(page_views)
31
+ deacon_check_boolean(:page_views, page_views)
32
+ deacon_check_boolean(:signed_in, signed_in)
30
33
 
31
34
  attributes = { "data-key" => key }
32
35
  attributes["data-context"] = JSON.generate(context) unless context.nil?
33
36
  attributes["data-no-page-views"] = true if page_views == false
34
37
 
35
- deacon_loader_tag(attributes, deacon_csp_nonce)
38
+ csp_nonce = deacon_csp_nonce
39
+ script = signed_in == false ? nil : deacon_signed_in_script(csp_nonce)
40
+
41
+ "#{script}#{deacon_loader_tag(attributes, csp_nonce)}".html_safe
36
42
  end
37
43
 
38
44
  private
39
45
 
46
+ # Who is signed in, for the loader to read as it starts, so it goes above the loader tag.
47
+ # The token function asks the app's own POST /deacon/token to sign them when the chat
48
+ # opens, sending the CSRF token as every other POST in the app does. With nobody signed
49
+ # in it signs the visitor out, so a sign-out ends Deacon's proof of who they were. An app
50
+ # with no way to say who is signed in gets neither.
51
+ def deacon_signed_in_script(csp_nonce)
52
+ user = CurrentUser.find(controller)
53
+ return if user == :unknown
54
+
55
+ code = if user.nil?
56
+ "window.deaconUser = null; window.Deacon?.identify(null);"
57
+ else
58
+ "window.deaconUser = #{deacon_user_object(user)}; " \
59
+ "window.Deacon?.identify(window.deaconUser);"
60
+ end
61
+
62
+ deacon_script(code, csp_nonce)
63
+ end
64
+
65
+ # The user's id, email and name, and the token function: written as code rather than
66
+ # JSON, since a function has no JSON. A 204 is nobody signed in. Any other failure throws,
67
+ # so Deacon can tell the founder the route is broken instead of taking it for a sign-out.
68
+ def deacon_user_object(user)
69
+ headers = { "Content-Type" => "application/json" }
70
+ headers["X-CSRF-Token"] = form_authenticity_token if protect_against_forgery?
71
+ path = ::Rails.application.routes.url_helpers.deacon_token_path
72
+
73
+ token = "(nonce) => fetch(#{deacon_js(path)}, { method: \"POST\", " \
74
+ "credentials: \"same-origin\", headers: #{deacon_js_object(headers)}, " \
75
+ "body: JSON.stringify({ nonce }) })" \
76
+ '.then((r) => { if (r.status === 204) return ""; ' \
77
+ 'if (!r.ok) throw new Error("Deacon token route answered " + r.status); ' \
78
+ "return r.text(); })"
79
+
80
+ fields = { id: user.id, email: user.email, name: user.name }.compact
81
+ fields = fields.map { |name, value| "#{name}: #{deacon_js(value)}" }
82
+
83
+ "{ #{[*fields, "token: #{token}"].join(', ')} }"
84
+ end
85
+
86
+ # A string as JavaScript, escaped so nothing in it can end the script it sits in.
87
+ def deacon_js(value)
88
+ ERB::Util.json_escape(JSON.generate(value.to_s))
89
+ end
90
+
91
+ def deacon_js_object(hash)
92
+ "{ #{hash.map { |name, value| "#{deacon_js(name)}: #{deacon_js(value)}" }.join(', ')} }"
93
+ end
94
+
95
+ # Turbo Drive keeps the head between visits and runs a script in the new page's head only
96
+ # when none just like it is there, so each page's is made unlike the last one's. Otherwise a
97
+ # sign-in or sign-out back to someone the head has seen before would never reach Deacon.
98
+ def deacon_script(code, csp_nonce)
99
+ render = SecureRandom.hex(8)
100
+
101
+ %(<script data-deacon-render="#{render}"#{deacon_nonce_attribute(csp_nonce)}>#{code}</script>)
102
+ end
103
+
104
+ def deacon_nonce_attribute(csp_nonce)
105
+ csp_nonce.nil? ? "" : %( nonce="#{ERB::Util.html_escape(csp_nonce)}")
106
+ end
107
+
40
108
  def deacon_config
41
109
  ::Rails.application.config.deacon
42
110
  end
@@ -56,10 +124,10 @@ module Deacon
56
124
  "are strings, numbers or booleans."
57
125
  end
58
126
 
59
- def deacon_check_page_views(page_views)
60
- return if page_views.nil? || page_views == true || page_views == false
127
+ def deacon_check_boolean(name, value)
128
+ return if value.nil? || value == true || value == false
61
129
 
62
- raise ArgumentError, "deacon-rails: config.deacon.page_views must be true or false."
130
+ raise ArgumentError, "deacon-rails: config.deacon.#{name} must be true or false."
63
131
  end
64
132
 
65
133
  def deacon_scalar?(value)
@@ -68,14 +136,13 @@ module Deacon
68
136
 
69
137
  # The tag Install gives, with any of its other attributes between the key and async, and
70
138
  # the nonce after them. An attribute that is true is written with no value, as async is.
71
- def deacon_loader_tag(attributes, nonce)
139
+ def deacon_loader_tag(attributes, csp_nonce)
72
140
  rendered = attributes.map do |name, value|
73
141
  value == true ? " #{name}" : %( #{name}="#{ERB::Util.html_escape(value)}")
74
142
  end.join
75
143
 
76
- nonce_attribute = nonce.nil? ? "" : %( nonce="#{ERB::Util.html_escape(nonce)}")
77
-
78
- %(<script src="#{LOADER_SRC}"#{rendered} async#{nonce_attribute}></script>).html_safe
144
+ %(<script src="#{LOADER_SRC}"#{rendered} async#{deacon_nonce_attribute(csp_nonce)}></script>)
145
+ .html_safe
79
146
  end
80
147
  end
81
148
  end
@@ -1,5 +1,5 @@
1
1
  module Deacon
2
2
  module Rails
3
- VERSION = "1.0.0"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
data/lib/deacon/rails.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require "deacon/rails/version"
2
+ require "deacon/rails/current_user"
2
3
  require "deacon/rails/helper"
3
- require "deacon/rails/railtie" if defined?(::Rails::Railtie)
4
+ require "deacon/rails/engine" if defined?(::Rails::Engine)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deacon-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deacon
@@ -23,6 +23,26 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '8.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: jwt
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.8'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '4'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '2.8'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '4'
26
46
  - !ruby/object:Gem::Dependency
27
47
  name: railties
28
48
  requirement: !ruby/object:Gem::Requirement
@@ -38,9 +58,10 @@ dependencies:
38
58
  - !ruby/object:Gem::Version
39
59
  version: '8.0'
40
60
  description: A view helper, deacon_widget_tag, that puts the Deacon chat widget on
41
- every page of a Rails app from one line in the layout. Deacon is an AI support agent
42
- that answers questions from your own docs and content, and says when it doesn't
43
- know.
61
+ every page of a Rails app from one line in the layout, and tells Deacon who is signed
62
+ in, with a token route that signs them with your secret so Deacon can trust it.
63
+ Deacon is an AI support agent that answers questions from your own docs and content,
64
+ and says when it doesn't know.
44
65
  email:
45
66
  - hello@heydeacon.com
46
67
  executables: []
@@ -49,9 +70,11 @@ extra_rdoc_files: []
49
70
  files:
50
71
  - LICENSE
51
72
  - README.md
73
+ - app/controllers/deacon/rails/tokens_controller.rb
52
74
  - lib/deacon/rails.rb
75
+ - lib/deacon/rails/current_user.rb
76
+ - lib/deacon/rails/engine.rb
53
77
  - lib/deacon/rails/helper.rb
54
- - lib/deacon/rails/railtie.rb
55
78
  - lib/deacon/rails/version.rb
56
79
  homepage: https://heydeacon.com
57
80
  licenses:
@@ -1,16 +0,0 @@
1
- module Deacon
2
- module Rails
3
- # What config.deacon holds. A Struct rather than ordered options, so a misspelt setting
4
- # fails loudly at boot instead of being ignored.
5
- Configuration = Struct.new(:key, :context, :page_views)
6
-
7
- # Adds config.deacon to the app and deacon_widget_tag to its views.
8
- class Railtie < ::Rails::Railtie
9
- config.deacon = Configuration.new
10
-
11
- initializer "deacon.view_helper" do
12
- ActiveSupport.on_load(:action_view) { include Deacon::Rails::Helper }
13
- end
14
- end
15
- end
16
- end