slack_sign_in 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab3441efeb6969dabc184dd1410341c307a0160f22bab3b72162a59c80adfd83
4
+ data.tar.gz: 07fe6499c9c387dacdba2723b3270a7f59ab37d078831a788b561701c0a49e8d
5
+ SHA512:
6
+ metadata.gz: 66b5d9e3dc895c00ebe4da4e06b72edbd87fe12ca8232230bef3ebd9b6f0b710d2407c9b20aa2bb32e64dc6c6128389156f8ad2bde6fb38948ce22ed0f47b0c1
7
+ data.tar.gz: 6edbf7498bda10084266dd77c456a35c8f3c52274976420181905f3cd6e25d5e61684c3857ee9335663a001b9f8b6dc78ec0539dfb300e71672c2047a9264662
@@ -0,0 +1,251 @@
1
+ # Slack Sign-In for Rails
2
+
3
+ The goal of this gem is to get you up and running with Slack sign-ins:
4
+
5
+ - with minimal configuration
6
+ - as quickly as possible
7
+ - without sacrificing on long-term stability and maintainability
8
+
9
+ The creation of this gem was heavily inspired by the awesome
10
+ [basecamp/google_sign_in](https://github.com/basecamp/google_sign_in) project,
11
+ both in its aspirations and in its implementation. If you need to add Google
12
+ sign-in to your Rails project, definitely check it out!
13
+
14
+ This project adheres to the Contributor Covenant
15
+ [code of conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to
16
+ uphold this code. Please report unacceptable behavior to
17
+ research@teecom.com.
18
+
19
+ ## Installation
20
+
21
+ Add `slack_sign_in` to your Rails app's Gemfile and run `bundle install`:
22
+
23
+ ```ruby
24
+ gem "slack_sign_in"
25
+ ```
26
+
27
+ *Note:* This gem requires Rails 5.2 or newer.
28
+
29
+ ## Creating a Slack App
30
+
31
+ Before getting started, you'll likely need to set up a Slack application:
32
+
33
+ 1. Go to the [Slack applications list](https://api.slack.com/apps)
34
+
35
+ 2. Either click **Create New App**, or select an existing application
36
+
37
+ 3. Take note of your app's **Client ID** and **Client Secret**
38
+
39
+ <details>
40
+ <summary>Slack app credentials visual guide :framed_picture:</summary>
41
+
42
+ ![Slack App Credentials](./doc/images/app_credentials.png)
43
+ </details>
44
+
45
+ 4. Under the **OAuth & Permissions** tab, add your app's callback URLs to the
46
+ list of **Redirect URLs** section.
47
+
48
+ This gem adds a single OAuth callback to your Rails application at
49
+ `/slack_sign_in/callback`. For a production application, you might add a
50
+ redirect URL of:
51
+
52
+ ```
53
+ https://www.example.com/slack_sign_in/callback
54
+ ```
55
+
56
+ To sign in with Slack in development, you would likely also add a redirect
57
+ URL for your local environment. Something like:
58
+
59
+ ```
60
+ http://localhost:3000/slack_sign_in/callback
61
+ ```
62
+
63
+ <details>
64
+ <summary>Slack app redirect URLs visual guide :framed_picture:</summary>
65
+
66
+ ![Slack App Redirect URLs](./doc/images/redirect_urls.png)
67
+ </details>
68
+
69
+ ## Configuration
70
+
71
+ With your Slack application set up, the next step is to configure your Rails
72
+ app to use it. Run `rails credentials:edit` to edit your app's
73
+ [encrypted credentials](https://guides.rubyonrails.org/security.html#custom-credentials)
74
+ and add the following:
75
+
76
+ ```yaml
77
+ slack_sign_in:
78
+ client_id: "[Your client ID here]"
79
+ client_secret: "[Your client secret here]"
80
+ ```
81
+
82
+ You're all set to use Slack sign-in now. The gem will automatically use these
83
+ client credentials! :tada:
84
+
85
+ Alternatively, you can provide the Slack credentials through an initializer
86
+ and environment variables:
87
+
88
+ ```ruby
89
+ # config/initializers/slack_sign_in.rb
90
+ Rails.application.configure do
91
+ config.slack_sign_in.client_id = ENV.fetch("SLACK_CLIENT_ID")
92
+ config.slack_sign_in.client_secret = ENV.fetch("SLACK_CLIENT_SECRET")
93
+ end
94
+ ```
95
+
96
+ **:warning: Important:** Take care to protect your client secret. It's a secret
97
+ after all!
98
+
99
+ ### Scopes
100
+
101
+ By default, this gem will request the following scopes from Slack:
102
+
103
+ - `identity.basic`
104
+ - `identity.email`
105
+ - `identity.avatar`
106
+
107
+ If these scopes don't suit your particular need, you can configure the gem to
108
+ use any of the
109
+ [supported Slack scopes](https://api.slack.com/docs/sign-in-with-slack#identity_scopes)
110
+ through an initializer:
111
+
112
+ ```ruby
113
+ # config/initializers/slack_sign_in.rb
114
+ Rails.application.configure do
115
+ config.slack_sign_in.scopes = %w(identity.basic identity.team)
116
+ end
117
+ ```
118
+
119
+ ### Mounting Root
120
+
121
+ By default, this gem will mount its routes at `/slack_sign_in`. If this doesn't
122
+ suit your needs, it can be configured through an initializer:
123
+
124
+ ```ruby
125
+ # config/initializers/slack_sign_in.rb
126
+ Rails.application.configure do
127
+ config.slack_sign_in.root = "sso/slack"
128
+ end
129
+ ```
130
+
131
+ In this example, the gem would add a callback URL of `/sso/slack/callback`
132
+ rather than the default of `/slack_sign_in/callback`.
133
+
134
+ ## Usage
135
+
136
+ This gem provides a `slack_sign_in_link` helper that generates a link that will
137
+ kick off the sign-in process:
138
+
139
+ ```erb
140
+ <%= slack_sign_in_link proceed_to: create_session_url %>
141
+
142
+ <%= slack_sign_in_link "Sign In!", proceed_to: create_session_url %>
143
+
144
+ <%= slack_sign_in_link proceed_to: create_session_url do %>
145
+ <div style="background: blue; padding: 10px; display: inline-block;">
146
+ <%= slack_sign_in_image %>
147
+ </div>
148
+ <% end %>
149
+ ```
150
+
151
+ <details>
152
+ <summary>Sign in link visuals :framed_picture:</summary>
153
+
154
+ ![Sign in links](./doc/images/sign_in_links.png)
155
+ </details>
156
+
157
+ Regardless of whether you use the default link, a text link, or a block link,
158
+ the `proceed_to` argument is always required. After authenticating with Slack,
159
+ we'll redirect to this URL with information on the authorization's success or
160
+ failure for your application to handle.
161
+
162
+ In most cases, that might look something like this:
163
+
164
+ ```ruby
165
+ # config/routes.rb
166
+ Rails.application.routes.draw do
167
+ # ...
168
+ get "sessions/create", to: "sessions#create", as: :create_session
169
+ end
170
+ ```
171
+
172
+ ```ruby
173
+ # app/controllers/sessions_controller.rb
174
+ class SessionsController < ApplicationController
175
+ include SlackSignIn::Authorization
176
+
177
+ def create
178
+ if slack_authorization.successful?
179
+ render plain: slack_authorization.identity.name
180
+ else
181
+ render plain: slack_authorization.error
182
+ end
183
+ end
184
+ end
185
+ ```
186
+
187
+ #### The `SlackSignIn::Authorization` Concern
188
+
189
+ The `SlackSignIn::Authorization` concern is the primary interface for accessing
190
+ information about the Slack sign-in process. It exposes a single method,
191
+ `slack_authorization`, which will give you a
192
+ [`SlackSignIn::Result`](#slacksigninresult) for the recently completed Slack
193
+ sign-in flow.
194
+
195
+ In the majority of cases, you should be able to use the
196
+ `SlackSignIn::Authorization` concern along with the `slack_authorization`
197
+ method to accomplish what you want. In some cases, you may need direct access to
198
+ the full Slack response, though.
199
+
200
+ Before redirecting to the specified `proceed_to` URL, this gem will either set
201
+ `flash[:slack_sign_in]["success"]` to the Slack response, or
202
+ `flash[:slack_sign_in]["error"]` to an
203
+ [OAuth authorizaton code grant error](https://tools.ietf.org/html/rfc6749#section-4.1.2.1).
204
+ If you need direct access to the Slack response information, this is how you can
205
+ get it.
206
+
207
+ #### `SlackSignIn::Result`
208
+
209
+ The `SlackSignIn::Result` class provides an interface for handling the result of
210
+ a Slack sign-in attempt. It exposes three instance methods:
211
+
212
+ 1. `successful?` - to determine whether the sign-in attempt succeeded or not
213
+
214
+ 2. `identity` - either `nil` or a [`SlackSignIn::Identity`](#slacksigninidentity)
215
+ instance with user identity information from Slack
216
+
217
+ 3. `error` - either `nil` or an
218
+ [OAuth authorizaton code grant error](https://tools.ietf.org/html/rfc6749#section-4.1.2.1)
219
+
220
+ #### `SlackSignIn::Identity`
221
+
222
+ The `SlackSignIn::Identity` class decodes user identity information from Slack.
223
+ It exposes this information through a handful of instance methods:
224
+
225
+ - `unique_id` - a unique identifier from Slack that can be used to look up
226
+ people
227
+
228
+ - `team_id` - the ID of the team used while signing in
229
+
230
+ - `user_id` - the ID of the user who signed in
231
+
232
+ - `name` - the name of the user who signed in
233
+
234
+ - `email` - the email of the user who signed in
235
+
236
+ - `avatar(size: 48)` - the avatar of the user who signed in
237
+ (in a specific size). Typically the sizes provided by slack are
238
+ `24x24`, `32x32`, `48x48`, `72x72`, `192x192`, and `512x512`.
239
+
240
+ ## Contributing
241
+
242
+ For information on how to contribute to this project, check out the
243
+ [contributing guidelines](./CONTRIBUTING.md).
244
+
245
+ ## Questions?
246
+
247
+ If you have any questions about, or if any of the documentation is unclear,
248
+ please feel free to reach out through a
249
+ [new issue](https://github.com/TEECOM/slack_sign_in/issues/new?labels=documentation%20:writing_hand:).
250
+
251
+ :smiley_cat:
@@ -0,0 +1,18 @@
1
+ begin
2
+ require "bundler/setup"
3
+ require "bundler/gem_tasks"
4
+ rescue LoadError
5
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
6
+ end
7
+
8
+ require "rake/testtask"
9
+ require "standard/rake"
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << "test"
13
+ t.pattern = "test/**/*_test.rb"
14
+ t.verbose = false
15
+ t.warning = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,9 @@
1
+ module SlackSignIn::Authorization
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ def slack_authorization
7
+ @_result ||= SlackSignIn::Result.new(flash[:slack_sign_in])
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module SlackSignIn
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ private
6
+
7
+ def client
8
+ @_client ||= OAuth2::Client.new(
9
+ SlackSignIn.client_id,
10
+ SlackSignIn.client_secret,
11
+ authorize_url: SlackSignIn::AUTHORIZE_URL,
12
+ token_url: SlackSignIn::TOKEN_URL,
13
+ redirect_uri: callback_url,
14
+ )
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ class SlackSignIn::AuthorizationsController < SlackSignIn::ApplicationController
2
+ def create
3
+ redirect_to slack_login_url, flash: {proceed_to: proceed_to, state: state}
4
+ end
5
+
6
+ private
7
+
8
+ def slack_login_url
9
+ client.auth_code.authorize_url(scope: scopes, state: state)
10
+ end
11
+
12
+ def scopes
13
+ SlackSignIn.scopes.join(" ")
14
+ end
15
+
16
+ def proceed_to
17
+ params.require(:proceed_to)
18
+ end
19
+
20
+ def state
21
+ @_state ||= SecureRandom.base64(24)
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ class SlackSignIn::CallbacksController < SlackSignIn::ApplicationController
2
+ def show
3
+ return head(:bad_request) if proceed_to_missing?
4
+
5
+ redirect_to proceed_to_url, flash: {slack_sign_in: slack_sign_in_result}
6
+ end
7
+
8
+ private
9
+
10
+ def proceed_to_missing?
11
+ proceed_to_url.blank?
12
+ end
13
+
14
+ def proceed_to_url
15
+ flash[:proceed_to]
16
+ end
17
+
18
+ def slack_sign_in_result
19
+ if valid_request? && params[:code].present?
20
+ {success: identity_information}
21
+ else
22
+ {error: error_for(params[:error])}
23
+ end
24
+ rescue OAuth2::Error => error
25
+ {error: error_for(error.code)}
26
+ end
27
+
28
+ def valid_request?
29
+ flash[:state].present? && params[:state] == flash[:state]
30
+ end
31
+
32
+ def identity_information
33
+ client.auth_code.get_token(params[:code]).params
34
+ end
35
+
36
+ def error_for(error)
37
+ error || "invalid_request"
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ module SlackSignIn::LinkHelper
2
+ def slack_sign_in_link(text = nil, proceed_to:, **options, &block)
3
+ options = options.merge(method: :post)
4
+ auth_url = slack_sign_in.authorization_path(proceed_to: proceed_to)
5
+
6
+ if text
7
+ link_to(text, auth_url, options)
8
+ elsif block_given?
9
+ link_to(auth_url, options, &block)
10
+ else
11
+ link_to(auth_url, options) { slack_sign_in_image }
12
+ end
13
+ end
14
+
15
+ def slack_sign_in_image
16
+ image_tag(
17
+ "https://platform.slack-edge.com/img/sign_in_with_slack.png",
18
+ alt: "Sign in with Slack",
19
+ height: 40,
20
+ width: 172,
21
+ srcset: {
22
+ "https://platform.slack-edge.com/img/sign_in_with_slack.png" => "1x",
23
+ "https://platform.slack-edge.com/img/sign_in_with_slack@2x.png" => "2x",
24
+ },
25
+ )
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ class SlackSignIn::Identity
2
+ def initialize(params)
3
+ @params = params
4
+ end
5
+
6
+ def unique_id
7
+ "#{team_id}-#{user_id}"
8
+ end
9
+
10
+ def team_id
11
+ params["team_id"]
12
+ end
13
+
14
+ def user_id
15
+ params["user_id"]
16
+ end
17
+
18
+ def name
19
+ params.dig("user", "name")
20
+ end
21
+
22
+ def email
23
+ params.dig("user", "email")
24
+ end
25
+
26
+ def avatar(size: 48)
27
+ params.dig("user", "image_#{size}")
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :params
33
+ end
@@ -0,0 +1,23 @@
1
+ class SlackSignIn::Result
2
+ def initialize(result)
3
+ @result = result || {"error" => "invalid_request"}
4
+ end
5
+
6
+ def successful?
7
+ result.key? "success"
8
+ end
9
+
10
+ def identity
11
+ return unless successful?
12
+
13
+ SlackSignIn::Identity.new(result["success"])
14
+ end
15
+
16
+ def error
17
+ result["error"]
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :result
23
+ end
@@ -0,0 +1,4 @@
1
+ SlackSignIn::Engine.routes.draw do
2
+ resource :authorization, only: :create
3
+ resource :callback, only: :show
4
+ end
@@ -0,0 +1,11 @@
1
+ require "slack_sign_in/engine"
2
+
3
+ module SlackSignIn
4
+ DEFAULT_SCOPES = %w[identity.basic identity.email identity.avatar]
5
+
6
+ AUTHORIZE_URL = "https://slack.com/oauth/authorize"
7
+ TOKEN_URL = "https://slack.com/api/oauth.access"
8
+
9
+ mattr_accessor :client_id, :client_secret
10
+ mattr_accessor :scopes
11
+ end
@@ -0,0 +1,33 @@
1
+ require "oauth2"
2
+
3
+ module SlackSignIn
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace SlackSignIn
6
+
7
+ config.slack_sign_in = ActiveSupport::OrderedOptions.new
8
+
9
+ initializer "slack_sign_in.config" do |app|
10
+ config.after_initialize do
11
+ SlackSignIn.client_id = config.slack_sign_in.client_id || app.credentials.dig(:slack_sign_in, :client_id)
12
+ SlackSignIn.client_secret = config.slack_sign_in.client_secret || app.credentials.dig(:slack_sign_in, :client_secret)
13
+ SlackSignIn.scopes = config.slack_sign_in.scopes || SlackSignIn::DEFAULT_SCOPES
14
+ end
15
+ end
16
+
17
+ initializer "slack_sign_in.helpers" do
18
+ ActiveSupport.on_load :action_controller_base do
19
+ helper SlackSignIn::Engine.helpers
20
+ end
21
+ end
22
+
23
+ initializer "slack_sign_in.mount" do |app|
24
+ app.routes.prepend do
25
+ mount SlackSignIn::Engine, at: app.config.slack_sign_in.root || "slack_sign_in"
26
+ end
27
+ end
28
+
29
+ initializer "slack_sign_in.parameter_filters" do |app|
30
+ app.config.filter_parameters << :code
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module SlackSignIn
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slack_sign_in
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tom Schaefer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: standardrb
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - tommy.schaefer@teecom.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - README.md
105
+ - Rakefile
106
+ - app/controllers/concerns/slack_sign_in/authorization.rb
107
+ - app/controllers/slack_sign_in/application_controller.rb
108
+ - app/controllers/slack_sign_in/authorizations_controller.rb
109
+ - app/controllers/slack_sign_in/callbacks_controller.rb
110
+ - app/helpers/slack_sign_in/link_helper.rb
111
+ - app/models/slack_sign_in/identity.rb
112
+ - app/models/slack_sign_in/result.rb
113
+ - config/routes.rb
114
+ - lib/slack_sign_in.rb
115
+ - lib/slack_sign_in/engine.rb
116
+ - lib/slack_sign_in/version.rb
117
+ homepage: https://github.com/teecom/slack_sign_in
118
+ licenses:
119
+ - MIT
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.0.3
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Sign in (or up) with Slack for Rails applications
140
+ test_files: []