remark_login_client 0.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 261e8a37b92b59bdd8b7cf2a29aa70b04a2d75beec1b6bd86ca432eb7310d5f7
4
+ data.tar.gz: a45e3d99e963a43466d9a2f079825714550c36a2802f21c1de8d742a473d72e1
5
+ SHA512:
6
+ metadata.gz: '000590b196484b778fc7e40921b13d7ff856bccb345bef023376326d5439fcda3c1de6cb2d94bf1c5d16b28ee86cb676da5d9aaef46fcf13daa0dbf08a38adb8'
7
+ data.tar.gz: b0499834a930161b4c76883ec2d6078e96e71edb839fb601164bfb585ef8b834e41d494efba38a9b0bae74adcde8b6b6c6a13a444b5cf062da16c1cd361ac15d
data/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # ReMark Login Client
2
+
3
+ Welcome to the readme of ReMark Login Client. This library is used to connect an application to the Remark Login application. It extends the standard Devise functionality to login via Doorkeeper as an omniauth_provider.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'remark_login_client',
11
+ git: 'git@bitbucket.org:ivaldi/remark-login-client.git', branch: :development
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install remark_login_client
21
+
22
+ ### 1. Setup application and roles
23
+
24
+ In ReMark Login we need to define the application and roles
25
+
26
+ Example script to run from your rails console:
27
+
28
+ ```ruby
29
+ begin
30
+ # clean up data from possible outdated seeds.
31
+ Application.destroy_all
32
+ Role.destroy_all
33
+
34
+ Application.create!(
35
+ name: 'Demo Tool',
36
+ key: 'demo',
37
+ sequence: 3,
38
+ uid: 'NotSoRandomDemoToolUid',
39
+ secret: 'NotSoRandomDemoToolSecret',
40
+ redirect_uri: 'http://localhost:3002/users/auth/doorkeeper/callback',
41
+ logout_api_url: 'http://localhost:3002/session_timeout',
42
+ roles: [
43
+ Role.create(name: 'Superadmin', key: 'superadmin', sequence: 1, admin_rights: true),
44
+ Role.create(name: 'user', key: 'user', sequence: 2, admin_rights: false)
45
+ ]
46
+ )
47
+ User.all.each do |user|
48
+ user.update!(roles: Role.all)
49
+ end
50
+ end
51
+ ```
52
+
53
+ ### 2. Configure secrets
54
+
55
+ Make sure the following keys and their values become available in your client application's Rails.application.secrets.
56
+
57
+ - **APP_URL** (the url of the client applicaton)
58
+ - **DOORKEEPER_APP_URL** (the url of the ReMark Login application)
59
+ - **DOORKEEPER_INTERNAL_APP_URL** (the url of the ReMark Login application if it's used within a Docker environment)
60
+ - **DOORKEEPER_APP_ID** (The client application ID in ReMark Login)
61
+ - **DOORKEEPER_APP_SECRET** (The client application Secret in ReMark Login)
62
+
63
+ Locally this can be done by adding setting these key->value pairs in your .env file and include the following line in your environments/development.rb file
64
+
65
+ ```ruby
66
+ # load .env file in secrets
67
+ Rails.application.secrets.merge!(Dotenv.load('.env').symbolize_keys)
68
+ ```
69
+
70
+ ### 3. Setup Devise
71
+
72
+ ReMark Login uses the omniauth functionality of Devise with Doorkeeper as provider. Make sure your Devise initializer has the following lines:
73
+
74
+ ```ruby
75
+
76
+ require 'omniauth/strategies/doorkeeper'
77
+
78
+ #Devise.setup do |config|
79
+
80
+ config.omniauth :doorkeeper,
81
+ ENV.fetch('DOORKEEPER_APP_ID', nil),
82
+ ENV.fetch('DOORKEEPER_APP_SECRET', nil),
83
+ scope: 'read',
84
+ strategy_class: OmniAuth::Strategies::Doorkeeper
85
+
86
+ OmniAuth.config.allowed_request_methods = [:post, :get]
87
+ OmniAuth.config.silence_get_warning = true
88
+
89
+ #end
90
+ ```
91
+
92
+ What needs to be done in the callbacks controller can vary per application. An example of the required controllers:
93
+
94
+ ```ruby
95
+ # app/controllers/users/omniauth_callbacks_controller.rb
96
+ module Users
97
+ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
98
+ def doorkeeper
99
+ @user = User.from_omniauth(request.env['omniauth.auth'])
100
+
101
+ if @user.persisted?
102
+ @user.update_doorkeeper_credentials(request.env['omniauth.auth'])
103
+
104
+ sign_in_and_redirect @user, event: :authentication
105
+ if is_navigational_format?
106
+ if request.env['omniauth.auth'].info['locale'].present?
107
+ I18n.locale = request.env['omniauth.auth'].info['locale']
108
+ else
109
+ I18n.locale = I18n.default_locale
110
+ end
111
+ set_flash_message(:notice, :success, kind: 'ReMark Login')
112
+ end
113
+ else
114
+ session['devise.doorkeeper_data'] = request.env['omniauth.auth']
115
+ redirect_to new_user_registration_url
116
+ end
117
+ end
118
+
119
+ def failure
120
+ redirect_to root_path
121
+ end
122
+ end
123
+ end
124
+
125
+ # app/controllers/users/sessions_controller.rb
126
+ module Users
127
+ class SessionsController < Devise::SessionsController
128
+ # DELETE /resource/sign_out
129
+ def destroy
130
+ remark_login_connection.delete '/oauth/authorize',
131
+ {
132
+ client_id: Rails.application.secrets.fetch(:DOORKEEPER_APP_ID),
133
+ access_token: current_user&.doorkeeper_access_token
134
+ }
135
+ current_user&.update(doorkeeper_access_token: nil, doorkeeper_refresh_token: nil)
136
+
137
+ sign_out current_user
138
+
139
+ redirect_to root_path, notice: t('devise.sessions.signed_out')
140
+ end
141
+
142
+ def remark_login_connection
143
+ strategy = OmniAuth::Strategies::Doorkeeper.new(nil)
144
+
145
+ Faraday.new(url: strategy.options.client_options.site)
146
+ end
147
+ end
148
+ end
149
+ ```
150
+
151
+ The routes to these controllers need to be defined in the `routes.rb` file:
152
+
153
+ ```ruby
154
+ devise_for :users,
155
+ controllers: {
156
+ omniauth_callbacks: 'users/omniauth_callbacks'
157
+ }
158
+ devise_scope :user do
159
+ delete '/users/sign_out' => 'users/sessions#destroy', as: :destroy_user_session
160
+ end
161
+ ```
162
+
163
+ ### 4. Include ReMark Login in your Controller
164
+
165
+ When the ReMark Login concern is included in the ApplicationController that protects your secured pages, the user is automatically redirected to the ReMark Login signin page.
166
+
167
+ Example:
168
+
169
+ ```ruby
170
+ module Admin
171
+ class ApplicationController < ApplicationController
172
+ include RemarkLoginClient::Authentication
173
+ end
174
+ end
175
+ ```
176
+
177
+ Concern can locally be redefined in `app/controllers/concerns/remark_login_client/authentication.rb`
178
+
179
+ ### 5. Include ReMark Login in your Model
180
+
181
+ Also the model needs a concern to manage the authentication with ReMark Login. `devise` needs to be setup with Doorkeeper as omniauth provider.
182
+
183
+ ```ruby
184
+ class User < ApplicationRecord
185
+ include RemarkLoginClient::UserMethods
186
+
187
+ devise :omniauthable, omniauth_providers: [:doorkeeper]
188
+ ```
189
+
190
+ Concern can locally be redefined in `app/models/concerns/remark_login_client/user_methods.rb`
191
+
192
+ ### 6. Database changes for your Model
193
+
194
+ For a working connection the Model (to be authorized) should have some extra fields.
195
+
196
+ Write migrations for your table to have at least the following columns and index. (table and index name can differ based on your Model's name)
197
+
198
+ ```ruby
199
+ create_table "users", force: :cascade do |t|
200
+ t.string "email", default: "", null: false
201
+ t.integer "status"
202
+ t.string "first_name"
203
+ t.string "last_name"
204
+ t.string "uid" # unique identifier from ReMark Login
205
+ t.string "locale", default: "en"
206
+ t.string "doorkeeper_access_token"
207
+ t.string "doorkeeper_refresh_token"
208
+ t.string "session_token"
209
+ t.jsonb "environment_links", default: {} # can be used for multi application setup
210
+ t.datetime "created_at", null: false
211
+ t.datetime "updated_at", null: false
212
+
213
+ t.index ["email"], name: "index_users_on_email", unique: true
214
+ end
215
+
216
+ ```
@@ -0,0 +1,25 @@
1
+ begin
2
+ require 'omniauth'
3
+ rescue LoadError
4
+ warn "Could not load 'omniauth'. Please ensure you have the " \
5
+ 'omniauth gem >= 1.0.0 installed and listed in your Gemfile.'
6
+ raise
7
+ end
8
+
9
+ # Clean up the default path_prefix. It will be automatically set by Devise.
10
+ OmniAuth.config.path_prefix = nil
11
+
12
+ OmniAuth.config.on_failure = proc do |env|
13
+ # debugger
14
+ env['devise.mapping'] = Devise::Mapping.find_by_path!(env['PATH_INFO'], :path)
15
+ controller_name = ActiveSupport::Inflector.camelize(env['devise.mapping'].controllers[:omniauth_callbacks])
16
+ controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
17
+ controller_klass.action(:failure).call(env)
18
+ end
19
+
20
+ module Devise
21
+ module OmniAuth
22
+ autoload :Config, 'devise/omniauth/config'
23
+ autoload :UrlHelpers, 'devise/omniauth/url_helpers'
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ module OmniAuth
2
+ module Strategies
3
+ class Doorkeeper < OmniAuth::Strategies::OAuth2
4
+ option :name, :doorkeeper
5
+
6
+ option :client_options,
7
+ site: if ENV['COMPOSE'].present?
8
+ ENV.fetch('DOORKEEPER_INTERNAL_APP_URL')
9
+ else
10
+ ENV.fetch('DOORKEEPER_APP_URL')
11
+ end,
12
+ authorize_url: "#{ENV.fetch('DOORKEEPER_APP_URL')}/oauth/authorize",
13
+ redirect_uri: "#{ENV.fetch('APP_URL')}/users/auth/doorkeeper/callback"
14
+
15
+ uid do
16
+ raw_info['login_user_key']
17
+ end
18
+
19
+ info do
20
+ {
21
+ email: raw_info['email'],
22
+ first_name: raw_info['first_name'],
23
+ last_name: raw_info['last_name'],
24
+ login_user_key: raw_info['login_user_key'],
25
+ payload: raw_info['payload'],
26
+ locale: raw_info['locale'],
27
+ environment_links: raw_info['environment_links']
28
+ }
29
+ end
30
+
31
+ def raw_info
32
+ @raw_info ||= access_token.get('/api/v1/me.json').parsed
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ module RemarkLoginClient
2
+ module Authentication
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :redirect_to_remark_login, if: :remark_oauth_login_available?
7
+ before_action :authenticate_user!
8
+ before_action :doorkeeper_access_token, if: :remark_oauth_login_available?
9
+ end
10
+
11
+ def remark_oauth_login_available?
12
+ return @remark_oauth_login_available if @remark_oauth_login_available.present?
13
+
14
+ @remark_oauth_login_available = false
15
+ doorkeeper_app_key = if ENV['COMPOSE'].present?
16
+ 'DOORKEEPER_INTERNAL_APP_URL'
17
+ else
18
+ 'DOORKEEPER_APP_URL'
19
+ end
20
+ remark_login_app_url = ENV.fetch(doorkeeper_app_key).presence
21
+ return false if remark_login_app_url.blank?
22
+
23
+ uri = URI("#{remark_login_app_url}/healthz")
24
+ res = Net::HTTP.get_response(uri)
25
+ if res.present?
26
+ @remark_oauth_login_available = true
27
+ return true
28
+ end
29
+
30
+ false
31
+ rescue StandardError => _e
32
+ false
33
+ end
34
+
35
+ def doorkeeper_oauth_client
36
+ doorkeeper_app_key = if ENV['COMPOSE'].present?
37
+ 'DOORKEEPER_INTERNAL_APP_URL'
38
+ else
39
+ 'DOORKEEPER_APP_URL'
40
+ end
41
+ prefix = '' # prefix = 'GROUP_' if ActsAsTenant.current_tenant.group_portal?
42
+ @doorkeeper_oauth_client = OAuth2::Client.new(
43
+ ENV.fetch("#{prefix}DOORKEEPER_APP_ID"),
44
+ ENV.fetch("#{prefix}DOORKEEPER_APP_SECRET"),
45
+ site: ENV.fetch("#{prefix}#{doorkeeper_app_key}")
46
+ )
47
+ end
48
+
49
+ def doorkeeper_access_token
50
+ return unless user_signed_in?
51
+
52
+ if @doorkeeper_access_token.nil?
53
+ @doorkeeper_access_token = OAuth2::AccessToken
54
+ .new(doorkeeper_oauth_client, current_user.doorkeeper_access_token)
55
+ store_remote_current_user_info_in_session if session[:remote_current_user].blank?
56
+ end
57
+
58
+ # Login.access_token = @doorkeeper_access_token
59
+
60
+ @doorkeeper_access_token
61
+ end
62
+
63
+ def redirect_to_remark_login
64
+ return if user_signed_in?
65
+ return if controller_name == 'session_timeouts'
66
+ return if controller_name == 'omniauth_callbacks'
67
+
68
+ # prevent redirects to paths that do not serve html (i.e session_timeouts)
69
+ store_location_for(:user, @stored_location_for_user.presence || root_path)
70
+ session[:remote_current_user] = nil
71
+
72
+ redirect_to user_doorkeeper_omniauth_authorize_path and return
73
+ end
74
+
75
+ def store_remote_current_user_info_in_session
76
+ return if controller_name == 'omniauth_callbacks' && action_name == 'failure'
77
+
78
+ response = @doorkeeper_access_token.get('api/v1/me')
79
+ session[:remote_current_user] = response.parsed if response.status == 200
80
+ rescue OAuth2::Error, StandardError => e
81
+ # oops, no connection possible
82
+ # capture for Sentry, clear tokens, logout and redirect to Login
83
+
84
+ Sentry.set_user(
85
+ uid: current_user&.uid,
86
+ doorkeeper_access_token: current_user&.doorkeeper_access_token,
87
+ doorkeeper_refresh_token: current_user&.doorkeeper_refresh_token
88
+ )
89
+ Sentry.capture_exception(e)
90
+
91
+ current_user&.update(doorkeeper_access_token: nil, doorkeeper_refresh_token: nil)
92
+ sign_out
93
+
94
+ redirect_to user_doorkeeper_omniauth_authorize_path and return
95
+ end
96
+
97
+ def clear_user_and_redirect_to_login(exception)
98
+ Sentry.set_user({
99
+ uid: current_user&.uid,
100
+ doorkeeper_access_token: current_user&.doorkeeper_access_token,
101
+ doorkeeper_refresh_token: current_user&.doorkeeper_refresh_token
102
+ })
103
+ Sentry.capture_exception(exception)
104
+ current_user&.update(doorkeeper_access_token: nil, doorkeeper_refresh_token: nil)
105
+ sign_out
106
+ redirect_to user_doorkeeper_omniauth_authorize_path and return
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module RemarkLoginClient
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,45 @@
1
+ module RemarkLoginClient
2
+ module UserMethods
3
+ def self.included(klass)
4
+ klass.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def from_omniauth(auth)
9
+ # find existing email first, if not, try on uid for changed address
10
+ where(uid: [auth.uid, nil], email: auth.info.email)
11
+ .or(User.where(uid: auth.uid))
12
+ .first_or_create do |user|
13
+ user.uid = auth.uid if user.uid.blank?
14
+ user.email = auth.info.email
15
+ user.first_name = auth.info.first_name
16
+ user.last_name = auth.info.last_name
17
+ if User.method_defined? :locale
18
+ user.locale = auth.info.locale
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def update_doorkeeper_credentials(auth)
25
+ update!(
26
+ email: auth.info.email,
27
+ first_name: auth.info.first_name,
28
+ last_name: auth.info.last_name,
29
+ doorkeeper_access_token: auth.credentials.token,
30
+ doorkeeper_refresh_token: auth.credentials.refresh_token,
31
+ uid: auth.info.login_user_key,
32
+ environment_links: auth.info.environment_links || {},
33
+ session_token: SecureRandom.hex
34
+ )
35
+ end
36
+
37
+ def provider
38
+ :doorkeeper
39
+ end
40
+
41
+ def authenticatable_salt
42
+ "#{super}#{session_token}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ module RemarkLoginClient
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module RemarkLoginClient
2
+ VERSION = '0.7'.freeze
3
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'remark_login_client/railtie'
4
+ require 'remark_login_client/version'
5
+
6
+ require 'remark_login_client/controllers/concerns/authentication'
7
+ require 'remark_login_client/models/concerns/user_methods'
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: remark_login_client
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.7'
5
+ platform: ruby
6
+ authors:
7
+ - Nick Rockx
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-10-09 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: devise
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.9'
26
+ - !ruby/object:Gem::Dependency
27
+ name: omniauth
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: omniauth-oauth2
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.8'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.8'
54
+ - !ruby/object:Gem::Dependency
55
+ name: omniauth-rails_csrf_protection
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: dotenv-rails
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ description: This gem/plugin is used in applications that have sections for which
83
+ users need to login via the ReMark Login application
84
+ email:
85
+ - nick.rockx@scordigitalsolutions.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - README.md
91
+ - lib/devise/omniauth.rb
92
+ - lib/omniauth/strategies/doorkeeper.rb
93
+ - lib/remark_login_client.rb
94
+ - lib/remark_login_client/controllers/concerns/authentication.rb
95
+ - lib/remark_login_client/errors.rb
96
+ - lib/remark_login_client/models/concerns/user_methods.rb
97
+ - lib/remark_login_client/railtie.rb
98
+ - lib/remark_login_client/version.rb
99
+ homepage: https://bitbucket.org/ivaldi/remark-login_client/src/development/
100
+ licenses: []
101
+ metadata:
102
+ rubygems_mfa_required: 'true'
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '3'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubygems_version: 3.6.3
118
+ specification_version: 4
119
+ summary: Gem to connect a client application to ReMark Login for authentication
120
+ test_files: []