authkit 0.4.0 → 0.5.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -3
  3. data/Rakefile +3 -2
  4. data/lib/authkit/version.rb +1 -1
  5. data/lib/generators/authkit/install_generator.rb +181 -35
  6. data/lib/generators/authkit/templates/app/controllers/application_controller.rb +6 -0
  7. data/lib/generators/authkit/templates/app/controllers/auths_controller.rb +144 -0
  8. data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +1 -1
  9. data/lib/generators/authkit/templates/app/controllers/password_reset_controller.rb +7 -1
  10. data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +11 -2
  11. data/lib/generators/authkit/templates/app/controllers/signup_controller.rb +4 -2
  12. data/lib/generators/authkit/templates/app/controllers/upload_controller.rb +78 -0
  13. data/lib/generators/authkit/templates/app/controllers/users_controller.rb +2 -2
  14. data/lib/generators/authkit/templates/app/forms/signup.rb +57 -7
  15. data/lib/generators/authkit/templates/app/helpers/auths_helper.rb +26 -0
  16. data/lib/generators/authkit/templates/app/helpers/upload_helper.rb +118 -0
  17. data/lib/generators/authkit/templates/app/models/auth.rb +81 -0
  18. data/lib/generators/authkit/templates/app/models/avatar.rb +45 -0
  19. data/lib/generators/authkit/templates/app/models/user.rb +53 -26
  20. data/lib/generators/authkit/templates/app/views/auths/connect.html.erb +34 -0
  21. data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +9 -9
  22. data/lib/generators/authkit/templates/app/views/password_reset/show.html.erb +6 -6
  23. data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +25 -7
  24. data/lib/generators/authkit/templates/app/views/signup/new.html.erb +44 -32
  25. data/lib/generators/authkit/templates/app/views/users/complete.html.erb +39 -0
  26. data/lib/generators/authkit/templates/app/views/users/edit.html.erb +31 -31
  27. data/lib/generators/authkit/templates/app/workers/avatar_import_worker.rb +12 -0
  28. data/lib/generators/authkit/templates/config/initializers/filter_parameter_logging.rb +2 -2
  29. data/lib/generators/authkit/templates/config/initializers/omniauth.rb +59 -0
  30. data/lib/generators/authkit/templates/config/initializers/paperclip.rb +68 -0
  31. data/lib/generators/authkit/templates/db/migrate/add_authkit_fields_to_users.rb +8 -6
  32. data/lib/generators/authkit/templates/db/migrate/create_auths.rb +24 -0
  33. data/lib/generators/authkit/templates/db/migrate/create_avatars.rb +27 -0
  34. data/lib/generators/authkit/templates/lib/full_name_splitter.rb +111 -0
  35. data/lib/generators/authkit/templates/lib/username_format_validator.rb +11 -0
  36. data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +31 -38
  37. data/lib/generators/authkit/templates/spec/controllers/auths_controller_spec.rb +72 -0
  38. data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +25 -27
  39. data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +30 -30
  40. data/lib/generators/authkit/templates/spec/controllers/password_reset_controller_spec.rb +20 -20
  41. data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +33 -33
  42. data/lib/generators/authkit/templates/spec/controllers/signup_controller_spec.rb +19 -19
  43. data/lib/generators/authkit/templates/spec/controllers/users_controller_spec.rb +21 -21
  44. data/lib/generators/authkit/templates/spec/factories/user.rb +3 -3
  45. data/lib/generators/authkit/templates/spec/forms/signup_spec.rb +32 -31
  46. data/lib/generators/authkit/templates/spec/models/auth_spec.rb +18 -0
  47. data/lib/generators/authkit/templates/spec/models/user_spec.rb +72 -78
  48. data/spec/rails_helper.rb +50 -0
  49. data/spec/spec_helper.rb +70 -13
  50. metadata +35 -17
  51. data/lib/generators/authkit/templates/spec/spec_helper.rb +0 -4
@@ -34,7 +34,7 @@ class EmailConfirmationController < ApplicationController
34
34
  # lock the account.
35
35
  def require_token
36
36
  verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_key_base)
37
- valid = params[:token].present?
37
+ valid = params[:token].present? && current_user.confirmation_token.present?
38
38
  valid = valid && verifier.send(:secure_compare, params[:token], current_user.confirmation_token)
39
39
  valid = valid && !current_user.confirmation_token_expired?
40
40
  deny_user("Invalid token", root_path) unless valid
@@ -28,8 +28,14 @@ class PasswordResetController < ApplicationController
28
28
 
29
29
  def user
30
30
  return @user if defined?(@user)
31
+ <% if username? %>
31
32
  username_or_email = "#{params[:email]}".downcase
32
33
  return if username_or_email.blank?
33
- @user = User.where('username = ? OR email = ?', username_or_email, username_or_email).first
34
+ @user = User.where('LOWER(username) = ? OR email = ?', username_or_email, username_or_email).first
35
+ <% else %>
36
+ email = "#{params[:email]}".downcase
37
+ return if email.blank?
38
+ @user = User.where('email = ?', email).first
39
+ <% end %>
34
40
  end
35
41
  end
@@ -1,4 +1,8 @@
1
1
  class SessionsController < ApplicationController
2
+ <% if oauth? %>
3
+ include AuthsHelper
4
+ <% end %>
5
+
2
6
  # Login
3
7
  def new
4
8
  end
@@ -35,9 +39,14 @@ class SessionsController < ApplicationController
35
39
  protected
36
40
 
37
41
  def user
38
- return @user if defined?(@user)
42
+ <% if username? %>
39
43
  username_or_email = "#{params[:email]}".downcase
40
44
  return if username_or_email.blank?
41
- @user = User.where('username = ? OR email = ?', username_or_email, username_or_email).first
45
+ @user = User.where('LOWER(username) = ? OR email = ?', username_or_email, username_or_email).first
46
+ <% else %>
47
+ email = "#{params[:email]}".downcase
48
+ return if email.blank?
49
+ @user = User.where('email = ?', email).first
50
+ <% end %>
42
51
  end
43
52
  end
@@ -1,4 +1,6 @@
1
1
  class SignupController < ApplicationController
2
+ <% if oauth? %>include AuthsHelper
3
+ <% end %>
2
4
  respond_to :html, :json
3
5
 
4
6
  # Create a new Signup form model (found in app/forms/signup.rb)
@@ -32,8 +34,8 @@ class SignupController < ApplicationController
32
34
  def signup_params
33
35
  params.require(:signup).permit(
34
36
  :email,
35
- :username,
36
- :password,
37
+ <% if username? %>:username,
38
+ <% end %>:password,
37
39
  :password_confirmation,
38
40
  :first_name,
39
41
  :last_name,
@@ -0,0 +1,78 @@
1
+ class UploadController < ApplicationController
2
+ before_filter :require_login
3
+ before_filter :require_advertiser, only: [:audio, :cover]
4
+ before_filter :aws_key_to_remote_url, only: [:avatar]
5
+
6
+ respond_to :json
7
+
8
+ def audio
9
+ @audio = current_user.audios.build(audio_params)
10
+
11
+ # Each new upload needs to be unique in case the same file is uploaded twice with different content
12
+ @audio.attachment_uploaded_at = Time.now
13
+
14
+ if @audio.save
15
+ respond_to do |format|
16
+ format.json { render json: @audio }
17
+ end
18
+ else
19
+ respond_to do |format|
20
+ format.json { render json: { status: 'error', errors: @audio.errors }.to_json, status: 422 }
21
+ end
22
+ end
23
+ end
24
+
25
+ def cover
26
+ @cover = current_user.covers.build(cover_params)
27
+
28
+ # Each new upload needs to be unique in case the same file is uploaded twice with different content
29
+ @cover.attachment_uploaded_at = Time.now
30
+
31
+ if @cover.save
32
+ respond_to do |format|
33
+ format.json { render json: @cover }
34
+ end
35
+ else
36
+ respond_to do |format|
37
+ format.json { render json: { status: 'error', errors: @cover.errors }.to_json, status: 422 }
38
+ end
39
+ end
40
+ end
41
+
42
+ def avatar
43
+ @avatar = current_user.build_avatar(avatar_params)
44
+
45
+ # Each new upload needs to be unique in case the same file is uploaded twice with different content
46
+ @avatar.attachment_uploaded_at = Time.now
47
+
48
+ if current_user.save
49
+ respond_to do |format|
50
+ format.json { render json: @avatar }
51
+ end
52
+ else
53
+ respond_to do |format|
54
+ format.json { render json: { status: 'error', errors: @avatar.errors }.to_json, status: 422 }
55
+ end
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ def audio_params
62
+ params.require(:audio).permit(:attachment)
63
+ end
64
+
65
+ def cover_params
66
+ params.require(:cover).permit(:attachment)
67
+ end
68
+
69
+ def avatar_params
70
+ params.require(:avatar).permit(:attachment, :remote_url)
71
+ end
72
+
73
+ def aws_key_to_remote_url
74
+ params[:avatar] ||= {}
75
+ params[:avatar][:remote_url] ||= view_context.aws_url_for(params[:key]) if params[:key].present?
76
+ end
77
+
78
+ end
@@ -36,8 +36,8 @@ class UsersController < ApplicationController
36
36
  def user_params
37
37
  params.require(:user).permit(
38
38
  :confirmation_email,
39
- :username,
40
- :password,
39
+ <% if username? %>:username,
40
+ <% end %>:password,
41
41
  :password_confirmation,
42
42
  :first_name,
43
43
  :last_name,
@@ -3,12 +3,15 @@ class Signup
3
3
  include ActiveModel::Model
4
4
 
5
5
  attr_accessor :user
6
+ <% if oauth? %>
7
+ attr_accessor :auth
8
+ <% end %>
6
9
 
7
10
  # User
8
11
  attr_accessor(
9
12
  :email,
10
- :username,
11
- :password,
13
+ <% if username? %>:username,
14
+ <% end %>:password,
12
15
  :password_confirmation,
13
16
  :first_name,
14
17
  :last_name,
@@ -17,12 +20,27 @@ class Signup
17
20
  :phone_number,
18
21
  :time_zone)
19
22
 
23
+ <% if oauth? %>
24
+ # Auth
20
25
  attr_accessor(
26
+ :auth_params)
27
+ <% end %>
28
+
29
+ attr_accessor(
30
+ :skip_email_confirmation,
21
31
  :terms_of_service)
22
32
 
23
33
  validates :terms_of_service, acceptance: true
24
34
  validate :validate_models
25
35
 
36
+ <% if oauth? %>
37
+ def self.new_with_oauth(auth_params, signup_params)
38
+ signup = Signup.new(signup_params)
39
+ signup.set_auth_params(auth_params)
40
+ signup
41
+ end
42
+ <% end %>
43
+
26
44
  def persisted?
27
45
  false
28
46
  end
@@ -30,7 +48,7 @@ class Signup
30
48
  def save
31
49
  if valid?
32
50
  persist!
33
- send_confirmation!
51
+ send_confirmation! unless skip_email_confirmation
34
52
  send_welcome!
35
53
  true
36
54
  else
@@ -44,10 +62,42 @@ class Signup
44
62
  @user
45
63
  end
46
64
 
65
+ <% if oauth? %>
66
+ def auth
67
+ return nil if self.auth_params.blank?
68
+ return @auth if @auth
69
+ @auth = self.user.auths.build(auth_params)
70
+ end
71
+
72
+ def has_auth?(provider)
73
+ self.auth.provider == provider.to_s if self.auth
74
+ end
75
+
76
+ def set_auth_params(auth_params)
77
+ self.auth_params = auth_params
78
+
79
+ self.email = self.auth.try(:email) if self.email.blank?
80
+ self.first_name = self.auth.try(:first_name) if self.first_name.blank?
81
+ self.last_name = self.auth.try(:last_name) if self.last_name.blank?
82
+ <% if username? %>self.username = self.auth.try(:username) if self.username.blank?
83
+ <% end %>self.skip_email_confirmation = true
84
+
85
+ # We need to reassign the user fields if the user is already created
86
+ self.user.attributes = user_params if self.user
87
+ self.auth
88
+ end
89
+ <% end %>
90
+
47
91
  private
48
92
 
49
93
  def validate_models
50
94
  self.user.errors.each { |k, v| errors[k] = v } unless self.user.valid?
95
+
96
+ <% if oauth? %>
97
+ if self.auth.present?
98
+ self.auth.errors.each { |k, v| errors[k] = v } unless self.auth.valid?
99
+ end
100
+ <% end %>
51
101
  end
52
102
 
53
103
  def persist!
@@ -57,18 +107,18 @@ class Signup
57
107
  end
58
108
 
59
109
  def send_confirmation!
60
- self.user.send_confirmation
110
+ self.user.send_confirmation if self.email
61
111
  end
62
112
 
63
113
  def send_welcome!
64
- self.user.send_welcome
114
+ self.user.send_welcome if self.email
65
115
  end
66
116
 
67
117
  def user_params
68
118
  {
69
119
  email: self.email,
70
- username: self.username,
71
- password: self.password,
120
+ <% if username? %>username: self.username,
121
+ <% end %>password: self.password,
72
122
  password_confirmation: self.password_confirmation,
73
123
  first_name: self.first_name,
74
124
  last_name: self.last_name,
@@ -0,0 +1,26 @@
1
+ module AuthsHelper
2
+ def providers
3
+ result = []
4
+ <% providers.each do |provider| %>
5
+ result << :<%= provider %>
6
+ <% end %>
7
+ result
8
+ end
9
+
10
+ def provider_font_awesome_icon(provider)
11
+ icon_names = HashWithIndifferentAccess.new
12
+ <% providers.each do |provider| %>
13
+ icon_names[:<%= provider %>] = "<%= font_awesome_icons[provider] %>"
14
+ <% end %>
15
+ icon_names[provider]
16
+ end
17
+
18
+ def provider_formatted_name(provider)
19
+ formatted_names = HashWithIndifferentAccess.new
20
+ <% providers.each do |provider| %>
21
+ formatted_names[:<%= provider %>] = "<%= formatted_providers[provider] %>"
22
+ <% end %>
23
+ formatted_names[provider]
24
+ end
25
+ end
26
+
@@ -0,0 +1,118 @@
1
+ # Allow direct uploads to Amazon S3. Uploads are segmented by the
2
+ # Everything is uploaded into an separate paths based on the current user
3
+ # so that files are not overwritten when multiple people upload different
4
+ # files with the same name.
5
+ #
6
+ # In order to use this helper you must have the following environment
7
+ # variables set:
8
+ #
9
+ # aws_access_key_id
10
+ # aws_secret_access_key
11
+ # aws_bucket
12
+ #
13
+ # Configure the AWS bucket with the following CORS policy
14
+ #
15
+ # <CORSConfiguration>
16
+ # <CORSRule>
17
+ # <AllowedOrigin>http://0.0.0.0:3000</AllowedOrigin>
18
+ # <AllowedOrigin><%= ENV['domain'] %></AllowedOrigin>
19
+ # <AllowedMethod>GET</AllowedMethod>
20
+ # <AllowedMethod>POST</AllowedMethod>
21
+ # <AllowedMethod>PUT</AllowedMethod>
22
+ # <MaxAgeSeconds>3600</MaxAgeSeconds>
23
+ # <AllowedHeader>*</AllowedHeader>
24
+ # </CORSRule>
25
+ # </CORSConfiguration>
26
+ #
27
+ module UploadHelper
28
+
29
+ # Use this as part of the XHR data params for the upload form
30
+ # Options include:
31
+ #
32
+ # expires_at: defaults to 1 hour from now
33
+ # max_file_size: defaults to 5GB (not currently used)
34
+ # acl: defaults to authenticated-read
35
+ # starts_with: defaults to "uploads/#{h(current_user.to_param)}/#{SecureRandom.hex}"
36
+ #
37
+ def aws_upload_params(options={})
38
+ expires_at = options[:expires_at] || 1.hours.from_now
39
+ max_file_size = options[:max_file_size] || 5.gigabyte
40
+ acl = options[:acl] || 'authenticated-read'
41
+ hash = "#{SecureRandom.hex}"
42
+ starts_with = options[:starts_with] || "uploads/#{hash}"
43
+ bucket = ENV['aws_bucket']
44
+ # This used to include , but it threw Server IO errors
45
+ policy = Base64.encode64(
46
+ "{'expiration': '#{expires_at.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z')}',
47
+ 'conditions': [
48
+ ['starts-with', '$key', '#{starts_with}'],
49
+ ['starts-with', '$hash', '#{hash}'],
50
+ ['starts-with', '$utf8', ''],
51
+ ['starts-with', '$x-requested-with', ''],
52
+ ['eq', '$success_action_status', '201'],
53
+ ['content-length-range', 0, #{max_file_size}],
54
+ {'bucket': '#{bucket}'},
55
+ {'acl': '#{acl}'},
56
+ {'success_action_status': '201'}
57
+ ]
58
+ }").gsub(/\n|\r/, '')
59
+
60
+ signature = Base64.encode64(
61
+ OpenSSL::HMAC.digest(
62
+ OpenSSL::Digest::Digest.new('sha1'),
63
+ ENV['aws_secret_access_key'], policy)).gsub("\n","")
64
+
65
+ return {
66
+ "key" => "#{starts_with}/${filename}",
67
+ "hash" => "#{hash}",
68
+ "utf8" => "",
69
+ "x-requested-with" => "",
70
+ "AWSAccessKeyId" => "#{ENV['aws_access_key_id']}",
71
+ "acl" => "#{acl}",
72
+ "policy" => "#{policy}",
73
+ "signature" => "#{signature}",
74
+ "success_action_status" => "201"
75
+ }
76
+ end
77
+
78
+ # Use aws_upload_tags when embedding the upload params in a form.
79
+ # For example:
80
+ #
81
+ # <form action="<%= aws_upload_url %>" method="post" enctype="multipart/form-data" id="avatar_form">
82
+ # <input type="file" name="file" id="avatar_attachment">
83
+ # <%= aws_upload_tags %>
84
+ # </form>
85
+ #
86
+ def aws_upload_tags
87
+ aws_upload_params.each do |name, value|
88
+ concat text_field_tag(name, value)
89
+ end
90
+ nil
91
+ end
92
+
93
+ # The destination host for the upload always uses https even though it can be slower.
94
+ # Safe and sure wins the race
95
+ def aws_bucket_url
96
+ UploadHelper.aws_bucket_url
97
+ end
98
+
99
+ def self.aws_bucket_url
100
+ "https://#{ENV['aws_bucket']}.#{ENV['aws_region'] || 's3'}.amazonaws.com"
101
+ end
102
+
103
+ # Authenticated url for S3 objects with expiration. By default the URL will expire in
104
+ # 1 day. The path should not include the bucket name.
105
+ def aws_url_for(path, expires=nil)
106
+ UploadHelper.aws_url_for(path, expires)
107
+ end
108
+
109
+ def self.aws_url_for(path, expires=nil)
110
+ path = "/#{path}" unless path =~ /\A\//
111
+ path = URI.encode(path)
112
+ expires ||= 1.day.from_now
113
+ string_to_sign = "GET\n\n\n#{expires.to_i}\n/#{ENV['aws_bucket']}#{path}"
114
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), ENV['aws_secret_access_key'], string_to_sign)).gsub("\n","")
115
+ query_string = URI.encode_www_form("AWSAccessKeyId" => ENV['aws_access_key_id'], "Signature" => signature, "Expires" => expires.to_i)
116
+ "#{aws_bucket_url}#{path}?#{query_string}"
117
+ end
118
+ end
@@ -0,0 +1,81 @@
1
+ class Auth < ActiveRecord::Base
2
+ belongs_to :user
3
+
4
+ def full_name
5
+ self.parsed_env["info"]["name"] rescue formatted_provider
6
+ end
7
+
8
+ def first_name
9
+ self.parsed_env["info"]["first_name"] rescue nil
10
+ end
11
+
12
+ def last_name
13
+ self.parsed_env["info"]["last_name"] rescue nil
14
+ end
15
+
16
+ def image_url
17
+ if <%= (providers - [:tumblr]).map{|p| "#{p}?"}.join(" || ") %>
18
+ self.parsed_env["info"]["image"] rescue nil
19
+ <% if provider?(:tumblr) %>elsif tumblr?
20
+ self.parsed_env["info"]["avatar"] rescue nil<% end %>
21
+ end
22
+ end
23
+
24
+ def username
25
+ <% if provider?(:google) %># Google does not provide a username<% end %>
26
+ self.parsed_env["info"]["nickname"] rescue nil
27
+ end
28
+
29
+ def expired?
30
+ self.token_expires_at && self.token_expires_at < Time.now
31
+ end
32
+
33
+ <% providers.each do |provider| %>
34
+ def <%= provider %>?
35
+ provider == "<%= provider %>"
36
+ end
37
+ <% end %>
38
+
39
+ def refresh!
40
+ return if refresh_token.blank?
41
+ <% if provider?(:google) %>refresh_google if google?<% end%>
42
+ save!
43
+ end
44
+
45
+ protected
46
+
47
+ <% if provider?(:google) %>
48
+ # https://github.com/intridea/omniauth-oauth2/issues/40#issuecomment-21275075
49
+ def refresh_google
50
+ conn = Faraday.new('https://accounts.google.com') do |faraday|
51
+ faraday.request :url_encoded
52
+ faraday.response :json
53
+ faraday.response :raise_error
54
+ faraday.response :logger unless Rails.env.production?
55
+ faraday.adapter Faraday.default_adapter
56
+ end
57
+ response = conn.post('/o/oauth2/token', {
58
+ grant_type: 'refresh_token',
59
+ refresh_token: refresh_token,
60
+ client_id: ENV['google_api_client_id'],
61
+ client_secret: ENV['google_api_client_secret']
62
+ })
63
+
64
+ body = response.body
65
+
66
+ self.token = body['access_token'] if body['access_token']
67
+ self.token_expires_at = Time.now.utc + body['expires_in'].to_i.seconds
68
+ end
69
+ <% end %>
70
+
71
+ def formatted_provider
72
+ <% providers.each do |provider| %>
73
+ return "<%= formatted_providers[provider] %>" if <%= provider %>?
74
+ <% end %>
75
+ end
76
+
77
+ def parsed_env
78
+ @parsed_env ||= JSON.parse(self.env) rescue {}
79
+ end
80
+ end
81
+