negroni 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|