devise-passwordless 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/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/devise-passwordless.gemspec +45 -0
- data/lib/devise/models/email_authenticatable.rb +53 -0
- data/lib/devise/passwordless.rb +3 -0
- data/lib/devise/passwordless/login_token.rb +57 -0
- data/lib/devise/passwordless/mailer.rb +9 -0
- data/lib/devise/passwordless/version.rb +5 -0
- data/lib/devise/strategies/email_authenticatable.rb +63 -0
- data/lib/generators/devise/passwordless/install_generator.rb +42 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '08511edac883e2b94811e349d41ce761a7d5771e55b628450bd1e02d835f0374'
|
4
|
+
data.tar.gz: 9d0f442ffed9f59818a370c8bdc815f362367bd24a6c09fc66b0097009992cd9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 405e6a4ee5dbb3c66dcd079a92642e01dfb10b336d5e220f8a05f0814a2ac50ef47e78aa13e734b4aec4ce2891eb8cde900854984c3c84193afb1ff05e5b8481
|
7
|
+
data.tar.gz: 933e273120b795b3b1eb1bb96a4672c9f3da7645ed7df55046eddc57197307afd9aadee5612fb047980cceed3fc5c2348f5388bb5b19df87191e9ee57e2fdb0f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Abe Voelker
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Devise::Passwordless
|
2
|
+
|
3
|
+
A passwordless login strategy for [Devise][]
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
You should already have Devise installed. Then add this gem:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem "devise-passwordless"
|
11
|
+
```
|
12
|
+
|
13
|
+
Then run the generator to automatically update your Devise initializer:
|
14
|
+
|
15
|
+
```
|
16
|
+
rails g devise:passwordless:install
|
17
|
+
```
|
18
|
+
|
19
|
+
Merge these YAML values into your `devise.en.yml` file:
|
20
|
+
|
21
|
+
```yaml
|
22
|
+
en:
|
23
|
+
devise:
|
24
|
+
failure:
|
25
|
+
passwordless_invalid: "Invalid or expired login link."
|
26
|
+
mailer:
|
27
|
+
passwordless_link:
|
28
|
+
subject: "Here's your magic link"
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
This gem adds an `:email_authenticatable` strategy that can be used in your Devise models for passwordless authentication. This strategy plays well with most other Devise strategies.
|
34
|
+
|
35
|
+
For example, for a User model, you could do this (other strategies optional and not an exhaustive list):
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class User < ApplicationRecord
|
39
|
+
devise :email_authenticatable,
|
40
|
+
:registerable,
|
41
|
+
:rememberable,
|
42
|
+
:validatable,
|
43
|
+
:confirmable
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
**Note** if using the `:rememberable` strategy for "remember me" functionality, you'll need to add a `remember_token` column to your resource, as there is no password salt to use for validating cookies:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
change_table :users do |t|
|
51
|
+
t.string :remember_token, limit: 20
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
**Note** if using the `:confirmable` strategy, you may want to override the default Devise behavior of requiring a fresh login after email confirmation (e.g. [this](https://stackoverflow.com/a/39010334/215168) or [this](https://stackoverflow.com/a/25865526/215168) approach). Otherwise, users will have to get a fresh login link after confirming their email, which makes no sense if they just confirmed they own the email address.
|
56
|
+
|
57
|
+
## Configuration
|
58
|
+
|
59
|
+
## License
|
60
|
+
|
61
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
62
|
+
|
63
|
+
[Devise]: https://github.com/heartcombo/devise
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "devise/passwordless"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "devise/passwordless/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "devise-passwordless"
|
8
|
+
spec.version = Devise::Passwordless::VERSION
|
9
|
+
spec.authors = ["Abe Voelker"]
|
10
|
+
spec.email = ["_@abevoelker.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Passwordless (email-only) login strategy for Devise}
|
13
|
+
#spec.description = %q{TODO: Write a longer description or delete this line.}
|
14
|
+
spec.homepage = "https://github.com/abevoelker/devise-passwordless"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
23
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
24
|
+
#spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
25
|
+
else
|
26
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
27
|
+
"public gem pushes."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
32
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
34
|
+
end
|
35
|
+
spec.bindir = "exe"
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
|
+
spec.require_paths = ["lib"]
|
38
|
+
|
39
|
+
spec.add_dependency "devise"
|
40
|
+
spec.add_dependency "rails"
|
41
|
+
|
42
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
43
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
44
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
45
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'devise/strategies/email_authenticatable'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
module EmailAuthenticatable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def password_required?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
# Not having a password method breaks the :validatable module
|
13
|
+
def password
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# A callback initiated after successfully authenticating. This can be
|
18
|
+
# used to insert your own logic that is only run after the user successfully
|
19
|
+
# authenticates.
|
20
|
+
#
|
21
|
+
# Example:
|
22
|
+
#
|
23
|
+
# def after_passwordless_authentication
|
24
|
+
# self.update_attribute(:invite_code, nil)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
def after_passwordless_authentication
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# We assume this method already gets the sanitized values from the
|
34
|
+
# EmailAuthenticatable strategy. If you are using this method on
|
35
|
+
# your own, be sure to sanitize the conditions hash to only include
|
36
|
+
# the proper fields.
|
37
|
+
def find_for_email_authentication(conditions)
|
38
|
+
find_for_authentication(conditions)
|
39
|
+
end
|
40
|
+
|
41
|
+
Devise::Models.config(self, :passwordless_login_within, :passwordless_secret_key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Devise
|
48
|
+
mattr_accessor :passwordless_login_within
|
49
|
+
@@passwordless_login_within = 20.minutes
|
50
|
+
|
51
|
+
mattr_accessor :passwordless_secret_key
|
52
|
+
@@passwordless_secret_key = nil
|
53
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Devise::Passwordless
|
2
|
+
class LoginToken
|
3
|
+
class InvalidOrExpiredTokenError < StandardError; end
|
4
|
+
|
5
|
+
def self.encode(resource)
|
6
|
+
now = Time.current
|
7
|
+
len = ActiveSupport::MessageEncryptor.key_len
|
8
|
+
salt = SecureRandom.random_bytes(len)
|
9
|
+
key = ActiveSupport::KeyGenerator.new(self.secret_key).generate_key(salt, len)
|
10
|
+
crypt = ActiveSupport::MessageEncryptor.new(key, serializer: JSON)
|
11
|
+
encrypted_data = crypt.encrypt_and_sign({
|
12
|
+
data: {
|
13
|
+
resource: {
|
14
|
+
key: resource.to_key,
|
15
|
+
email: resource.email,
|
16
|
+
},
|
17
|
+
},
|
18
|
+
created_at: now.to_f,
|
19
|
+
})
|
20
|
+
salt_base64 = Base64.strict_encode64(salt)
|
21
|
+
"#{salt_base64}:#{encrypted_data}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.decode(token, as_of=Time.current, expire_duration=Devise.passwordless_login_within)
|
25
|
+
raise InvalidOrExpiredTokenError if token.blank?
|
26
|
+
salt_base64, encrypted_data = token.split(":")
|
27
|
+
begin
|
28
|
+
salt = Base64.strict_decode64(salt_base64)
|
29
|
+
rescue ArgumentError
|
30
|
+
raise InvalidOrExpiredTokenError
|
31
|
+
end
|
32
|
+
len = ActiveSupport::MessageEncryptor.key_len
|
33
|
+
key = ActiveSupport::KeyGenerator.new(self.secret_key).generate_key(salt, len)
|
34
|
+
crypt = ActiveSupport::MessageEncryptor.new(key, serializer: JSON)
|
35
|
+
begin
|
36
|
+
decrypted_data = crypt.decrypt_and_verify(encrypted_data)
|
37
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
38
|
+
raise InvalidOrExpiredTokenError
|
39
|
+
end
|
40
|
+
|
41
|
+
created_at = ActiveSupport::TimeZone["UTC"].at(decrypted_data["created_at"])
|
42
|
+
if as_of.to_f > (created_at + expire_duration).to_f
|
43
|
+
raise InvalidOrExpiredTokenError
|
44
|
+
end
|
45
|
+
|
46
|
+
decrypted_data
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.secret_key
|
50
|
+
if Devise.passwordless_secret_key.present?
|
51
|
+
Devise.passwordless_secret_key
|
52
|
+
else
|
53
|
+
Devise.secret_key
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
if defined?(Devise::Mailer)
|
2
|
+
Devise::Mailer.class_eval do
|
3
|
+
def passwordless_link(record, remember_me, opts = {})
|
4
|
+
@remember_me = remember_me
|
5
|
+
@token = Devise::Passwordless::LoginToken.encode(record)
|
6
|
+
devise_mail(record, :passwordless_link, opts)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "devise"
|
4
|
+
require "devise/strategies/authenticatable"
|
5
|
+
require "devise/passwordless/login_token"
|
6
|
+
|
7
|
+
module Devise
|
8
|
+
module Strategies
|
9
|
+
class EmailAuthenticatable < Authenticatable
|
10
|
+
#undef :password
|
11
|
+
#undef :password=
|
12
|
+
attr_accessor :token
|
13
|
+
|
14
|
+
def valid_for_http_auth?
|
15
|
+
super && http_auth_hash[:token].present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_for_params_auth?
|
19
|
+
super && params_auth_hash[:token].present?
|
20
|
+
end
|
21
|
+
|
22
|
+
def authenticate!
|
23
|
+
data = begin
|
24
|
+
x = Devise::Passwordless::LoginToken.decode(self.token)
|
25
|
+
x["data"]
|
26
|
+
rescue Devise::Passwordless::LoginToken::InvalidOrExpiredTokenError
|
27
|
+
fail!(:passwordless_invalid)
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
resource = mapping.to.find_by(id: data["resource"]["key"])
|
32
|
+
if validate(resource)
|
33
|
+
remember_me(resource)
|
34
|
+
resource.after_passwordless_authentication
|
35
|
+
success!(resource)
|
36
|
+
else
|
37
|
+
fail!(:passwordless_invalid)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Sets the authentication hash and the token from params_auth_hash or http_auth_hash.
|
44
|
+
def with_authentication_hash(auth_type, auth_values)
|
45
|
+
self.authentication_hash, self.authentication_type = {}, auth_type
|
46
|
+
self.token = auth_values[:token]
|
47
|
+
|
48
|
+
parse_authentication_key_values(auth_values, authentication_keys) &&
|
49
|
+
parse_authentication_key_values(request_values, request_keys)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Warden::Strategies.add(:email_authenticatable, Devise::Strategies::EmailAuthenticatable)
|
56
|
+
|
57
|
+
Devise.add_module(:email_authenticatable, {
|
58
|
+
strategy: true,
|
59
|
+
controller: :sessions,
|
60
|
+
model: "devise/models/email_authenticatable",
|
61
|
+
#route: { email_authenticatable: [nil, :new, :edit] }
|
62
|
+
route: :session
|
63
|
+
})
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Devise::Passwordless
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < ::Rails::Generators::Base
|
7
|
+
desc "Updates the Devise initializer to add passwordless config options"
|
8
|
+
|
9
|
+
def update_devise_initializer
|
10
|
+
inject_into_file 'config/initializers/devise.rb', before: /^end$/ do <<~'CONFIG'.indent(2)
|
11
|
+
|
12
|
+
# ==> Configuration for :email_authenticatable
|
13
|
+
|
14
|
+
# Time period after a magic login link is sent out that it will be valid for.
|
15
|
+
# config.passwordless_login_within = 20.minutes
|
16
|
+
|
17
|
+
# The secret key used to generate passwordless login tokens. The default
|
18
|
+
# value is nil, which means defer to Devise's `secret_key` config value.
|
19
|
+
# Changing this key will render invalid all existing passwordless login
|
20
|
+
# tokens. You can generate your own value with e.g. `rake secret`
|
21
|
+
# config.passwordless_secret_key = nil
|
22
|
+
CONFIG
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_devise_yaml
|
27
|
+
devise_yaml = "config/locales/devise.en.yml"
|
28
|
+
begin
|
29
|
+
config = YAML.load_file(devise_yaml)
|
30
|
+
rescue Errno::ENOENT
|
31
|
+
STDERR.puts "Couldn't find devise.en.yml - skipping patch"
|
32
|
+
return
|
33
|
+
end
|
34
|
+
config["en"]["devise"]["failure"]["passwordless_invalid"] = "Invalid or expired login link."
|
35
|
+
config["en"]["devise"]["mailer"]["passwordless_link"] = {subject: "Your login link"}
|
36
|
+
File.open(devise_yaml, "w") do |f|
|
37
|
+
f.write(config.to_yaml)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devise-passwordless
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Abe Voelker
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: devise
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.17'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.17'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- _@abevoelker.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- devise-passwordless.gemspec
|
100
|
+
- lib/devise/models/email_authenticatable.rb
|
101
|
+
- lib/devise/passwordless.rb
|
102
|
+
- lib/devise/passwordless/login_token.rb
|
103
|
+
- lib/devise/passwordless/mailer.rb
|
104
|
+
- lib/devise/passwordless/version.rb
|
105
|
+
- lib/devise/strategies/email_authenticatable.rb
|
106
|
+
- lib/generators/devise/passwordless/install_generator.rb
|
107
|
+
homepage: https://github.com/abevoelker/devise-passwordless
|
108
|
+
licenses:
|
109
|
+
- MIT
|
110
|
+
metadata:
|
111
|
+
homepage_uri: https://github.com/abevoelker/devise-passwordless
|
112
|
+
source_code_uri: https://github.com/abevoelker/devise-passwordless
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.0.3
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Passwordless (email-only) login strategy for Devise
|
132
|
+
test_files: []
|