rails-identity 0.0.1

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/rails_identity/application.js +13 -0
  5. data/app/assets/javascripts/rails_identity/sessions.js +2 -0
  6. data/app/assets/javascripts/rails_identity/users.js +2 -0
  7. data/app/assets/stylesheets/rails_identity/application.css +15 -0
  8. data/app/assets/stylesheets/rails_identity/sessions.css +4 -0
  9. data/app/assets/stylesheets/rails_identity/users.css +4 -0
  10. data/app/controllers/rails_identity/application_controller.rb +200 -0
  11. data/app/controllers/rails_identity/sessions_controller.rb +108 -0
  12. data/app/controllers/rails_identity/users_controller.rb +168 -0
  13. data/app/helpers/rails_identity/application_helper.rb +19 -0
  14. data/app/helpers/rails_identity/sessions_helper.rb +4 -0
  15. data/app/helpers/rails_identity/users_helper.rb +4 -0
  16. data/app/jobs/rails_identity/sessions_cleanup_job.rb +13 -0
  17. data/app/mailers/application_mailer.rb +4 -0
  18. data/app/mailers/rails_identity/user_mailer.rb +14 -0
  19. data/app/models/rails_identity/session.rb +44 -0
  20. data/app/models/rails_identity/user.rb +48 -0
  21. data/app/views/layouts/mailer.html.erb +5 -0
  22. data/app/views/layouts/mailer.text.erb +1 -0
  23. data/app/views/layouts/rails_identity/application.html.erb +14 -0
  24. data/app/views/rails_identity/user_mailer/email_verification.html.erb +12 -0
  25. data/app/views/rails_identity/user_mailer/email_verification.text.erb +13 -0
  26. data/app/views/rails_identity/user_mailer/password_reset.html.erb +14 -0
  27. data/app/views/rails_identity/user_mailer/password_reset.text.erb +15 -0
  28. data/config/routes.rb +7 -0
  29. data/db/migrate/20160323210013_create_rails_identity_users.rb +13 -0
  30. data/db/migrate/20160323210017_create_rails_identity_sessions.rb +12 -0
  31. data/db/migrate/20160401223433_add_reset_token_to_users.rb +5 -0
  32. data/db/migrate/20160411215917_add_verification_token_to_users.rb +10 -0
  33. data/db/migrate/20160414145851_add_api_key_to_users.rb +5 -0
  34. data/lib/rails_identity/engine.rb +9 -0
  35. data/lib/rails_identity/version.rb +3 -0
  36. data/lib/rails_identity.rb +52 -0
  37. data/lib/tasks/rails_identity_tasks.rake +4 -0
  38. data/test/controllers/rails_identity/sessions_controller_test.rb +192 -0
  39. data/test/controllers/rails_identity/users_controller_test.rb +253 -0
  40. data/test/dummy/README.rdoc +28 -0
  41. data/test/dummy/Rakefile +6 -0
  42. data/test/dummy/app/assets/javascripts/application.js +13 -0
  43. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  44. data/test/dummy/app/controllers/application_controller.rb +5 -0
  45. data/test/dummy/app/helpers/application_helper.rb +2 -0
  46. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  47. data/test/dummy/bin/bundle +3 -0
  48. data/test/dummy/bin/rails +4 -0
  49. data/test/dummy/bin/rake +4 -0
  50. data/test/dummy/bin/setup +29 -0
  51. data/test/dummy/config/application.rb +26 -0
  52. data/test/dummy/config/boot.rb +5 -0
  53. data/test/dummy/config/database.yml +25 -0
  54. data/test/dummy/config/environment.rb +5 -0
  55. data/test/dummy/config/environments/development.rb +41 -0
  56. data/test/dummy/config/environments/production.rb +79 -0
  57. data/test/dummy/config/environments/test.rb +42 -0
  58. data/test/dummy/config/initializers/assets.rb +11 -0
  59. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  60. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/initializers/mime_types.rb +4 -0
  64. data/test/dummy/config/initializers/session_store.rb +3 -0
  65. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  66. data/test/dummy/config/locales/en.yml +23 -0
  67. data/test/dummy/config/routes.rb +4 -0
  68. data/test/dummy/config/secrets.yml +22 -0
  69. data/test/dummy/config.ru +4 -0
  70. data/test/dummy/db/development.sqlite3 +0 -0
  71. data/test/dummy/db/schema.rb +42 -0
  72. data/test/dummy/db/test.sqlite3 +0 -0
  73. data/test/dummy/log/development.log +215 -0
  74. data/test/dummy/log/test.log +280622 -0
  75. data/test/dummy/public/404.html +67 -0
  76. data/test/dummy/public/422.html +67 -0
  77. data/test/dummy/public/500.html +66 -0
  78. data/test/dummy/public/favicon.ico +0 -0
  79. data/test/dummy/tmp/cache/A5C/3F0/rails-identity-0.0.1-session-1 +0 -0
  80. data/test/fixtures/rails_identity/sessions.yml +36 -0
  81. data/test/fixtures/rails_identity/users.yml +24 -0
  82. data/test/integration/navigation_test.rb +10 -0
  83. data/test/jobs/rails_identity/sessions_cleanup_job_test.rb +9 -0
  84. data/test/mailers/previews/rails_identity/user_mailer_preview.rb +6 -0
  85. data/test/mailers/rails_identity/user_mailer_test.rb +9 -0
  86. data/test/models/rails_identity/session_test.rb +26 -0
  87. data/test/models/rails_identity/user_test.rb +54 -0
  88. data/test/rails_identity_test.rb +7 -0
  89. data/test/test_helper.rb +33 -0
  90. metadata +297 -0
@@ -0,0 +1,44 @@
1
+ module RailsIdentity
2
+ class Session < ActiveRecord::Base
3
+ include UUIDModel
4
+ # does not act as paranoid!
5
+
6
+ belongs_to :user, foreign_key: "user_uuid", primary_key: "uuid"
7
+ validates :user, presence: true
8
+
9
+ ##
10
+ # Creates a session object. The attributes must include user. The secret
11
+ # to the JWT is generated here and is unique to the session being
12
+ # created. Since the JWT includes the session id, the secret can be
13
+ # retrieved.
14
+ #
15
+ def initialize(attributes = {})
16
+ seconds = attributes.delete(:seconds) || (24 * 3600 * 14)
17
+ super
18
+ self.uuid = UUIDTools::UUID.timestamp_create().to_s
19
+ iat = Time.now.to_i
20
+ payload = {
21
+ user_uuid: self.user_uuid,
22
+ session_uuid: self.uuid,
23
+ role: self.user.role,
24
+ iat: iat,
25
+ exp: iat + seconds
26
+ }
27
+ self.secret = UUIDTools::UUID.random_create
28
+ self.token = JWT.encode(payload, self.secret, 'HS256')
29
+ end
30
+
31
+ ##
32
+ # Determines if the session has expired or not.
33
+ #
34
+ def expired?
35
+ begin
36
+ JWT.decode self.token, nil, false
37
+ rescue JWT::ExpiredSignature
38
+ return true
39
+ end
40
+ return false
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ module RailsIdentity
2
+ class User < ActiveRecord::Base
3
+ include UUIDModel
4
+ acts_as_paranoid
5
+ has_secure_password
6
+
7
+ validates :username, presence: true, uniqueness: true,
8
+ format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i,
9
+ on: [:create, :update] }
10
+ validates :password, confirmation: true
11
+ before_save :default_role
12
+
13
+ alias_attribute :email, :username
14
+
15
+ ##
16
+ # Initializes the user. User is not verified initially. The user has one
17
+ # hour to get verified. After that, a PATCH request must be made to
18
+ # re-issue the verification token.
19
+ #
20
+ def initialize(attributes = {})
21
+ super
22
+ session = Session.new(user: self, seconds: 3600)
23
+ self.verification_token = session.token
24
+ self.verified = false
25
+ end
26
+
27
+ ##
28
+ # Sets the default the role for the user if not set.
29
+ #
30
+ def default_role
31
+ self.role ||= Roles::USER
32
+ end
33
+
34
+ ##
35
+ # This method will generate a reset token that lasts for an hour.
36
+ #
37
+ def issue_token(kind)
38
+ session = Session.new(user: self, seconds: 3600)
39
+ session.save
40
+ if kind == :reset_token
41
+ self.reset_token = session.token
42
+ elsif kind == :verification_token
43
+ self.verification_token = session.token
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <%= yield %>
4
+ </body>
5
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>RailsIdentity</title>
5
+ <%= stylesheet_link_tag "rails_identity/application", media: "all" %>
6
+ <%= javascript_include_tag "rails_identity/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,12 @@
1
+ <p>Dear <%= @user.username %>,</p>
2
+
3
+ <p>Please confirm your account with rails-identity by making a PATCH request
4
+ on the current user with a provided verification token. For example,
5
+ <pre>http PATCH /users/current token=<%= @user.verification_token %>
6
+ verified=true</pre> will confirm the account. Here is the verification
7
+ token:</p>
8
+
9
+ <pre><%= @user.verification_token %></pre>
10
+
11
+ <p>Thank you for using rails-identity</p>
12
+ <p><b>rails-identity</b></p>
@@ -0,0 +1,13 @@
1
+ Dear <%= @user.username %>,
2
+
3
+ Please confirm your account with rails-identity by making a PATCH request
4
+ on the current user with a provided verification token. For example,
5
+
6
+ http PATCH /users/current token=<%= @user.verification_token %> verified=true
7
+
8
+ will confirm the account. Here is the verification token:
9
+
10
+ <%= @user.verification_token %>
11
+
12
+ Thank you for using rails-identity,
13
+ rails-identity
@@ -0,0 +1,14 @@
1
+ <p>Dear <%= @user.username %>,</p>
2
+
3
+ <p>You have requested to reset your password. Here are the user UUID and
4
+ reset token. Make a PATCH request on the UUID with the reset token to set a
5
+ new password. For instance, <pre>http PATCH /users/current token=<%=
6
+ @user.reset_token %> password=reallysecret
7
+ password_confirmation=reallysecret</pre> will set the password to
8
+ <pre>reallysecret</pre> for the user to whom the reset token was issued.
9
+ Here is the reset token:</p>
10
+
11
+ <pre><%= @user.reset_token %></pre>
12
+
13
+ <p>Good luck! :)</p>
14
+ <p><b>rails-identity</b></p>
@@ -0,0 +1,15 @@
1
+ Dear <%= @user.username %>,
2
+
3
+ You have requested to reset your password. Here are the user UUID and reset
4
+ token. Make a PATCH request on the UUID with the reset token to set a new
5
+ password. For instance,
6
+
7
+ http PATCH /users/current token=... password=reallysecret password_confirmation=reallysecret
8
+
9
+ will set the password to "reallysecret" (without quotes) for the user to
10
+ whom the reset token was issued.
11
+
12
+ Here is the reset token: @user.reset_token
13
+
14
+ Good luck! :)
15
+ rails-identity
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ RailsIdentity::Engine.routes.draw do
2
+ resources :sessions
3
+ match 'sessions(/:id)' => 'sessions#options', via: [:options]
4
+
5
+ resources :users
6
+ match 'users(/:id)' => 'users#options', via: [:options]
7
+ end
@@ -0,0 +1,13 @@
1
+ class CreateRailsIdentityUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :rails_identity_users do |t|
4
+ t.string :uuid, primary_key: true, null: false
5
+ t.string :username
6
+ t.string :password_digest
7
+ t.integer :role
8
+ t.string :metadata
9
+ t.datetime :deleted_at, index: true
10
+ t.timestamps null: false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ class CreateRailsIdentitySessions < ActiveRecord::Migration
2
+ def change
3
+ create_table :rails_identity_sessions do |t|
4
+ t.string :uuid, primary_key: true, null: false
5
+ t.string :user_uuid, null: false
6
+ t.string :token, null: false
7
+ t.string :secret, null: false
8
+ t.string :metadata
9
+ t.timestamps null: false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ class AddResetTokenToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :rails_identity_users, :reset_token, :string
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class AddVerificationTokenToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :rails_identity_users, :verification_token, :string
4
+ add_column :rails_identity_users, :verified, :boolean, default: false
5
+
6
+ # Assign true for existing accounts since they existed without
7
+ # a verification token.
8
+ users = RailsIdentity::User.update_all(verified: true)
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class AddApiKeyToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :rails_identity_users, :api_key, :string
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ Gem.loaded_specs['rails-identity'].dependencies.each do |d|
2
+ require d.name
3
+ end
4
+
5
+ module RailsIdentity
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace RailsIdentity
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module RailsIdentity
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,52 @@
1
+ require "rails_identity/engine"
2
+
3
+ module RailsIdentity
4
+
5
+ # App MUST monkey patch this constant
6
+ MAILER_EMAIL = "no-reply@rails-identity.com"
7
+
8
+ # To be able to break cache when backwards compatibility breaks
9
+ CACHE_PREFIX = "rails-identity-#{RailsIdentity::VERSION}"
10
+
11
+ # Fixed set of roles.
12
+ module Roles
13
+ PUBLIC = 0
14
+ USER = 10
15
+ ADMIN = 100
16
+ OWNER = 1000
17
+ end
18
+
19
+ # Errors defined by RailsIdentity
20
+ module Errors
21
+ class InvalidTokenError < StandardError; end
22
+ class ObjectNotFoundError < StandardError; end
23
+ class UnauthorizedError < StandardError; end
24
+ class InvalidOldPasswordError < StandardError; end
25
+ class InvalidResetTokenError < StandardError; end
26
+ end
27
+
28
+ # This module is a mixin that allows the model to use UUIDs instead of
29
+ # normal IDs. By including this module, the model class declares that the
30
+ # primary key is called "uuid" and an UUID is generated right before
31
+ # save(). You may assign an UUID prior to save, in which case, no new UUID
32
+ # will be generated.
33
+ module UUIDModel
34
+
35
+ def self.included(klass)
36
+ # Triggered when this module is included.
37
+
38
+ klass.primary_key = "uuid"
39
+ klass.before_create :generate_uuid
40
+ end
41
+
42
+ def generate_uuid()
43
+ # Generates an UUID for the model object only if it hasn't been assigned
44
+ # one yet.
45
+
46
+ if self.uuid.nil?
47
+ self.uuid = UUIDTools::UUID.timestamp_create().to_s
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails_identity do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,192 @@
1
+ require 'test_helper'
2
+
3
+ module RailsIdentity
4
+ class SessionsControllerTest < ActionController::TestCase
5
+ setup do
6
+ Rails.cache.clear
7
+ @routes = Engine.routes
8
+ @session = rails_identity_sessions(:one)
9
+ @token = @session.token
10
+ end
11
+
12
+ test "public can see options" do
13
+ get :options
14
+ assert_response :success
15
+ end
16
+
17
+ test "user can list all his sessions" do
18
+ get :index, token: @token
19
+ assert_response :success
20
+ sessions = assigns(:sessions)
21
+ assert_not_nil sessions
22
+ all_his_sessions = Session.where(user: @session.user)
23
+ assert_equal sessions.length, all_his_sessions.length
24
+ sessions.each do |session|
25
+ assert session.user == @session.user
26
+ end
27
+ end
28
+
29
+ test "user can list all his sessions using user id in routing" do
30
+ get :index, user_id: @session.user.uuid, token: @token
31
+ assert_response :success
32
+ sessions = assigns(:sessions)
33
+ assert_not_nil sessions
34
+ all_his_sessions = Session.where(user: @session.user)
35
+ assert_equal sessions.length, all_his_sessions.length
36
+ sessions.each do |session|
37
+ assert session.user == @session.user
38
+ end
39
+ end
40
+
41
+ test "user cannot list expired session" do
42
+ session = Session.new(user: @session.user, seconds: -1)
43
+ session.save()
44
+ get :index, user_id: session.user.uuid, token: @token
45
+ assert_response :success
46
+ json = JSON.parse(@response.body)
47
+ assert_equal 1, json.length
48
+ end
49
+
50
+ test "user cannot list other's sessions" do
51
+ get :index, user_id: rails_identity_users(:two), token: @token
52
+ assert_response 401
53
+ end
54
+
55
+ test "public cannot list sessions" do
56
+ get :index
57
+ assert_response 401
58
+ end
59
+
60
+ test "create a session" do
61
+ user = rails_identity_users(:one)
62
+ post :create, username: user.username, password: "password"
63
+ assert_response :success
64
+ session = assigns(:session)
65
+ assert_not_nil session
66
+ json = JSON.parse(@response.body)
67
+ assert json.has_key?("token")
68
+ assert !json.has_key?("secret")
69
+ end
70
+
71
+ test "cannot create a session if not verified" do
72
+ user = rails_identity_users(:one)
73
+ user.verified = false
74
+ user.save()
75
+ post :create, username: user.username, password: "password"
76
+ assert_response 401
77
+ end
78
+
79
+ test "cannot create a session with non-existent username" do
80
+ post :create, username: 'idontexist', password: "secret"
81
+ assert_response 401
82
+ json = JSON.parse(@response.body)
83
+ assert json["errors"].length == 1
84
+ end
85
+
86
+ test "cannot create a session without username" do
87
+ post :create, password: "secret"
88
+ assert_response 401
89
+ json = JSON.parse(@response.body)
90
+ assert json["errors"].length == 1
91
+ end
92
+
93
+ test "cannot create a session without a password" do
94
+ post :create, username: rails_identity_users(:one).username
95
+ assert_response 401
96
+ json = JSON.parse(@response.body)
97
+ assert json["errors"].length == 1
98
+ end
99
+
100
+ test "cannot create a session with a wrong password" do
101
+ post :create, username: rails_identity_users(:one).username, password: "notsecret"
102
+ assert_response 401
103
+ json = JSON.parse(@response.body)
104
+ assert json["errors"].length == 1
105
+ end
106
+
107
+ test "public cannot create sessions" do
108
+ get :index
109
+ assert_response 401
110
+ end
111
+
112
+ test "show a session" do
113
+ get :show, id: 1, token: @token
114
+ assert_response 200
115
+ json = JSON.parse(@response.body)
116
+ assert_equal @token, json["token"]
117
+ # Do a quick cache check
118
+ session = Rails.cache.fetch("#{CACHE_PREFIX}-session-#{json["uuid"]}")
119
+ assert_not_nil session
120
+ assert_equal @token, session.token
121
+ end
122
+
123
+ test "show a current session" do
124
+ get :show, id: "current", token: @token
125
+ assert_response 200
126
+ json = JSON.parse(@response.body)
127
+ assert_equal @token, json["token"]
128
+ end
129
+
130
+ test "cannot show other's session" do
131
+ get :show, id: 2, token: @token
132
+ assert_response 401
133
+ end
134
+
135
+ test "cannot show expired session" do
136
+ session = Session.new(user: @session.user, seconds: -1)
137
+ session.save()
138
+ get :show, id: session.uuid, token: @token
139
+ assert_response 404
140
+ end
141
+
142
+ test "public cannot show session" do
143
+ get :show, id:1
144
+ assert_response 401
145
+ end
146
+
147
+ test "admin can show other's session" do
148
+ @session = rails_identity_sessions(:admin_one)
149
+ @token = @session.token
150
+ get :show, id: 1, token: @token
151
+ assert_response :success
152
+ json = JSON.parse(@response.body)
153
+ session = rails_identity_sessions(:one)
154
+ assert_equal session.token, json["token"]
155
+ end
156
+
157
+ test "cannot show a nonexisting session" do
158
+ get :show, id: 999, token: @token
159
+ assert_response 404
160
+ json = JSON.parse(@response.body)
161
+ assert json["errors"].length == 1
162
+ end
163
+
164
+ test "delete a session" do
165
+ delete :destroy, id: 1, token: @token
166
+ assert_response 204
167
+ end
168
+
169
+ test "delete a current session" do
170
+ delete :destroy, id: "current", token: @token
171
+ assert_response 204
172
+ end
173
+
174
+ test "cannot delete a non-existent session" do
175
+ delete :destroy, id: 999, token: @token
176
+ assert_response 404
177
+ end
178
+
179
+ test "cannot delete other's session" do
180
+ delete :destroy, id: 2, token: @token
181
+ assert_response 401
182
+ end
183
+
184
+ test "admin can delete other's session" do
185
+ @session = rails_identity_sessions(:admin_one)
186
+ @token = @session.token
187
+ delete :destroy, id: 1, token: @token
188
+ assert_response :success
189
+ end
190
+
191
+ end
192
+ end