negroni 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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +59 -0
- data/Rakefile +64 -0
- data/app/mailers/negroni/mailer.rb +45 -0
- data/app/views/negroni/mailer/password_change.html.erb +5 -0
- data/app/views/negroni/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/negroni/mailer/unlock_instructions.html.erb +7 -0
- data/config/locales/en.yml +9 -0
- data/config/routes.rb +4 -0
- data/lib/negroni.rb +209 -0
- data/lib/negroni/configuration.rb +231 -0
- data/lib/negroni/controllers/helpers.rb +29 -0
- data/lib/negroni/controllers/token_authenticable.rb +20 -0
- data/lib/negroni/encryptor.rb +35 -0
- data/lib/negroni/engine.rb +35 -0
- data/lib/negroni/mailers/helpers.rb +112 -0
- data/lib/negroni/models.rb +138 -0
- data/lib/negroni/models/authenticable.rb +197 -0
- data/lib/negroni/models/base.rb +318 -0
- data/lib/negroni/models/lockable.rb +216 -0
- data/lib/negroni/models/omniauthable.rb +33 -0
- data/lib/negroni/models/recoverable.rb +204 -0
- data/lib/negroni/models/registerable.rb +14 -0
- data/lib/negroni/models/validatable.rb +63 -0
- data/lib/negroni/modules.rb +12 -0
- data/lib/negroni/omniauth.rb +25 -0
- data/lib/negroni/omniauth/config.rb +81 -0
- data/lib/negroni/orm/active_record.rb +7 -0
- data/lib/negroni/orm/mongoid.rb +6 -0
- data/lib/negroni/param_filter.rb +53 -0
- data/lib/negroni/resolver.rb +17 -0
- data/lib/negroni/token_generator.rb +58 -0
- data/lib/negroni/token_not_found.rb +13 -0
- data/lib/negroni/version.rb +6 -0
- data/lib/tasks/negroni_tasks.rake +5 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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,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:"
|
data/config/routes.rb
ADDED
data/lib/negroni.rb
ADDED
@@ -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'
|