negroni 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +59 -0
  4. data/Rakefile +64 -0
  5. data/app/mailers/negroni/mailer.rb +45 -0
  6. data/app/views/negroni/mailer/password_change.html.erb +5 -0
  7. data/app/views/negroni/mailer/reset_password_instructions.html.erb +8 -0
  8. data/app/views/negroni/mailer/unlock_instructions.html.erb +7 -0
  9. data/config/locales/en.yml +9 -0
  10. data/config/routes.rb +4 -0
  11. data/lib/negroni.rb +209 -0
  12. data/lib/negroni/configuration.rb +231 -0
  13. data/lib/negroni/controllers/helpers.rb +29 -0
  14. data/lib/negroni/controllers/token_authenticable.rb +20 -0
  15. data/lib/negroni/encryptor.rb +35 -0
  16. data/lib/negroni/engine.rb +35 -0
  17. data/lib/negroni/mailers/helpers.rb +112 -0
  18. data/lib/negroni/models.rb +138 -0
  19. data/lib/negroni/models/authenticable.rb +197 -0
  20. data/lib/negroni/models/base.rb +318 -0
  21. data/lib/negroni/models/lockable.rb +216 -0
  22. data/lib/negroni/models/omniauthable.rb +33 -0
  23. data/lib/negroni/models/recoverable.rb +204 -0
  24. data/lib/negroni/models/registerable.rb +14 -0
  25. data/lib/negroni/models/validatable.rb +63 -0
  26. data/lib/negroni/modules.rb +12 -0
  27. data/lib/negroni/omniauth.rb +25 -0
  28. data/lib/negroni/omniauth/config.rb +81 -0
  29. data/lib/negroni/orm/active_record.rb +7 -0
  30. data/lib/negroni/orm/mongoid.rb +6 -0
  31. data/lib/negroni/param_filter.rb +53 -0
  32. data/lib/negroni/resolver.rb +17 -0
  33. data/lib/negroni/token_generator.rb +58 -0
  34. data/lib/negroni/token_not_found.rb +13 -0
  35. data/lib/negroni/version.rb +6 -0
  36. data/lib/tasks/negroni_tasks.rake +5 -0
  37. metadata +169 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 862ef7ec915b261c58ccae478762ddfaa3d8e786
4
+ data.tar.gz: b4dd5712588c466dcca659bba213cc350cc78fa3
5
+ SHA512:
6
+ metadata.gz: b8e97dfc7fb1c0defc7033859205fb5d6a2619f7cb38f795a4bd042ebcd0d275d331ce1f37bf4b6e13e7a2939f2839758f93b449e550a2928ab08a023329433e
7
+ data.tar.gz: cee2afbb8b7e955fac0fce75f153538fa1993c5a06e0761afa9bd274e33e00a16857473461c77dbbe19faa180819d18e1a14b4ea7c7fbdbce33b16e74a8a9b8b
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 J. Morgan Lieberthal
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.
@@ -0,0 +1,59 @@
1
+ # Negroni
2
+ Negroni is a user-management solution for Rails. It is indended for api-only
3
+ applications.
4
+
5
+ Born out of [devise][devise-gh], it removes all the session-related code,
6
+ focusing on JSON web token authentication, using [knock][knock-gh].
7
+
8
+ As with [devise][devise-gh], both ActiveRecord and Mongoid are supported.
9
+
10
+ ## Rails Version
11
+ This gem was developed with Rails version 5.0.0.1, but should work with anything
12
+ greater that Rails 5.
13
+
14
+ ## Usage
15
+ First, install the gem (see [installation](#Installation) for details.)
16
+
17
+ To install the initializers, run:
18
+
19
+ ```bash
20
+ $ bin/rails generate negroni:install
21
+ ```
22
+
23
+ ## Installation
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'negroni'
28
+ ```
29
+
30
+ And then execute:
31
+ ```bash
32
+ $ bundle
33
+ ```
34
+
35
+ Or install it yourself as:
36
+ ```bash
37
+ $ gem install negroni
38
+ ```
39
+
40
+ ## Documentation
41
+
42
+ Documentation is avaliable by running:
43
+
44
+ ```bash
45
+ $ rake docs
46
+ ```
47
+
48
+ in the root folder of the project. Docs will be placed in doc/.
49
+
50
+
51
+ ## Contributing
52
+ Contribution directions go here.
53
+
54
+ ## License
55
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
56
+
57
+
58
+ [devise-gh]: https://github.com/plataformatec/devise
59
+ [knock-gh]: https://github.com/nsarno/knock
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ APP_RAKEFILE = File.expand_path('../spec/test_app/Rakefile', __FILE__)
10
+ load 'rails/tasks/engine.rake'
11
+
12
+ load 'rails/tasks/statistics.rake'
13
+
14
+ require 'bundler/gem_tasks'
15
+
16
+ ENV['COVERAGE'] ||= 'true'
17
+
18
+ require 'rspec/core/rake_task'
19
+ RSpec::Core::RakeTask.new(:spec) do |t|
20
+ t.pattern = Dir['spec/**/*_spec.rb']
21
+ t.rspec_opts = '-f doc'
22
+ end
23
+
24
+ task default: :spec
25
+
26
+ Dir["#{File.dirname(__FILE__)}/spec/support/orm/*.rb"].each do |file|
27
+ orm = File.basename(file).split('.').first
28
+ # There __must__ be a better way to do this.
29
+ namespace :spec do
30
+ desc "Run RSpec examples with AA_ORM=#{orm}"
31
+ task "orm:#{orm}" do
32
+ exit 1 unless system "AA_ORM=#{orm} rake spec"
33
+ end
34
+
35
+ desc 'Run RSpec examples for all supported orms'
36
+ task 'orm:all' => "spec:orm:#{orm}"
37
+ end
38
+ end
39
+
40
+ task ci: :default
41
+ task ci: 'spec:orm:all'
42
+
43
+ require 'yard/rake/yardoc_task'
44
+
45
+ YARD::Rake::YardocTask.new(:docs) do |t|
46
+ require 'yard'
47
+ t.options = ['--yardopts', '.yardopts']
48
+ t.stats_options = ['--list-undoc']
49
+ end
50
+
51
+ task default: :docs
52
+
53
+ require 'rubocop/rake_task'
54
+
55
+ desc 'Run RuboCop on the app, config, lib, and spec directories'
56
+ RuboCop::RakeTask.new do |t|
57
+ t.patterns = ['{app,config,lib,spec/^test_app}/**/*.rb']
58
+ t.formatters = %w(clang)
59
+ t.fail_on_error = false
60
+ t.requires = ['rubocop-rspec']
61
+ t.options = %w(--config .rubocop.yml)
62
+ end
63
+
64
+ task default: :rubocop
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(ActionMailer)
4
+ module Negroni
5
+ # @!parse
6
+ # # Mailer for all auth-related mail
7
+ # class Mailer < ActionMailer::Base; end
8
+ #
9
+ # The base mailer for all emails
10
+ class Mailer < Negroni.parent_mailer.constantize
11
+ include Negroni::Mailers::Helpers
12
+
13
+ # Sends `record` instructions to reset their password using `token`
14
+ #
15
+ # @param record [Object, #email] the record to send instructions
16
+ # @param token [String] the token needed to reset the password
17
+ # @param options [Hash] a hash of options for the mail
18
+ #
19
+ def reset_password_instructions(record, token, options = {})
20
+ @token = token
21
+ negroni_mail(record, :reset_password_instructions, options)
22
+ end
23
+
24
+ # Sends `record` instructions to unlock their account using `token`
25
+ #
26
+ # @param record [Object, #email] the record to send instructions
27
+ # @param token [String] the token needed to reset the password
28
+ # @param options [Hash] a hash of options for the mail
29
+ #
30
+ def unlock_instructions(record, token, options = {})
31
+ @token = token
32
+ negroni_mail(record, :unlock_instructions, options)
33
+ end
34
+
35
+ # Sends `record` a notification of password changes
36
+ #
37
+ # @param record [Object, #email] the record to send instructions
38
+ # @param options [Hash] a hash of options for the mail
39
+ #
40
+ def password_change(record, options = {})
41
+ negroni_mail(record, :password_change, options)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ <p>Hello <%= @resource.email %></p>
2
+
3
+ <p>We're contacting you to notify you that your password has changed.</p>
4
+
5
+ <p>If you wanted this to happen, you can safely ignore this email. Otherwise, please contact us.</p>
@@ -0,0 +1,8 @@
1
+ <p>Hello <%= @resource.email %></p>
2
+
3
+ <p>Someone has requested a link to change your password. You can do this via the below link.</p>
4
+
5
+ <%# <p><%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %1></p> %>
6
+
7
+ <p>If you didn't request this, please ignore this email.</p>
8
+ <p>Your password won't change until you access the above link and create a new one.</p>
@@ -0,0 +1,7 @@
1
+ <p>Hello <%= @resource.email %>!</p>
2
+
3
+ <p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
4
+
5
+ <p>Click the link below to unlock your account:</p>
6
+
7
+ <%# <p><%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %1></p> %>
@@ -0,0 +1,9 @@
1
+ en:
2
+ errors:
3
+ messages:
4
+ expired: "has expired, please request a new one"
5
+ not_found: "not found"
6
+ not_locked: "was not locked"
7
+ not_saved:
8
+ one: "one (1) error prohibited this %{resource} from being saved:"
9
+ other: "%{count} errors prohibited this %{resource} from being saved:"
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Negroni::Engine.routes.draw do
4
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+ require 'knock'
5
+ require 'active_support/core_ext/numeric/time'
6
+ require 'active_support/dependencies'
7
+ require 'orm_adapter'
8
+ require 'securerandom'
9
+
10
+ require 'negroni/version'
11
+
12
+ # Negroni extracts common authentication configuration to be used across the
13
+ # application.
14
+ module Negroni
15
+ extend ActiveSupport::Autoload
16
+
17
+ autoload :Configuration, 'negroni/configuration'
18
+ autoload :Encryptor, 'negroni/encryptor'
19
+ autoload :OmniAuth, 'negroni/omniauth'
20
+ autoload :ParamFilter, 'negroni/param_filter'
21
+ autoload :Resolver, 'negroni/resolver'
22
+ autoload :TokenGenerator, 'negroni/token_generator'
23
+ autoload :TokenNotFound, 'negroni/token_not_found'
24
+
25
+ # Namespace for controllers
26
+ module Controllers
27
+ autoload :Helpers, 'negroni/controllers/helpers'
28
+ autoload :TokenAuthenticable, 'negroni/controllers/token_authenticable'
29
+ end
30
+
31
+ # Namespace for mailer-related code
32
+ module Mailers
33
+ autoload :Helpers, 'negroni/mailers/helpers'
34
+ end
35
+
36
+ # @api private
37
+ # To hold all modules
38
+ ALL = [] # rubocop:disable Style/MutableConstant
39
+
40
+ extend Configuration::Delegation
41
+
42
+ class << self
43
+ # @!group Private Configuration
44
+
45
+ # Stores OmniAuth configurations
46
+ # @return [Hash<Symbol,OmniAuth::Config>]
47
+ attr_accessor :omniauth_configs
48
+
49
+ # When true, enter in paranoid mode to avoid user enumeration.
50
+ # @return [Boolean]
51
+ attr_accessor :paranoid
52
+
53
+ # Stores the token generator
54
+ # @return [TokenGenerator]
55
+ attr_accessor :token_generator
56
+
57
+ # Stores the secret key
58
+ # @return [String]
59
+ attr_accessor :secret_key
60
+
61
+ # @!attribute [rw] mailer
62
+ # @return [Mailer]
63
+
64
+ # Returns the mailer
65
+ # @return [Mailer]
66
+ def mailer
67
+ @mailer_ref.resolve
68
+ end
69
+
70
+ # Set the mailer class
71
+ def mailer=(new_mailer)
72
+ @mailer_ref = ref(new_mailer)
73
+ end
74
+
75
+ # @!endgroup
76
+
77
+ # Yields the module for configuration. This is the standard way to configure
78
+ # Negroni.
79
+ #
80
+ # @yield [Negroni]
81
+ # @return [Void]
82
+ def configure
83
+ yield self
84
+ end
85
+
86
+ # List of OmniAuth provider that are registered
87
+ # @return [Array<Symbol>]
88
+ def omniauth_providers
89
+ omniauth_configs.keys
90
+ end
91
+
92
+ # Register an OmniAuth provider.
93
+ #
94
+ # @param provider [Symbol] the OmniAuth provider to register
95
+ # @param args [Object*] any additional arguments needed to configure and
96
+ # initialize the provider
97
+ #
98
+ # @example
99
+ # config.omniauth :github, APP_ID, APP_SECRET
100
+ #
101
+ # @return [Void]
102
+ def omniauth(provider, *args)
103
+ config = Negroni::OmniAuth::Config.new(provider, args)
104
+ omniauth_configs[config.strategy_name.to_sym] = config
105
+ end
106
+
107
+ # Generate a friendly string randomly to be used as a token.
108
+ #
109
+ # @note: Taken from `Devise`.
110
+ #
111
+ # @param length [Integer] the length of the token. Default: 20
112
+ # @return [String] the generated token
113
+ def friendly_token(length = 20)
114
+ rlength = (length * 3) / 4
115
+ SecureRandom.urlsafe_base64(rlength).tr('lIO0', 'sxyz')
116
+ end
117
+
118
+ # Securely compare two passwords
119
+ #
120
+ # @param a [String] the hashed password
121
+ # @param b [String] the password to compare
122
+ #
123
+ # @return [Boolean] whether or not the passwords match
124
+ def secure_compare(a, b)
125
+ return false if a.blank? || b.blank? || a.bytesize != b.bytesize
126
+
127
+ l = a.unpack "C#{a.bytesize}"
128
+
129
+ res = 0
130
+ b.each_byte { |byte| res |= byte ^ l.shift }
131
+ res.zero?
132
+ end
133
+
134
+ private
135
+
136
+ # Create a reference
137
+ # @return [Resolver]
138
+ def ref(arg)
139
+ ActiveSupport::Dependencies.reference(arg)
140
+ Resolver.new(arg)
141
+ end
142
+ end
143
+
144
+ self.omniauth_configs = {}
145
+ self.paranoid = false
146
+ self.token_generator = nil
147
+ self.mailer = 'Negroni::Mailer'
148
+
149
+ class << self
150
+ # @!group Module Registration
151
+
152
+ # Register available negroni modules. For the standard modules that Negroni
153
+ # provides, this method is called from lib/negroni/modules.rb. Third-party
154
+ # modules need to be added explicitly using this method.
155
+ #
156
+ # @note that adding a module using this method does not cause it to be used
157
+ # in the authentication process. That requires that the module be listed in
158
+ # the arguments passed to the 'negroni' method in the model class
159
+ # definition.
160
+ #
161
+ # @param module_name [Symbol] the name of the module to register
162
+ # @param options [Hash] a hash of options
163
+ # @option options [String] :model the load path to a custom __model__ for
164
+ # this module (to autoload.)
165
+ # @option options [Symbol, Boolean] :controller the name of an existing or
166
+ # custom __controller__ for this module.
167
+ # @option options [Symbol, Boolean] :route the named __route__ helper for
168
+ # this module.
169
+ #
170
+ # All values which accept a boolean will have the same name as the given
171
+ # module name.
172
+ #
173
+ # @return [Void]
174
+ #
175
+ # @example
176
+ # Negroni.register_module(:party_module)
177
+ # Negroni.register_module(:party_module, model: 'party_module/model')
178
+ # Negroni.register_module(:party_module, insert_at: 0)
179
+ #
180
+ def register_module(module_name, options = {})
181
+ options.assert_valid_keys(:model, :controller, :insert_at)
182
+
183
+ ALL.insert (options[:insert_at] || -1), module_name
184
+
185
+ if (controller = options[:controller])
186
+ register_controller(controller, module_name)
187
+ end
188
+
189
+ options[:model] && register_model(options[:model], module_name)
190
+ end
191
+
192
+ private
193
+
194
+ def register_controller(controller, module_name)
195
+ controller = (controller == true ? module_name : controller)
196
+ CONTROLLERS[module_name] = controller
197
+ end
198
+
199
+ def register_model(model, module_name)
200
+ path = (model == true ? "negroni/models/#{module_name}" : model)
201
+ camelized = ActiveSupport::Inflector.camelize(module_name.to_s)
202
+ Negroni::Models.send(:autoload, camelized.to_sym, path)
203
+ end
204
+ end
205
+ end
206
+
207
+ require 'negroni/models'
208
+ require 'negroni/modules'
209
+ require 'negroni/engine'