linkedin_sign_in 0.3

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.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +18 -0
  4. data/CHANGELOG.md +3 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +155 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +154 -0
  9. data/Rakefile +40 -0
  10. data/SECURITY.md +3 -0
  11. data/app/controllers/linkedin_sign_in/authorizations_controller.rb +17 -0
  12. data/app/controllers/linkedin_sign_in/base_controller.rb +15 -0
  13. data/app/controllers/linkedin_sign_in/callbacks_controller.rb +27 -0
  14. data/app/helpers/linkedin_sign_in/button_helper.rb +7 -0
  15. data/bin/rails +16 -0
  16. data/config/routes.rb +4 -0
  17. data/lib/linkedin-id-token.rb +190 -0
  18. data/lib/linkedin_sign_in/engine.rb +32 -0
  19. data/lib/linkedin_sign_in/identity.rb +55 -0
  20. data/lib/linkedin_sign_in/redirect_protector.rb +25 -0
  21. data/lib/linkedin_sign_in.rb +10 -0
  22. data/linkedin_sign_in.gemspec +21 -0
  23. data/test/certificate.pem +19 -0
  24. data/test/controllers/authorizations_controller_test.rb +26 -0
  25. data/test/controllers/callbacks_controller_test.rb +36 -0
  26. data/test/dummy/.ruby-version +1 -0
  27. data/test/dummy/Rakefile +6 -0
  28. data/test/dummy/app/assets/config/manifest.js +3 -0
  29. data/test/dummy/app/assets/images/.keep +0 -0
  30. data/test/dummy/app/assets/javascripts/application.js +15 -0
  31. data/test/dummy/app/assets/javascripts/cable.js +13 -0
  32. data/test/dummy/app/assets/javascripts/channels/.keep +0 -0
  33. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  34. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  35. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  36. data/test/dummy/app/controllers/application_controller.rb +2 -0
  37. data/test/dummy/app/controllers/concerns/.keep +0 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/jobs/application_job.rb +2 -0
  40. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  41. data/test/dummy/app/models/application_record.rb +3 -0
  42. data/test/dummy/app/models/concerns/.keep +0 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  44. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  45. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  46. data/test/dummy/bin/bundle +3 -0
  47. data/test/dummy/bin/rails +4 -0
  48. data/test/dummy/bin/rake +4 -0
  49. data/test/dummy/bin/setup +36 -0
  50. data/test/dummy/bin/update +31 -0
  51. data/test/dummy/bin/yarn +11 -0
  52. data/test/dummy/config/application.rb +20 -0
  53. data/test/dummy/config/boot.rb +5 -0
  54. data/test/dummy/config/cable.yml +10 -0
  55. data/test/dummy/config/database.yml +25 -0
  56. data/test/dummy/config/environment.rb +5 -0
  57. data/test/dummy/config/environments/development.rb +32 -0
  58. data/test/dummy/config/environments/production.rb +57 -0
  59. data/test/dummy/config/environments/test.rb +33 -0
  60. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  61. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  63. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  64. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  65. data/test/dummy/config/initializers/inflections.rb +16 -0
  66. data/test/dummy/config/initializers/linkedin_sign_in.rb +4 -0
  67. data/test/dummy/config/initializers/mime_types.rb +4 -0
  68. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  69. data/test/dummy/config/locales/en.yml +33 -0
  70. data/test/dummy/config/puma.rb +34 -0
  71. data/test/dummy/config/routes.rb +2 -0
  72. data/test/dummy/config/spring.rb +6 -0
  73. data/test/dummy/config/storage.yml +34 -0
  74. data/test/dummy/config.ru +5 -0
  75. data/test/dummy/lib/assets/.keep +0 -0
  76. data/test/dummy/log/.keep +0 -0
  77. data/test/dummy/package.json +5 -0
  78. data/test/dummy/public/404.html +67 -0
  79. data/test/dummy/public/422.html +67 -0
  80. data/test/dummy/public/500.html +66 -0
  81. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  82. data/test/dummy/public/apple-touch-icon.png +0 -0
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/helpers/button_helper_test.rb +36 -0
  85. data/test/key.pem +27 -0
  86. data/test/models/identity_test.rb +48 -0
  87. data/test/models/redirect_protector_test.rb +34 -0
  88. data/test/test_helper.rb +28 -0
  89. metadata +267 -0
@@ -0,0 +1,190 @@
1
+ # encoding: utf-8
2
+ # Copyright 2012 Google Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ ##
17
+ # Validates strings alleged to be ID Tokens issued by Google; if validation
18
+ # succeeds, returns the decoded ID Token as a hash.
19
+ # It's a good idea to keep an instance of this class around for a long time,
20
+ # because it caches the keys, performs validation statically, and only
21
+ # refreshes from Google when required (once per day by default)
22
+ #
23
+ # @author Tim Bray, adapted from code by Bob Aman
24
+
25
+ require 'json'
26
+ require 'jwt'
27
+ require 'monitor'
28
+ require 'net/http'
29
+ require 'openssl'
30
+
31
+ module LinkedinIDToken
32
+ class CertificateError < StandardError; end
33
+ class ValidationError < StandardError; end
34
+ class ExpiredTokenError < ValidationError; end
35
+ class SignatureError < ValidationError; end
36
+ class InvalidIssuerError < ValidationError; end
37
+ class AudienceMismatchError < ValidationError; end
38
+ class ClientIDMismatchError < ValidationError; end
39
+
40
+ class Validator
41
+ include MonitorMixin
42
+
43
+ LINKEDIN_CERTS_URI = 'https://www.googleapis.com/oauth2/v1/certs'
44
+ LINKEDIN_CERTS_EXPIRY = 3600 # 1 hour
45
+
46
+ # https://developers.google.com/identity/sign-in/web/backend-auth
47
+ LINKEDIN_ISSUERS = ['accounts.google.com', 'https://accounts.google.com']
48
+
49
+ def initialize(options = {})
50
+ super()
51
+
52
+ if options[:x509_cert]
53
+ @certs_mode = :literal
54
+ @certs = { :_ => options[:x509_cert] }
55
+ # elsif options[:jwk_uri] # TODO
56
+ # @certs_mode = :jwk
57
+ # @certs = {}
58
+ else
59
+ @certs_mode = :old_skool
60
+ @certs = {}
61
+ end
62
+
63
+ @certs_expiry = options.fetch(:expiry, LINKEDIN_CERTS_EXPIRY)
64
+ end
65
+
66
+ ##
67
+ # If it validates, returns a hash with the JWT payload from the ID Token.
68
+ # You have to provide an "aud" value, which must match the
69
+ # token's field with that name, and will similarly check cid if provided.
70
+ #
71
+ # If something fails, raises an error
72
+ #
73
+ # @param [String] token
74
+ # The string form of the token
75
+ # @param [String] aud
76
+ # The required audience value
77
+ # @param [String] cid
78
+ # The optional client-id ("azp" field) value
79
+ #
80
+ # @return [Hash] The decoded ID token
81
+ def check(token, aud, cid = nil)
82
+ synchronize do
83
+ payload = check_cached_certs(token, aud, cid)
84
+
85
+ unless payload
86
+ # no certs worked, might've expired, refresh
87
+ if refresh_certs
88
+ payload = check_cached_certs(token, aud, cid)
89
+
90
+ unless payload
91
+ raise SignatureError, 'Token not verified as issued by Google'
92
+ end
93
+ else
94
+ raise CertificateError, 'Unable to retrieve Google public keys'
95
+ end
96
+ end
97
+
98
+ payload
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ # tries to validate the token against each cached cert.
105
+ # Returns the token payload or raises a ValidationError or
106
+ # nil, which means none of the certs validated.
107
+ def check_cached_certs(token, aud, cid)
108
+ payload = nil
109
+
110
+ # find first public key that validates this token
111
+ @certs.detect do |key, cert|
112
+ begin
113
+ public_key = cert.public_key
114
+ decoded_token = JWT.decode(token, public_key, !!public_key, { :algorithm => 'RS256' })
115
+ payload = decoded_token.first
116
+
117
+ # in Feb 2013, the 'cid' claim became the 'azp' claim per changes
118
+ # in the OIDC draft. At some future point we can go all-azp, but
119
+ # this should keep everything running for a while
120
+ if payload['azp']
121
+ payload['cid'] = payload['azp']
122
+ elsif payload['cid']
123
+ payload['azp'] = payload['cid']
124
+ end
125
+ payload
126
+ rescue JWT::ExpiredSignature
127
+ raise ExpiredTokenError, 'Token signature is expired'
128
+ rescue JWT::DecodeError
129
+ nil # go on, try the next cert
130
+ end
131
+ end
132
+
133
+ if payload
134
+ if !(payload.has_key?('aud') && payload['aud'] == aud)
135
+ raise AudienceMismatchError, 'Token audience mismatch'
136
+ end
137
+ if cid && payload['cid'] != cid
138
+ raise ClientIDMismatchError, 'Token client-id mismatch'
139
+ end
140
+ if !LINKEDIN_ISSUERS.include?(payload['iss'])
141
+ raise InvalidIssuerError, 'Token issuer mismatch'
142
+ end
143
+ payload
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ # returns false if there was a problem
150
+ def refresh_certs
151
+ case @certs_mode
152
+ when :literal
153
+ true # no-op
154
+ when :old_skool
155
+ old_skool_refresh_certs
156
+ # when :jwk # TODO
157
+ # jwk_refresh_certs
158
+ end
159
+ end
160
+
161
+ def old_skool_refresh_certs
162
+ return true unless certs_cache_expired?
163
+
164
+ uri = URI(LINKEDIN_CERTS_URI)
165
+ get = Net::HTTP::Get.new uri.request_uri
166
+ http = Net::HTTP.new(uri.host, uri.port)
167
+ http.use_ssl = true
168
+ res = http.request(get)
169
+
170
+ if res.is_a?(Net::HTTPSuccess)
171
+ new_certs = Hash[JSON.load(res.body).map do |key, cert|
172
+ [key, OpenSSL::X509::Certificate.new(cert)]
173
+ end]
174
+ @certs.merge! new_certs
175
+ @certs_last_refresh = Time.now
176
+ true
177
+ else
178
+ false
179
+ end
180
+ end
181
+
182
+ def certs_cache_expired?
183
+ if defined? @certs_last_refresh
184
+ Time.now > @certs_last_refresh + @certs_expiry
185
+ else
186
+ true
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/engine'
2
+
3
+ module LinkedinSignIn
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace LinkedinSignIn
6
+
7
+ config.linkedin_sign_in = ActiveSupport::OrderedOptions.new
8
+
9
+ initializer 'linkedin_sign_in.config' do |app|
10
+ config.after_initialize do
11
+ LinkedinSignIn.client_id = config.linkedin_sign_in.client_id || app.credentials.dig(:linkedin_sign_in, :client_id)
12
+ LinkedinSignIn.client_secret = config.linkedin_sign_in.client_secret || app.credentials.dig(:linkedin_sign_in, :client_secret)
13
+ end
14
+ end
15
+
16
+ initializer 'linkedin_sign_in.helpers' do
17
+ ActiveSupport.on_load :action_controller_base do
18
+ helper LinkedinSignIn::Engine.helpers
19
+ end
20
+ end
21
+
22
+ initializer 'linkedin_sign_in.mount' do |app|
23
+ app.routes.prepend do
24
+ mount LinkedinSignIn::Engine, at: app.config.linkedin_sign_in.root || 'linkedin_sign_in'
25
+ end
26
+ end
27
+
28
+ initializer 'linkedin_sign_in.parameter_filters' do |app|
29
+ app.config.filter_parameters << :code
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,55 @@
1
+ require 'linkedin-id-token'
2
+ require 'active_support/core_ext/module/delegation'
3
+
4
+ module LinkedinSignIn
5
+ class Identity
6
+ class ValidationError < StandardError; end
7
+
8
+ def initialize(token)
9
+ set_extracted_payload(token)
10
+ end
11
+
12
+ def user_id
13
+ @payload["id"]
14
+ end
15
+
16
+ def first_name
17
+ @payload["firstName"]
18
+ end
19
+
20
+ def last_name
21
+ @payload["lastName"]
22
+ end
23
+
24
+ def email_address
25
+ @payload["emailAddress"]
26
+ end
27
+
28
+ def avatar_url
29
+ @payload["pictureUrl"]
30
+ end
31
+
32
+ def current_company_name
33
+ positions = @payload["positions"]["values"]
34
+ current_position = positions.find { |position| position["isCurrent"] }
35
+ current_position["company"]["name"]
36
+ end
37
+
38
+ private
39
+ def set_extracted_payload(token)
40
+ uri = URI("https://api.linkedin.com/v1/people/~:(id,firstName,lastName,picture-url,email-address,positions)?format=json")
41
+ request = Net::HTTP::Get.new uri
42
+ request['Authorization'] = "Bearer #{token}"
43
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
44
+ http.request(request)
45
+ end
46
+
47
+ case response
48
+ when Net::HTTPSuccess
49
+ @payload = JSON(response.body)
50
+ else
51
+ raise ValidationError, response.body
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,25 @@
1
+ require 'uri'
2
+
3
+ module LinkedinSignIn
4
+ module RedirectProtector
5
+ extend self
6
+
7
+ class Violation < StandardError; end
8
+
9
+ QUALIFIED_URL_PATTERN = /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
10
+
11
+ def ensure_same_origin(target, source)
12
+ if target =~ QUALIFIED_URL_PATTERN && origin_of(target) != origin_of(source)
13
+ raise Violation, "Redirect target #{target} does not have same origin as request (expected #{origin_of(source)})"
14
+ end
15
+ end
16
+
17
+ private
18
+ def origin_of(url)
19
+ uri = URI(url)
20
+ "#{uri.scheme}://#{uri.host}:#{uri.port}"
21
+ rescue ArgumentError
22
+ nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ require 'active_support'
2
+ require 'active_support/rails'
3
+
4
+ module LinkedinSignIn
5
+ mattr_accessor :client_id
6
+ mattr_accessor :client_secret
7
+ end
8
+
9
+ require 'linkedin_sign_in/identity'
10
+ require 'linkedin_sign_in/engine' if defined?(Rails)
@@ -0,0 +1,21 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'linkedin_sign_in'
3
+ s.version = '0.3'
4
+ s.authors = ['Vincent Robert']
5
+ s.email = ['vincent.robert@genezys.net']
6
+ s.summary = 'Sign in (or up) with Linkedin for Rails applications'
7
+ s.homepage = 'https://github.com/genezys/linkedin_sign_in'
8
+ s.license = 'MIT'
9
+
10
+ s.required_ruby_version = '>= 1.9.3'
11
+
12
+ s.add_dependency 'rails', '>= 5.2.0'
13
+ s.add_dependency 'oauth2', '>= 1.4.0'
14
+
15
+ s.add_development_dependency 'bundler', '~> 1.15'
16
+ s.add_development_dependency 'jwt', '>= 1.5.6'
17
+ s.add_development_dependency 'webmock', '>= 3.4.2'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- test/*`.split("\n")
21
+ end
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDETCCAfmgAwIBAQIBADANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDDCRnb29n
3
+ bGUtc2lnbi1pbi1mb3ItcmFpbHMuZXhhbXBsZS5jb20wHhcNMTgwOTAyMjI0MTQw
4
+ WhcNMjMwOTAyMjI0MTQwWjAvMS0wKwYDVQQDDCRnb29nbGUtc2lnbi1pbi1mb3It
5
+ cmFpbHMuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
6
+ AQCtnO1OcLbdxj4f6I/aUMJkCJfrDNvp0v2ljUJoaq6hqWPmoZgcl92njBvt91np
7
+ JPfGaCy0ZYLfizUNBRKkfo6u3MXvubktYqk3SVCejy0TUE11PwRx1u/x0c2rCTa6
8
+ Y7ppAoO9Ur3yoccDmkceP8MpofHWetrdaxyhktlqy6gpM7V+kjj+anySQk4XqDJl
9
+ F4FXHt82HMNK3xbjXJyEoyMudGUISBDn/rG8b3LxEKawUiLVCI54g3+L/Oi4nZCE
10
+ XNCd/mvWpVFoPpFQGMoKW3S9KxFowvfkDSxWYwgYnWnsO0ueXS+WYML88KO1Qf7V
11
+ mEZ91u7w1/EiMVxiuczxycSfAgMBAAGjODA2MAwGA1UdEwEB/wQCMAAwDgYDVR0P
12
+ AQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBBQUA
13
+ A4IBAQCVXynTCZhpbINC4j1prGaPfY6mRkCZzcRpQFim4C6hJtYSRn57qjpmYWG3
14
+ eVc3ElfNUAWgC3trACjN3hDqKv0/hH9TGTY9iFhc747L/VSaKWzH/uWewj1qTwsX
15
+ dUEFxZILAWvAMBNUT060t8bt+pSFc2h4fHsftOqFLfkFUcCr22QsWyueXzWZyDeZ
16
+ XWFGtD+WOR5SC4mIY359e75/vZsJymzZIfM+pfcaHnXtXez9SeLM81rvnRdR1b+H
17
+ /S0LT0dPRkXSvC2HRPwzHxVctNrDoaxON+OIMgd4lHAFs6doVoYmnprzO69+IBUK
18
+ s0LBENxbn2rd7IEl6EaC91cXCl3y
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class LinkedinSignIn::AuthorizationsControllerTest < ActionDispatch::IntegrationTest
4
+ test "redirecting to Linkedin for authorization" do
5
+ post linkedin_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
6
+ assert_response :redirect
7
+ assert_match 'https://www.linkedin.com/oauth/v2/authorization', response.location
8
+
9
+ params = extract_query_params_from(response.location)
10
+ assert_equal FAKE_LINKEDIN_CLIENT_ID, params[:client_id]
11
+ assert_equal 'login', params[:prompt]
12
+ assert_equal 'code', params[:response_type]
13
+ assert_equal 'http://www.example.com/linkedin_sign_in/callback', params[:redirect_uri]
14
+ assert_equal 'r_basicprofile r_emailaddress', params[:scope]
15
+ assert_match /[A-Za-z0-9+\/]{32}/, params[:state]
16
+
17
+ assert_equal 'http://www.example.com/login', flash[:proceed_to]
18
+ assert_equal params[:state], flash[:state]
19
+ end
20
+
21
+ private
22
+ def extract_query_params_from(url)
23
+ query = URI(url).query
24
+ Rack::Utils.parse_query(query).symbolize_keys
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ class LinkedinSignIn::CallbacksControllerTest < ActionDispatch::IntegrationTest
4
+ test "receiving an authorization code" do
5
+ post linkedin_sign_in.authorization_url, params: { proceed_to: 'http://www.example.com/login' }
6
+ assert_response :redirect
7
+
8
+ stub_token_request code: '4/SgCpHSVW5-Cy', access_token: 'ya29.GlwIBo', id_token: 'eyJhbGciOiJSUzI'
9
+
10
+ get linkedin_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: flash[:state])
11
+ assert_redirected_to 'http://www.example.com/login'
12
+ assert_equal 'ya29.GlwIBo', flash[:linkedin_sign_in_token]
13
+ end
14
+
15
+ test "protecting against CSRF" do
16
+ get linkedin_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: 'invalid')
17
+ assert_response :unprocessable_entity
18
+ end
19
+
20
+ test "protecting against open redirects" do
21
+ post linkedin_sign_in.authorization_url, params: { proceed_to: 'http://malicious.example.com/login' }
22
+ assert_response :redirect
23
+
24
+ get linkedin_sign_in.callback_url(code: '4/SgCpHSVW5-Cy', state: flash[:state])
25
+ assert_response :bad_request
26
+ end
27
+
28
+ private
29
+ def stub_token_request(code:, **params)
30
+ stub_request(:post, 'https://www.linkedin.com/oauth/v2/accessToken').
31
+ with(body: { grant_type: 'authorization_code', code: code,
32
+ client_id: FAKE_LINKEDIN_CLIENT_ID, client_secret: FAKE_LINKEDIN_CLIENT_SECRET,
33
+ redirect_uri: 'http://www.example.com/linkedin_sign_in/callback' }).
34
+ to_return(status: 200, headers: { 'Content-Type' => 'application/json' }, body: JSON.generate(params))
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ 2.5.0
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative 'config/application'
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../javascripts .js
3
+ //= link_directory ../stylesheets .css
File without changes
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ // Action Cable provides the framework to deal with WebSockets in Rails.
2
+ // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3
+ //
4
+ //= require action_cable
5
+ //= require_self
6
+ //= require_tree ./channels
7
+
8
+ (function() {
9
+ this.App || (this.App = {});
10
+
11
+ App.cable = ActionCable.createConsumer();
12
+
13
+ }).call(this);
File without changes
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Channel < ActionCable::Channel::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ApplicationCable
2
+ class Connection < ActionCable::Connection::Base
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
File without changes
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: 'from@example.com'
3
+ layout 'mailer'
4
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ self.abstract_class = true
3
+ end
File without changes
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dummy</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag 'application', media: 'all' %>
9
+ <%= javascript_include_tag 'application' %>
10
+ </head>
11
+
12
+ <body>
13
+ <%= yield %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../config/application', __dir__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../config/boot'
3
+ require 'rake'
4
+ Rake.application.run
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+ include FileUtils
4
+
5
+ # path to your application root.
6
+ APP_ROOT = File.expand_path('..', __dir__)
7
+
8
+ def system!(*args)
9
+ system(*args) || abort("\n== Command #{args} failed ==")
10
+ end
11
+
12
+ chdir APP_ROOT do
13
+ # This script is a starting point to setup your application.
14
+ # Add necessary setup steps to this file.
15
+
16
+ puts '== Installing dependencies =='
17
+ system! 'gem install bundler --conservative'
18
+ system('bundle check') || system!('bundle install')
19
+
20
+ # Install JavaScript dependencies if using Yarn
21
+ # system('bin/yarn')
22
+
23
+ # puts "\n== Copying sample files =="
24
+ # unless File.exist?('config/database.yml')
25
+ # cp 'config/database.yml.sample', 'config/database.yml'
26
+ # end
27
+
28
+ puts "\n== Preparing database =="
29
+ system! 'bin/rails db:setup'
30
+
31
+ puts "\n== Removing old logs and tempfiles =="
32
+ system! 'bin/rails log:clear tmp:clear'
33
+
34
+ puts "\n== Restarting application server =="
35
+ system! 'bin/rails restart'
36
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+ include FileUtils
4
+
5
+ # path to your application root.
6
+ APP_ROOT = File.expand_path('..', __dir__)
7
+
8
+ def system!(*args)
9
+ system(*args) || abort("\n== Command #{args} failed ==")
10
+ end
11
+
12
+ chdir APP_ROOT do
13
+ # This script is a way to update your development environment automatically.
14
+ # Add necessary update steps to this file.
15
+
16
+ puts '== Installing dependencies =='
17
+ system! 'gem install bundler --conservative'
18
+ system('bundle check') || system!('bundle install')
19
+
20
+ # Install JavaScript dependencies if using Yarn
21
+ # system('bin/yarn')
22
+
23
+ puts "\n== Updating database =="
24
+ system! 'bin/rails db:migrate'
25
+
26
+ puts "\n== Removing old logs and tempfiles =="
27
+ system! 'bin/rails log:clear tmp:clear'
28
+
29
+ puts "\n== Restarting application server =="
30
+ system! 'bin/rails restart'
31
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path('..', __dir__)
3
+ Dir.chdir(APP_ROOT) do
4
+ begin
5
+ exec "yarnpkg", *ARGV
6
+ rescue Errno::ENOENT
7
+ $stderr.puts "Yarn executable was not detected in the system."
8
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9
+ exit 1
10
+ end
11
+ end