magic-link 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6d9cdcff5bac8ba5789ac41f1b297408a4fba83f063d3afbb3bd585549996be9
4
+ data.tar.gz: 6e7cf401d907dcdb65421a2fac94084b1749b3bd84e5d0e0ac334e82738ef96d
5
+ SHA512:
6
+ metadata.gz: 58db75b1fed54635bdc015e83a7f170c5c3193b054d94a35270a58f269045925c5a26004edc7ce5f9218057a72bec54d4d3095bf66aab19fce6b9ff1801a55df
7
+ data.tar.gz: b06b4740c460825a17e969ff4cc0d9272463c028dd6b855f49d2dff8767a6e62277601065416cc6a3d55d1b9512172d99b3d1cb6517198a1a6d987c49a028b38
@@ -0,0 +1,20 @@
1
+ Copyright 2018 David Van Der Beek
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,47 @@
1
+ # Magic::Link
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ configure the gem
6
+ ```ruby
7
+ # config/initializers/magic_link.rb
8
+ Magic::Link.configure do |config|
9
+ config.user_class = "Customer" # Default is User
10
+ config.email_from = "test@yourapp.com"
11
+ config.token_expiration_hours = 6 # Default is 6
12
+ end
13
+ ```
14
+
15
+ Add `sign_in_token` and `sign_in_token_sent_at` to your Devise class
16
+
17
+ mount the engine
18
+ ```ruby
19
+ mount Magic::Link::Engine, at: '/'
20
+ ```
21
+
22
+ Now users can visit `/magic_links/new` to enter their email and have a sign in
23
+ link sent to them via email. Tokens are cleared after use and expire after the
24
+ configured number of hours
25
+
26
+ ## Installation
27
+ Add this line to your application's Gemfile:
28
+
29
+ ```ruby
30
+ gem 'magic-link'
31
+ ```
32
+
33
+ And then execute:
34
+ ```bash
35
+ $ bundle
36
+ ```
37
+
38
+ Or install it yourself as:
39
+ ```bash
40
+ $ gem install magic-link
41
+ ```
42
+
43
+ ## Contributing
44
+ Contribution directions go here.
45
+
46
+ ## License
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Magic::Link'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/magic/link .js
2
+ //= link_directory ../stylesheets/magic/link .css
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,29 @@
1
+ module Magic
2
+ module Link
3
+ class MagicLinksController < ::ApplicationController
4
+ before_action :check_user, only: :new
5
+
6
+ def new
7
+ @magic_link = MagicLink.new
8
+ end
9
+
10
+ def create
11
+ @magic_link = MagicLink.new(permitted_params)
12
+ @magic_link.send_login_instructions
13
+ redirect_to main_app.root_path, notice: "Check your email for a sign in link!"
14
+ end
15
+
16
+ private
17
+
18
+ def check_user
19
+ if send("#{Magic::Link.user_class.name.underscore}_signed_in?")
20
+ redirect_to main_app.root_path, notice: "You are already signed in"
21
+ end
22
+ end
23
+
24
+ def permitted_params
25
+ params.fetch(:magic_link, {}).permit(:email)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module Magic
2
+ module Link
3
+ module ApplicationHelper
4
+ def method_missing method, *args, &block
5
+ if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
6
+ if main_app.respond_to?(method)
7
+ main_app.send(method, *args)
8
+ else
9
+ super
10
+ end
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def respond_to?(method, include_all = false)
17
+ if method.to_s.end_with?('_path') or method.to_s.end_with?('_url')
18
+ if main_app.respond_to?(method)
19
+ true
20
+ else
21
+ super
22
+ end
23
+ else
24
+ super
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ module Magic
2
+ module Link
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Magic
2
+ module Link
3
+ class ApplicationMailer < ::ApplicationMailer
4
+ default from: Magic::Link.email_from
5
+ layout 'mailer'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Magic::Link
2
+ class MagicLinkMailer < ApplicationMailer
3
+ def send_magic_link(email, token)
4
+ @email = email
5
+ @token = token
6
+ mail(to: email, subject: "Your sign in link")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Magic
2
+ module Link
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ module Magic
2
+ module Link
3
+ class MagicLink
4
+ include ActiveModel::Model
5
+ attr_accessor :email
6
+
7
+ def send_login_instructions
8
+ token = set_sign_in_token
9
+ send_magic_link_email(token) if token
10
+ token
11
+ end
12
+
13
+ private
14
+
15
+ def user
16
+ @user ||= Magic::Link.user_class.find_by(email: email.downcase)
17
+ end
18
+
19
+ def send_magic_link_email(token)
20
+ MagicLinkMailer.send_magic_link(email, token).deliver_later
21
+ end
22
+
23
+ def set_sign_in_token(force: false)
24
+ if user && (force || (user.sign_in_token.blank? || user.sign_in_token_sent_at < Magic::Link.token_expiration_hours.hours.ago))
25
+ raw, enc = Devise.token_generator.generate(Magic::Link.user_class, :sign_in_token)
26
+ user.sign_in_token = enc
27
+ user.sign_in_token_sent_at = Time.current
28
+ user.save(validate: false)
29
+ raw
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ <p>Hey there! Here is your sign in link (you can only use it once):</p>
2
+
3
+ <p><%= link_to "Sign In", main_app.root_url(email: @email, sign_in_token: @token) %></p>
4
+
5
+ <p>Thanks!</p>
@@ -0,0 +1,5 @@
1
+ Hey there! Here is your sign in link (you can only use it once):
2
+
3
+ <%= main_app.root_url(email: @email, sign_in_token: @token) %>
4
+
5
+ Thanks!
@@ -0,0 +1,10 @@
1
+ <div class="row">
2
+ <div class="col-md-6 offset-md-3">
3
+ <%= simple_form_for @magic_link do |f| %>
4
+ <%= f.input :email, label: "Enter your email", autofocus: true %>
5
+ <%= f.submit "Email me a sign in link", class: "btn btn-primary btn-block" %>
6
+ <% end %>
7
+ <br>
8
+ <%= link_to "Back", :back %>
9
+ </div>
10
+ </div>
@@ -0,0 +1,3 @@
1
+ Magic::Link::Engine.routes.draw do
2
+ resources :magic_links, only: [:new, :create]
3
+ end
@@ -0,0 +1,26 @@
1
+ require "magic/link/engine"
2
+ require "magic/link/controller_extensions"
3
+ require "magic/link/railtie"
4
+
5
+ module Magic
6
+ module Link
7
+ mattr_accessor :user_class
8
+ @@user_class = "User"
9
+
10
+ mattr_accessor :email_from
11
+ @@email_from = "please-change-me@magic-link.com"
12
+
13
+ mattr_accessor :token_expiration_hours
14
+ @@token_expiration_hours = 6
15
+
16
+ class << self
17
+ def configure
18
+ yield self
19
+ end
20
+
21
+ def user_class
22
+ @@user_class.constantize
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ module Magic
2
+ module Link
3
+ module ControllerExtensions
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ end
7
+
8
+ module InstanceMethods
9
+ def authenticate_user_from_token!
10
+ email = params[:email].presence
11
+ token = params[:sign_in_token].presence
12
+ user = email && token && Magic::Link.user_class.find_by(email: email)
13
+
14
+ if token && send("#{Magic::Link.user_class.name.underscore}_signed_in?")
15
+ flash.now[:alert] = "You are already signed in"
16
+ elsif user && token_matches?(user) && token_not_expired?(user)
17
+ flash[:notice] = "You have signed in successfully"
18
+ user.update_columns(sign_in_token: nil, sign_in_token_sent_at: nil)
19
+ sign_in user
20
+ elsif email && token
21
+ flash[:alert] = "Your sign in token is invalid"
22
+ redirect_to main_app.root_path
23
+ end
24
+ end
25
+
26
+ def token_matches?(user)
27
+ Devise.secure_compare(
28
+ user.sign_in_token,
29
+ Devise.token_generator.digest(Magic::Link.user_class, :sign_in_token, params[:sign_in_token])
30
+ )
31
+ end
32
+
33
+ def token_not_expired?(user)
34
+ user.sign_in_token_sent_at >= Magic::Link.token_expiration_hours.hours.ago
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Magic
2
+ module Link
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Magic::Link
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Magic
2
+ module Link
3
+ class Railtie < ::Rails::Railtie
4
+ config.to_prepare do
5
+ ::ApplicationController.send(:include, Magic::Link::ControllerExtensions)
6
+ ::ApplicationController.send(:helper, Magic::Link::ApplicationHelper)
7
+ ::ApplicationController.send(:before_action, :authenticate_user_from_token!)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Magic
2
+ module Link
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :magic_link do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: magic-link
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Van Der Beek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: devise
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Devise magic sign in links
56
+ email:
57
+ - earlynovrock@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/assets/config/magic_link_manifest.js
66
+ - app/assets/javascripts/magic/link/application.js
67
+ - app/assets/stylesheets/magic/link/application.css
68
+ - app/controllers/magic/link/magic_links_controller.rb
69
+ - app/helpers/magic/link/application_helper.rb
70
+ - app/jobs/magic/link/application_job.rb
71
+ - app/mailers/magic/link/application_mailer.rb
72
+ - app/mailers/magic/link/magic_link_mailer.rb
73
+ - app/models/magic/link/application_record.rb
74
+ - app/models/magic/link/magic_link.rb
75
+ - app/views/magic/link/magic_link_mailer/send_magic_link.html.erb
76
+ - app/views/magic/link/magic_link_mailer/send_magic_link.text.erb
77
+ - app/views/magic/link/magic_links/new.html.erb
78
+ - config/routes.rb
79
+ - lib/magic/link.rb
80
+ - lib/magic/link/controller_extensions.rb
81
+ - lib/magic/link/engine.rb
82
+ - lib/magic/link/railtie.rb
83
+ - lib/magic/link/version.rb
84
+ - lib/tasks/magic/link_tasks.rake
85
+ homepage: https://github.com/dvanderbeek/magic-link
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 3.0.6
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: Devise magic sign in links
108
+ test_files: []