slack_sign_in 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []