linked_rails-auth 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.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +28 -0
- data/Rakefile +34 -0
- data/app/actions/linked_rails/auth/access_token_action_list.rb +16 -0
- data/app/actions/linked_rails/auth/confirmation_action_list.rb +17 -0
- data/app/actions/linked_rails/auth/otp_attempt_action_list.rb +13 -0
- data/app/actions/linked_rails/auth/otp_secret_action_list.rb +31 -0
- data/app/actions/linked_rails/auth/password_action_list.rb +25 -0
- data/app/actions/linked_rails/auth/registration_action_list.rb +15 -0
- data/app/actions/linked_rails/auth/session_action_list.rb +22 -0
- data/app/actions/linked_rails/auth/unlock_action_list.rb +17 -0
- data/app/controllers/linked_rails/auth/access_tokens_controller.rb +131 -0
- data/app/controllers/linked_rails/auth/confirmations_controller.rb +87 -0
- data/app/controllers/linked_rails/auth/otp_attempts_controller.rb +21 -0
- data/app/controllers/linked_rails/auth/otp_secrets_controller.rb +40 -0
- data/app/controllers/linked_rails/auth/passwords_controller.rb +63 -0
- data/app/controllers/linked_rails/auth/registrations_controller.rb +33 -0
- data/app/controllers/linked_rails/auth/sessions_controller.rb +55 -0
- data/app/controllers/linked_rails/auth/unlocks_controller.rb +44 -0
- data/app/forms/linked_rails/auth/access_token_form.rb +20 -0
- data/app/forms/linked_rails/auth/confirmation_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_attempt_form.rb +9 -0
- data/app/forms/linked_rails/auth/otp_secret_form.rb +12 -0
- data/app/forms/linked_rails/auth/password_form.rb +21 -0
- data/app/forms/linked_rails/auth/registration_form.rb +21 -0
- data/app/forms/linked_rails/auth/session_form.rb +13 -0
- data/app/forms/linked_rails/auth/unlock_form.rb +9 -0
- data/app/helpers/linked_rails/auth/otp_helper.rb +30 -0
- data/app/models/linked_rails/auth/access_token.rb +40 -0
- data/app/models/linked_rails/auth/confirmation.rb +70 -0
- data/app/models/linked_rails/auth/guest_user.rb +29 -0
- data/app/models/linked_rails/auth/otp_attempt.rb +41 -0
- data/app/models/linked_rails/auth/otp_base.rb +57 -0
- data/app/models/linked_rails/auth/otp_secret.rb +91 -0
- data/app/models/linked_rails/auth/password.rb +47 -0
- data/app/models/linked_rails/auth/registration.rb +39 -0
- data/app/models/linked_rails/auth/session.rb +46 -0
- data/app/models/linked_rails/auth/unlock.rb +59 -0
- data/app/policies/linked_rails/auth/access_token_policy.rb +17 -0
- data/app/policies/linked_rails/auth/confirmation_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_attempt_policy.rb +17 -0
- data/app/policies/linked_rails/auth/otp_secret_policy.rb +37 -0
- data/app/policies/linked_rails/auth/password_policy.rb +23 -0
- data/app/policies/linked_rails/auth/registration_policy.rb +13 -0
- data/app/policies/linked_rails/auth/session_policy.rb +17 -0
- data/app/policies/linked_rails/auth/unlock_policy.rb +21 -0
- data/app/serializers/linked_rails/auth/access_token_serializer.rb +11 -0
- data/app/serializers/linked_rails/auth/confirmation_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/otp_attempt_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/otp_secret_serializer.rb +14 -0
- data/app/serializers/linked_rails/auth/password_serializer.rb +18 -0
- data/app/serializers/linked_rails/auth/registration_serializer.rb +12 -0
- data/app/serializers/linked_rails/auth/session_serializer.rb +10 -0
- data/app/serializers/linked_rails/auth/unlock_serializer.rb +9 -0
- data/config/routes.rb +4 -0
- data/lib/generators/linked_rails/auth/install_generator.rb +155 -0
- data/lib/generators/linked_rails/auth/templates/README +2 -0
- data/lib/generators/linked_rails/auth/templates/doorkeeper_jwt_initializer.rb +52 -0
- data/lib/generators/linked_rails/auth/templates/locales.yml +39 -0
- data/lib/generators/linked_rails/auth/templates/migration.rb.erb +20 -0
- data/lib/linked_rails/auth/auth_helper.rb +101 -0
- data/lib/linked_rails/auth/controller/error_handling.rb +17 -0
- data/lib/linked_rails/auth/controller.rb +16 -0
- data/lib/linked_rails/auth/engine.rb +8 -0
- data/lib/linked_rails/auth/errors/account_locked.rb +10 -0
- data/lib/linked_rails/auth/errors/expired.rb +10 -0
- data/lib/linked_rails/auth/errors/unauthorized.rb +10 -0
- data/lib/linked_rails/auth/errors/unknown_email.rb +14 -0
- data/lib/linked_rails/auth/errors/wrong_password.rb +14 -0
- data/lib/linked_rails/auth/errors.rb +7 -0
- data/lib/linked_rails/auth/routes.rb +86 -0
- data/lib/linked_rails/auth/version.rb +7 -0
- data/lib/linked_rails/auth.rb +46 -0
- data/lib/tasks/linked_rails/auth_tasks.rake +6 -0
- metadata +257 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class AccessTokenPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[email password redirect_url]
|
7
|
+
|
8
|
+
def create?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def show?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpAttemptPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[otp_attempt]
|
7
|
+
|
8
|
+
def show?
|
9
|
+
user_context.guest?
|
10
|
+
end
|
11
|
+
|
12
|
+
def create?
|
13
|
+
user_context.guest? && record.active?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpSecretPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[otp_attempt]
|
7
|
+
|
8
|
+
def show?
|
9
|
+
user_context.guest? || current_user? || administrate_otp?
|
10
|
+
end
|
11
|
+
|
12
|
+
def create?
|
13
|
+
return forbid_with_message(I18n.t('messages.otp_secrets.already_exists')) if user_context.otp_active?
|
14
|
+
|
15
|
+
user_context.guest? || current_user?
|
16
|
+
end
|
17
|
+
|
18
|
+
def destroy?
|
19
|
+
raise(ActiveRecord::RecordNotFound) unless administrate_otp? || current_user?
|
20
|
+
|
21
|
+
return forbid_with_message(I18n.t('messages.otp_secrets.not_activated')) unless record.active?
|
22
|
+
|
23
|
+
current_user? || administrate_otp?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def current_user?
|
29
|
+
record.owner_id == user_context.id
|
30
|
+
end
|
31
|
+
|
32
|
+
def administrate_otp?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class PasswordPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[password password_confirmation reset_password_token],
|
7
|
+
has_properties: {reset_password_token: true}
|
8
|
+
permit_attributes %i[email], has_properties: {reset_password_token: false}
|
9
|
+
|
10
|
+
def create?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def update?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def show?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class RegistrationPolicy < Pundit::PolicyFinder.new(LinkedRails.user_class).policy || LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[email password password_confirmation redirect_url]
|
7
|
+
|
8
|
+
def create?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class SessionPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[email redirect_url]
|
7
|
+
|
8
|
+
def create?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def show?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class UnlockPolicy < LinkedRails.policy_parent_class
|
6
|
+
permit_attributes %i[email]
|
7
|
+
|
8
|
+
def create?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def update?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def show?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class AccessTokenSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
|
7
|
+
attribute :password, predicate: Vocab.ontola[:password], datatype: RDF::XSD[:string]
|
8
|
+
attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class ConfirmationSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
|
7
|
+
attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpAttemptSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :otp_attempt,
|
7
|
+
predicate: LinkedRails.app_ns[:otp],
|
8
|
+
datatype: RDF::XSD[:integer],
|
9
|
+
if: method(:never)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class OtpSecretSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :otp_attempt,
|
7
|
+
predicate: LinkedRails.app_ns[:otp],
|
8
|
+
datatype: RDF::XSD[:integer],
|
9
|
+
if: method(:never)
|
10
|
+
attribute :active, predicate: LinkedRails.app_ns[:otpActive]
|
11
|
+
has_one :image, predicate: Vocab.schema.image
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class PasswordSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
|
7
|
+
attribute :password,
|
8
|
+
predicate: Vocab.ontola[:password],
|
9
|
+
datatype: Vocab.ontola['datatype/password'],
|
10
|
+
if: method(:never)
|
11
|
+
attribute :password_confirmation,
|
12
|
+
predicate: Vocab.ontola[:passwordConfirmation],
|
13
|
+
datatype: Vocab.ontola['datatype/password'],
|
14
|
+
if: method(:never)
|
15
|
+
attribute :reset_password_token, predicate: Vocab.ontola[:resetPasswordToken], datatype: RDF::XSD[:string]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class RegistrationSerializer < RDF::Serializers.serializer_for(LinkedRails.user_class) || LinkedRails.serializer_parent_class
|
6
|
+
attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
|
7
|
+
attribute :password, predicate: Vocab.ontola[:password], datatype: RDF::XSD[:string]
|
8
|
+
attribute :password_confirmation, predicate: Vocab.ontola[:passwordConfirmation], datatype: RDF::XSD[:string]
|
9
|
+
attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LinkedRails
|
4
|
+
module Auth
|
5
|
+
class SessionSerializer < LinkedRails.serializer_parent_class
|
6
|
+
attribute :email, predicate: Vocab.schema.email, datatype: RDF::XSD[:string]
|
7
|
+
attribute :redirect_url, predicate: Vocab.ontola[:redirectUrl], datatype: RDF::XSD[:string]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module LinkedRails
|
7
|
+
module Auth
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
9
|
+
include ::Rails::Generators::Migration
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
11
|
+
desc "Installs LinkedRails Auth."
|
12
|
+
|
13
|
+
def install
|
14
|
+
template "doorkeeper_jwt_initializer.rb", "config/initializers/doorkeeper_jwt.rb"
|
15
|
+
template "locales.yml", "config/locales/linked_rails_auth.en.yml"
|
16
|
+
route "use_linked_rails_auth"
|
17
|
+
|
18
|
+
migration_template(
|
19
|
+
"migration.rb.erb",
|
20
|
+
"db/migrate/install_linked_rails_auth.rb",
|
21
|
+
migration_version: migration_version
|
22
|
+
)
|
23
|
+
update_user_model
|
24
|
+
insert_doorkeeper
|
25
|
+
create_doorkeeper_app
|
26
|
+
|
27
|
+
readme "README"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def create_doorkeeper_app
|
33
|
+
if Doorkeeper::Application.any?
|
34
|
+
Rails.logger.info('Skipping Doorkeeper app creation, already exists')
|
35
|
+
|
36
|
+
return
|
37
|
+
end
|
38
|
+
client_id = ask('Enter the client_id of your doorkeeper app', default: 'client_id')
|
39
|
+
client_secret = ask('Enter the client_secret of your doorkeeper app', default: 'client_secret')
|
40
|
+
client_token = ask('Enter the client_token of your doorkeeper app. Leave empty to create new.')
|
41
|
+
|
42
|
+
libro_app = Doorkeeper::Application.find_or_initialize_by(uid: client_id) do |app|
|
43
|
+
app.name = 'Libro'
|
44
|
+
app.redirect_uri = 'http://example.com/'
|
45
|
+
app.scopes = 'guest user'
|
46
|
+
app.secret = client_secret
|
47
|
+
end
|
48
|
+
libro_app.save!(validate: false)
|
49
|
+
# rubocop:disable Rails/SkipsModelValidations
|
50
|
+
libro_app.update_columns(uid: client_id, secret: client_secret)
|
51
|
+
# rubocop:enable Rails/SkipsModelValidations
|
52
|
+
|
53
|
+
ActiveRecord::Base.connection.try(:reset_pk_sequence!, Doorkeeper::Application.table_name)
|
54
|
+
|
55
|
+
token = Doorkeeper::AccessToken.find_or_create_for(
|
56
|
+
application: libro_app,
|
57
|
+
scopes: 'service',
|
58
|
+
expires_in: 10.years.to_i,
|
59
|
+
resource_owner: nil,
|
60
|
+
use_refresh_token: true
|
61
|
+
)
|
62
|
+
|
63
|
+
if client_token.present?
|
64
|
+
token.update(token: client_token)
|
65
|
+
else
|
66
|
+
Rails.logger.info("Generated client token: #{token.token}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def insert_doorkeeper
|
71
|
+
file = 'config/initializers/doorkeeper.rb'
|
72
|
+
data = "api_only\n"\
|
73
|
+
"base_controller 'ApplicationController'\n"\
|
74
|
+
"base_metal_controller 'ApplicationController'\n"
|
75
|
+
inject_into_file file, optimize_indentation(data, 2), after: "orm :active_record\n", verbose: false
|
76
|
+
uncomment_lines file, "access_token_generator '::Doorkeeper::JWT'"
|
77
|
+
uncomment_lines file, 'use_refresh_token'
|
78
|
+
|
79
|
+
replace_doorkeeper_line(
|
80
|
+
'# default_scopes :public',
|
81
|
+
'default_scopes :guest'
|
82
|
+
)
|
83
|
+
replace_doorkeeper_line(
|
84
|
+
'# optional_scopes :write, :update',
|
85
|
+
'optional_scopes :user'
|
86
|
+
)
|
87
|
+
replace_doorkeeper_line(
|
88
|
+
'# grant_flows %w[authorization_code client_credentials]',
|
89
|
+
'grant_flows %w[client_credentials authorization_code password]'
|
90
|
+
)
|
91
|
+
replace_doorkeeper_line("resource_owner_authenticator do\n(.*?)end\n", authentication, true)
|
92
|
+
end
|
93
|
+
|
94
|
+
def authentication
|
95
|
+
<<-FOO
|
96
|
+
resource_owner_authenticator do
|
97
|
+
if doorkeeper_token&.acceptable?('user')
|
98
|
+
User.find_by(id: doorkeeper_token.resource_owner_id)
|
99
|
+
elsif doorkeeper_token&.acceptable?('guest') && doorkeeper_token_payload['user']
|
100
|
+
LinkedRails.guest_user_class.new(id: doorkeeper_token.resource_owner_id)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
resource_owner_from_credentials do
|
105
|
+
request.params[:user] = request.params[:access_token] || {}
|
106
|
+
request.params[:user][:email] ||= (request.params[:username] || request.params[:email])&.downcase
|
107
|
+
request.params[:user][:password] ||= request.params[:token] || request.params[:password]
|
108
|
+
request.env['devise.allow_params_authentication'] = true
|
109
|
+
user =
|
110
|
+
if request.params[:scope] == 'guest'
|
111
|
+
LinkedRails.guest_user_class.new
|
112
|
+
else
|
113
|
+
request.env['warden'].authenticate(scope: :user, store: false)
|
114
|
+
end
|
115
|
+
raise_login_error(request) if user.blank?
|
116
|
+
request.env['warden'].logout
|
117
|
+
user
|
118
|
+
end
|
119
|
+
FOO
|
120
|
+
end
|
121
|
+
|
122
|
+
def inject_controller_include
|
123
|
+
sentinel = /LinkedRails::Controller\n/m
|
124
|
+
in_root do
|
125
|
+
inject_into_file(
|
126
|
+
"app/controllers/application_controller.rb",
|
127
|
+
optimize_indentation('include LinkedRails::Auth::AuthHelper', 2),
|
128
|
+
after: sentinel
|
129
|
+
)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def migration_version
|
134
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
135
|
+
end
|
136
|
+
|
137
|
+
def replace_doorkeeper_line(old, new, multi_line_sentinel = false)
|
138
|
+
sentinel = multi_line_sentinel ? /^(\s*)#{old}/m : /^(\s*)#[[:blank:]]*(.*#{old})/
|
139
|
+
gsub_file('config/initializers/doorkeeper.rb', sentinel, new)
|
140
|
+
end
|
141
|
+
|
142
|
+
def update_user_model
|
143
|
+
file = 'app/models/user.rb'
|
144
|
+
no_guest = "\ndef guest?\n false\nend"
|
145
|
+
inject_into_file file, optimize_indentation(no_guest, 2), after: ":recoverable, :rememberable, :validatable\n", verbose: false
|
146
|
+
end
|
147
|
+
|
148
|
+
class << self
|
149
|
+
def next_migration_number(dirname)
|
150
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|