authkit 0.4.0 → 0.5.0

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