authenticate 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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +21 -0
  6. data/Gemfile.lock +154 -0
  7. data/LICENSE +20 -0
  8. data/README.md +240 -0
  9. data/Rakefile +6 -0
  10. data/app/assets/config/authenticate_manifest.js +0 -0
  11. data/app/assets/images/authenticate/.keep +0 -0
  12. data/app/assets/javascripts/authenticate/.keep +0 -0
  13. data/app/assets/stylesheets/authenticate/.keep +0 -0
  14. data/app/controllers/.keep +0 -0
  15. data/app/helpers/.keep +0 -0
  16. data/app/mailers/.keep +0 -0
  17. data/app/models/.keep +0 -0
  18. data/app/views/.keep +0 -0
  19. data/authenticate.gemspec +38 -0
  20. data/bin/rails +12 -0
  21. data/config/routes.rb +2 -0
  22. data/lib/authenticate.rb +12 -0
  23. data/lib/authenticate/callbacks/authenticatable.rb +4 -0
  24. data/lib/authenticate/callbacks/brute_force.rb +31 -0
  25. data/lib/authenticate/callbacks/lifetimed.rb +5 -0
  26. data/lib/authenticate/callbacks/timeoutable.rb +15 -0
  27. data/lib/authenticate/callbacks/trackable.rb +8 -0
  28. data/lib/authenticate/configuration.rb +144 -0
  29. data/lib/authenticate/controller.rb +110 -0
  30. data/lib/authenticate/crypto/bcrypt.rb +30 -0
  31. data/lib/authenticate/debug.rb +10 -0
  32. data/lib/authenticate/engine.rb +21 -0
  33. data/lib/authenticate/lifecycle.rb +120 -0
  34. data/lib/authenticate/login_status.rb +27 -0
  35. data/lib/authenticate/model/brute_force.rb +51 -0
  36. data/lib/authenticate/model/db_password.rb +71 -0
  37. data/lib/authenticate/model/email.rb +76 -0
  38. data/lib/authenticate/model/lifetimed.rb +48 -0
  39. data/lib/authenticate/model/timeoutable.rb +47 -0
  40. data/lib/authenticate/model/trackable.rb +43 -0
  41. data/lib/authenticate/model/username.rb +45 -0
  42. data/lib/authenticate/modules.rb +61 -0
  43. data/lib/authenticate/session.rb +123 -0
  44. data/lib/authenticate/token.rb +7 -0
  45. data/lib/authenticate/user.rb +50 -0
  46. data/lib/authenticate/version.rb +3 -0
  47. data/lib/tasks/authenticate_tasks.rake +4 -0
  48. data/spec/configuration_spec.rb +60 -0
  49. data/spec/dummy/README.rdoc +28 -0
  50. data/spec/dummy/Rakefile +6 -0
  51. data/spec/dummy/app/assets/images/.keep +0 -0
  52. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  53. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  54. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  55. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  56. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  57. data/spec/dummy/app/mailers/.keep +0 -0
  58. data/spec/dummy/app/models/.keep +0 -0
  59. data/spec/dummy/app/models/concerns/.keep +0 -0
  60. data/spec/dummy/app/models/user.rb +3 -0
  61. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  62. data/spec/dummy/bin/bundle +3 -0
  63. data/spec/dummy/bin/rails +4 -0
  64. data/spec/dummy/bin/rake +4 -0
  65. data/spec/dummy/bin/setup +29 -0
  66. data/spec/dummy/config.ru +4 -0
  67. data/spec/dummy/config/application.rb +26 -0
  68. data/spec/dummy/config/boot.rb +5 -0
  69. data/spec/dummy/config/database.yml +25 -0
  70. data/spec/dummy/config/environment.rb +5 -0
  71. data/spec/dummy/config/environments/development.rb +41 -0
  72. data/spec/dummy/config/environments/production.rb +79 -0
  73. data/spec/dummy/config/environments/test.rb +42 -0
  74. data/spec/dummy/config/initializers/assets.rb +11 -0
  75. data/spec/dummy/config/initializers/authenticate.rb +7 -0
  76. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  77. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  78. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  79. data/spec/dummy/config/initializers/inflections.rb +16 -0
  80. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  81. data/spec/dummy/config/initializers/session_store.rb +3 -0
  82. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  83. data/spec/dummy/config/locales/en.yml +23 -0
  84. data/spec/dummy/config/routes.rb +56 -0
  85. data/spec/dummy/config/secrets.yml +22 -0
  86. data/spec/dummy/db/development.sqlite3 +0 -0
  87. data/spec/dummy/db/migrate/20160120003910_create_users.rb +18 -0
  88. data/spec/dummy/db/schema.rb +31 -0
  89. data/spec/dummy/db/test.sqlite3 +0 -0
  90. data/spec/dummy/lib/assets/.keep +0 -0
  91. data/spec/dummy/log/.keep +0 -0
  92. data/spec/dummy/public/404.html +67 -0
  93. data/spec/dummy/public/422.html +67 -0
  94. data/spec/dummy/public/500.html +66 -0
  95. data/spec/dummy/public/favicon.ico +0 -0
  96. data/spec/factories/users.rb +23 -0
  97. data/spec/model/session_spec.rb +86 -0
  98. data/spec/model/token_spec.rb +11 -0
  99. data/spec/model/user_spec.rb +12 -0
  100. data/spec/orm/active_record.rb +17 -0
  101. data/spec/spec_helper.rb +148 -0
  102. metadata +255 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 95466d6fe7aaa95a7157ca870d2cb7324a11aacf
4
+ data.tar.gz: f10ec192418dff2700252c2c37c99ff0324c18ba
5
+ SHA512:
6
+ metadata.gz: 137399918e6cad2a327fde6bda5c724192d10cc52b7c5682814773641662ab4b58808a48a7a3076e1baf9dde108cc4db942675bdf3072177aa8ab3cb6dc489a8
7
+ data.tar.gz: be34b75f473e5f0ae869c0859710a158c87a92d02bd5d2166d0ebe9527ce02eb58458027bdc1133665d2ee3be4148ddd58cbfeac14618ea548e4156fe76a6973
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3-journal
6
+ test/dummy/log/*.log
7
+ test/dummy/tmp/
8
+ spec/dummy/log/test.log
9
+ spec/dummy/log/development.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rails'
4
+ gem 'sqlite3'
5
+ gem 'pry'
6
+ gem 'factory_girl_rails'
7
+ # gem 'capybara'
8
+
9
+ # Declare your gem's dependencies in authenticate.gemspec.
10
+ # Bundler will treat runtime dependencies like base dependencies, and
11
+ # development dependencies will be added by default to the :development group.
12
+ gemspec
13
+
14
+ # Declare any dependencies that are still in development here instead of in
15
+ # your gemspec. These might include edge Rails or gems from your path or
16
+ # Git. Remember to move these dependencies to your gemspec before releasing
17
+ # your gem to rubygems.org.
18
+
19
+ # To use a debugger
20
+ # gem 'byebug', group: [:development, :test]
21
+
data/Gemfile.lock ADDED
@@ -0,0 +1,154 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ authenticate (0.1.0)
5
+ bcrypt
6
+ email_validator (~> 1.6)
7
+ rails (>= 4.0, < 5.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionmailer (4.2.5)
13
+ actionpack (= 4.2.5)
14
+ actionview (= 4.2.5)
15
+ activejob (= 4.2.5)
16
+ mail (~> 2.5, >= 2.5.4)
17
+ rails-dom-testing (~> 1.0, >= 1.0.5)
18
+ actionpack (4.2.5)
19
+ actionview (= 4.2.5)
20
+ activesupport (= 4.2.5)
21
+ rack (~> 1.6)
22
+ rack-test (~> 0.6.2)
23
+ rails-dom-testing (~> 1.0, >= 1.0.5)
24
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
25
+ actionview (4.2.5)
26
+ activesupport (= 4.2.5)
27
+ builder (~> 3.1)
28
+ erubis (~> 2.7.0)
29
+ rails-dom-testing (~> 1.0, >= 1.0.5)
30
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
31
+ activejob (4.2.5)
32
+ activesupport (= 4.2.5)
33
+ globalid (>= 0.3.0)
34
+ activemodel (4.2.5)
35
+ activesupport (= 4.2.5)
36
+ builder (~> 3.1)
37
+ activerecord (4.2.5)
38
+ activemodel (= 4.2.5)
39
+ activesupport (= 4.2.5)
40
+ arel (~> 6.0)
41
+ activesupport (4.2.5)
42
+ i18n (~> 0.7)
43
+ json (~> 1.7, >= 1.7.7)
44
+ minitest (~> 5.1)
45
+ thread_safe (~> 0.3, >= 0.3.4)
46
+ tzinfo (~> 1.1)
47
+ arel (6.0.3)
48
+ bcrypt (3.1.10)
49
+ builder (3.2.2)
50
+ coderay (1.1.0)
51
+ concurrent-ruby (1.0.0)
52
+ diff-lcs (1.2.5)
53
+ email_validator (1.6.0)
54
+ activemodel
55
+ erubis (2.7.0)
56
+ factory_girl (4.4.0)
57
+ activesupport (>= 3.0.0)
58
+ factory_girl_rails (4.4.1)
59
+ factory_girl (~> 4.4.0)
60
+ railties (>= 3.0.0)
61
+ globalid (0.3.6)
62
+ activesupport (>= 4.1.0)
63
+ i18n (0.7.0)
64
+ json (1.8.3)
65
+ loofah (2.0.3)
66
+ nokogiri (>= 1.5.9)
67
+ mail (2.6.3)
68
+ mime-types (>= 1.16, < 3)
69
+ method_source (0.8.2)
70
+ mime-types (2.99)
71
+ mini_portile2 (2.0.0)
72
+ minitest (5.8.3)
73
+ nokogiri (1.6.7.1)
74
+ mini_portile2 (~> 2.0.0.rc2)
75
+ pry (0.10.3)
76
+ coderay (~> 1.1.0)
77
+ method_source (~> 0.8.1)
78
+ slop (~> 3.4)
79
+ rack (1.6.4)
80
+ rack-test (0.6.3)
81
+ rack (>= 1.0)
82
+ rails (4.2.5)
83
+ actionmailer (= 4.2.5)
84
+ actionpack (= 4.2.5)
85
+ actionview (= 4.2.5)
86
+ activejob (= 4.2.5)
87
+ activemodel (= 4.2.5)
88
+ activerecord (= 4.2.5)
89
+ activesupport (= 4.2.5)
90
+ bundler (>= 1.3.0, < 2.0)
91
+ railties (= 4.2.5)
92
+ sprockets-rails
93
+ rails-deprecated_sanitizer (1.0.3)
94
+ activesupport (>= 4.2.0.alpha)
95
+ rails-dom-testing (1.0.7)
96
+ activesupport (>= 4.2.0.beta, < 5.0)
97
+ nokogiri (~> 1.6.0)
98
+ rails-deprecated_sanitizer (>= 1.0.1)
99
+ rails-html-sanitizer (1.0.2)
100
+ loofah (~> 2.0)
101
+ railties (4.2.5)
102
+ actionpack (= 4.2.5)
103
+ activesupport (= 4.2.5)
104
+ rake (>= 0.8.7)
105
+ thor (>= 0.18.1, < 2.0)
106
+ rake (10.4.2)
107
+ rspec (3.1.0)
108
+ rspec-core (~> 3.1.0)
109
+ rspec-expectations (~> 3.1.0)
110
+ rspec-mocks (~> 3.1.0)
111
+ rspec-core (3.1.7)
112
+ rspec-support (~> 3.1.0)
113
+ rspec-expectations (3.1.2)
114
+ diff-lcs (>= 1.2.0, < 2.0)
115
+ rspec-support (~> 3.1.0)
116
+ rspec-mocks (3.1.3)
117
+ rspec-support (~> 3.1.0)
118
+ rspec-rails (3.1.0)
119
+ actionpack (>= 3.0)
120
+ activesupport (>= 3.0)
121
+ railties (>= 3.0)
122
+ rspec-core (~> 3.1.0)
123
+ rspec-expectations (~> 3.1.0)
124
+ rspec-mocks (~> 3.1.0)
125
+ rspec-support (~> 3.1.0)
126
+ rspec-support (3.1.2)
127
+ slop (3.6.0)
128
+ sprockets (3.5.2)
129
+ concurrent-ruby (~> 1.0)
130
+ rack (> 1, < 3)
131
+ sprockets-rails (3.0.0)
132
+ actionpack (>= 4.0)
133
+ activesupport (>= 4.0)
134
+ sprockets (>= 3.0.0)
135
+ sqlite3 (1.3.11)
136
+ thor (0.19.1)
137
+ thread_safe (0.3.5)
138
+ tzinfo (1.2.2)
139
+ thread_safe (~> 0.1)
140
+
141
+ PLATFORMS
142
+ ruby
143
+
144
+ DEPENDENCIES
145
+ authenticate!
146
+ factory_girl_rails
147
+ pry
148
+ rails
149
+ rspec
150
+ rspec-rails
151
+ sqlite3
152
+
153
+ BUNDLED WITH
154
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Justin Tomich
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # Authenticate
2
+
3
+ A Rails authentication gem.
4
+
5
+ Authenticate is small, simple, but extensible. It has highly opinionated defaults but is
6
+ open to significant modification.
7
+
8
+ Authenticate is inspired by, and draws from, Devise, Warden, Authlogic, Clearance, Sorcery, and restful_authentication.
9
+
10
+
11
+ ## Install
12
+
13
+ Installation is pretty standard. Authenticate does not currently have an automated install process. One is coming.
14
+
15
+ * Include `Authenticate::User` into your `User` model.
16
+ * Include `Authenticate::Controller` into your `ApplicationController`
17
+ * Add an initializer: config/intializers/authenticate.rb containing:
18
+ Authenticate.configure do |config|
19
+ # any settings you wish to tweak, see below
20
+ end
21
+ * Create a migration for any Authenticate features you wish to take advantage of. Here's a good default:
22
+ `rails g migration AddAuthenticateToUsers email:string encrypted_password:string session_token:string
23
+ session_expiration:datetime sign_in_count:integer last_sign_in_at:datetime last_sign_in_ip:string
24
+ last_access_at:datetime current_sign_in_at:datetime current_sign_in_ip:string`
25
+
26
+
27
+ ## Configure
28
+
29
+ Override any of these defaults in your application `config/initializers/authenticate.rb`.
30
+
31
+ ```ruby
32
+ Authenticate.configure do |config|
33
+ config.user_model = 'User'
34
+ config.cookie_name = 'authenticate_session_token'
35
+ config.cookie_expiration = { 1.year.from_now.utc }
36
+ config.cookie_domain = nil
37
+ config.crypto_provider = Bcrypt
38
+ config.timeout_in = nil # 45.minutes
39
+ config.max_session_lifetime = nil # 8.hours
40
+ config.max_consecutive_bad_logins_allowed = nil # 5
41
+ config.bad_login_lockout_period = nil # 5.minutes
42
+ config.authentication_strategy = :email
43
+ ```
44
+
45
+
46
+
47
+ ### timeout_in
48
+
49
+ * timeout_in: the interval to timeout the user session without activity.
50
+
51
+ If your configuration sets timeout_in to a non-nil value, then the last user access is tracked.
52
+ If the interval between the current access time and the last access time is greater than timeout_in,
53
+ the session is invalidated. The user will be prompted for authentication again.
54
+
55
+
56
+
57
+ ### max_session_lifetime
58
+
59
+ * max_session_lifetime: the maximum interval a session is valid, regardless of user activity.
60
+
61
+ If your configuration sets max_session_lifetime, a User session will expire once it has been active for
62
+ max_session_lifetime. The user session is invalidated and the next access will will prompt the user for
63
+ authentication again.
64
+
65
+
66
+
67
+ ### max_consecutive_bad_logins_allowed & bad_login_lockout_period
68
+
69
+ * max_consecutive_bad_logins_allowed: an integer
70
+ * bad_login_lockout_period: a ActiveSupport::CoreExtensions::Numeric::Time
71
+
72
+ To enable brute force protection, set max_consecutive_bad_logins_allowed to a non-nil positive integer.
73
+ The user's consecutive bad logins will be tracked, and if they exceed the allowed maximumm the user's account
74
+ will be locked. The lock will last `bad_login_lockout_period`, which can be any time period (e.g. `10.minutes`).
75
+
76
+
77
+
78
+ ### authentication_strategy
79
+
80
+ The default authentication strategy is :email. This requires that your User model have an attribute named `email`.
81
+ The User account will be identified by this email address. The strategy will add email attribute validation to
82
+ the User, ensuring that it exists, is properly formatted, and is unique.
83
+
84
+ You may instead opt for :username. The username strategy will identify users with an attribute named `username`.
85
+ The strategy will also add username attribute validation, ensuring the username exists and is unique.
86
+
87
+
88
+ ## Use
89
+
90
+ ### Authentication
91
+
92
+ To perform authentication use:
93
+
94
+ * authenticate(params) - authenticate a user with credentials in params, return user if correct.
95
+ `params[:session][:email]` and `params[:session][:password]` are required for the :email authentication
96
+ strategy. `params[:session][:username]` and `params[:session][:password]` are required for
97
+ the :username authentication strategy.
98
+
99
+ * login(user, &block) - log in the just-authenticated user. Login will run all rules as provided in the configuration,
100
+ such as timeout_in detection, max_session_lifetime, etc. You can provide a block to this method to handle the result.
101
+ Your block will receive either {SuccessStatus} or {FailureStatus}.
102
+
103
+ An example session controller:
104
+
105
+ ```ruby
106
+ class SessionsController < ActionController::Base
107
+ include Authenticate::Controller
108
+
109
+ def create
110
+ user = authenticate(params)
111
+ login(user) do |status|
112
+ if status.success?
113
+ flash[:notice] = 'You successfully logged in! Very nice.'
114
+ logger.info flash[:notice].inspect
115
+ redirect_to '/'
116
+ else
117
+ flash[:notice] = status.message
118
+ logger.info flash[:notice].inspect
119
+ render template: 'sessions/new', status: :unauthorized
120
+ end
121
+ end
122
+ end
123
+
124
+
125
+ def new
126
+ end
127
+
128
+ def destroy
129
+ logout
130
+ redirect_to '/', notice: 'You logged out successfully'
131
+ end
132
+ end
133
+ ```
134
+
135
+
136
+ ### Access Control
137
+
138
+ Use the `require_authentication` filter to control access to controller actions.
139
+
140
+ ```ruby
141
+ class ApplicationController < ActionController::Base
142
+ before_action :require_authentication
143
+ end
144
+ ```
145
+
146
+
147
+ ### Helpers
148
+
149
+ Use `current_user` and `authenticated?` in controllers, views, and helpers.
150
+
151
+ Example:
152
+
153
+ ```erb
154
+ <% if authenticated? %>
155
+ <%= current_user.email %>
156
+ <%= button_to "Sign out", sign_out_path, method: :delete %>
157
+ <% else %>
158
+ <%= link_to "Sign in", sign_in_path %>
159
+ <% end %>
160
+ ```
161
+
162
+ ### Logout
163
+
164
+ Log the user out. The user session_token will be deleted from the database, and the session cookie will
165
+ be deleted from the user's browser session.
166
+
167
+ ```ruby
168
+ # in session controller...
169
+ def destroy
170
+ logout
171
+ redirect_to '/', notice: 'You logged out successfully'
172
+ end
173
+ ```
174
+
175
+
176
+ ## Extending Authenticate
177
+
178
+ Authenticate can be extended with two mechanisms:
179
+
180
+ * user modules: add behavior to the user model
181
+ * callbacks: add login during various authentication events, during login and access
182
+
183
+
184
+
185
+ ### User Modules
186
+
187
+ Add behavior to your User model for your callbacks to use. Include them yourself directly in your User class,
188
+ or via the Authentication configuration.
189
+
190
+ Example:
191
+ ```ruby
192
+ Authenticate.configuraton do |config|
193
+ config.modules = [MyUserModule]
194
+ end
195
+ ```
196
+
197
+
198
+ ### Callbacks
199
+
200
+ Callbacks can be added with `after_set_user` or `after_authentication`. See {Authenticate::Lifecycle} for full details.
201
+
202
+ Callbacks can `throw(:failure, message)` to signal an authentication/authorization failure, or perform
203
+ actions on the user or session. Callbacks are passed a block at runtime of `|user, session, options|`.
204
+
205
+
206
+ Example that counts logins for users. It consists of a module for User, and a callback that is
207
+ set in the `included` block. The callback is then added to the User module via the Authenticate configuration.
208
+
209
+ ```ruby
210
+ module LoginCount
211
+ extend ActiveSupport::Concern
212
+
213
+ included do
214
+ # authentication hook
215
+ Authenticate.lifecycle.after_authentication name:'login counter' do |user, session, options|
216
+ user.count_login if user
217
+ end
218
+ end
219
+
220
+ def count_login
221
+ self.login_counter += 1
222
+ end
223
+ end
224
+
225
+ Authenticate.configiration do |config|
226
+ config.modules = [LoginCount]
227
+ end
228
+ ```
229
+
230
+
231
+
232
+ ## Testing
233
+
234
+ Authenticate has been tested with rails 4.2, other versions to follow.
235
+
236
+ ## License
237
+
238
+ This project rocks and uses MIT-LICENSE.
239
+
240
+