Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ gem "redis"

# Authentication
gem "doorkeeper", "~> 5.8"
gem "doorkeeper-openid_connect"
gem "doorkeeper-jwt"
gem "jwt"
gem "nkf" # required for omniauth-oauth2 in Ruby 3.4
Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ GEM
railties (>= 5)
doorkeeper-jwt (0.4.2)
jwt (>= 2.1)
doorkeeper-openid_connect (1.8.11)
doorkeeper (>= 5.5, < 5.9)
jwt (>= 2.5)
ostruct (>= 0.5)
drb (2.2.3)
email_validator (2.2.4)
activemodel
Expand Down Expand Up @@ -322,6 +326,7 @@ DEPENDENCIES
debug
doorkeeper (~> 5.8)
doorkeeper-jwt
doorkeeper-openid_connect
email_validator
jwt
lograge
Expand Down
14 changes: 13 additions & 1 deletion config/application.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
require_relative "boot"

require "rails/all"
require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
# require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
# require "action_mailbox/engine"
# require "action_text/engine"
require "action_view/railtie"
require "action_cable/engine"
# require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Expand Down
3 changes: 0 additions & 3 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@
# Change to :null_store to avoid any caching.
config.cache_store = :null_store

# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local

# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false

Expand Down
21 changes: 18 additions & 3 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ def initialize(*targets)
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com"

# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local

# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# config.assume_ssl = true

Expand Down Expand Up @@ -157,6 +154,24 @@ def initialize(*targets)

config.lograge.formatter = Lograge::Formatters::Logstash.new

# NEW: event-scoped options so we can access the exception object + backtrace
config.lograge.custom_options = lambda do |event|
ex = event.payload[:exception_object]
next {} unless ex

# Optional: cleaner to strip framework noise from the backtrace
cleaned = Rails.backtrace_cleaner.clean(Array(ex.backtrace))

{
error: {
class: ex.class.name,
message: ex.message,
# Keep payloads safe for UDP/ingest by truncating if huge
backtrace: cleaned.first(200) # or fewer if you ship via UDP
}
}
end

# Ensures only our lograge error is logged
# standard:disable Lint/ConstantDefinitionInBlock
module ActionDispatch
Expand Down
3 changes: 0 additions & 3 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false

# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test

# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
Expand Down
17 changes: 14 additions & 3 deletions config/initializers/doorkeeper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,20 @@
"public",

# these can be found via: GET /api/engine/v2/scopes
"alert_dashboards",
"alerts",
"api_keys",
"asset_categories",
"asset_purchase_orders",
"asset_types",
"asset_instances",
"assets",
"ldap_authentications",
"saml_authentications",
"o_auth_authentications",
"brokers",
"build_monitor",
"chat_gpt",
"cluster",
"domains",
"drivers",
Expand All @@ -95,20 +102,24 @@
"guest",
"control",
"edges",
"edge-control",
"metadata",
"mqtt",
"flux",
"o_auth_applications",
"repositories"
].map { |scope| [scope, "#{scope}.read", "#{scope}.write"] }.flatten
"repositories",
].map { |scope| [scope, "#{scope}.read", "#{scope}.write"] }.flatten + [
# this is required for OpenID connect
"openid"
]

default_scopes :public
optional_scopes(*all_scopes)

access_token_generator "::Doorkeeper::JWT"

force_ssl_in_redirect_uri false
grant_flows %w[authorization_code client_credentials implicit password]
grant_flows %w[authorization_code client_credentials implicit password implicit_oidc]
end

Doorkeeper::JWT.configure do
Expand Down
109 changes: 109 additions & 0 deletions config/initializers/doorkeeper_openid_connect.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# frozen_string_literal: true

require "uri"

Doorkeeper::OpenidConnect.configure do
issuer do |request|
"https://#{request.host}"
end

# Set the encryption secret. This would be shared with any other applications
# that should be able to read the payload of the token. Defaults to "secret".
key = ENV["JWT_SECRET"]
key = key.try { |k| Base64.decode64(k) } || DEV_KEY
signing_key key

subject_types_supported [:public]

resource_owner_from_access_token do |access_token|
User.find_by(id: access_token.resource_owner_id)
end

auth_time_from_resource_owner do |resource_owner|
resource_owner.last_login
end

reauthenticate_resource_owner do |resource_owner, return_to|
# Example implementation:
# store_location_for resource_owner, return_to
# sign_out resource_owner
# redirect_to new_user_session_url
domain = "https://#{request.host}"
authority = Authority.find_by_domain(request.host)
url = authority.login_url.gsub("{{url}}", URI.encode_uri_component(return_to))

redirect_to "#{domain}#{url}"
end

# Depending on your configuration, a DoubleRenderError could be raised
# if render/redirect_to is called at some point before this callback is executed.
# To avoid the DoubleRenderError, you could add these two lines at the beginning
# of this callback: (Reference: https://github.com/rails/rails/issues/25106)
# self.response_body = nil
# @_response_body = nil
select_account_for_resource_owner do |resource_owner, return_to|
self.response_body = nil
@_response_body = nil

# there is no account selection in PlaceOS
redirect_to return_to
end

subject do |resource_owner, application|
# Example implementation:
# resource_owner.id

# or if you need pairwise subject identifier, implement like below:
# Digest::SHA256.hexdigest("#{resource_owner.id}#{URI.parse(application.redirect_uri).host}#{'your_secret_salt'}")

resource_owner.id
end

end_session_endpoint do
authority = Authority.find_by_domain(request.host)
authority.logout_url
end

# Protocol to use when generating URIs for the discovery endpoint,
# for example if you also use HTTPS in development
# protocol do
# :https
# end

# Expiration time on or after which the ID Token MUST NOT be accepted for processing. (default 120 seconds).
# expiration 600

claims do
claim :sub do |resource_owner|
resource_owner.id
end

claim :email do |resource_owner|
resource_owner.email
end

claim :full_name do |resource_owner|
resource_owner.name
end

claim :given_name do |resource_owner|
resource_owner.first_name
end

claim :family_name do |resource_owner|
resource_owner.last_name
end

claim :nickname do |resource_owner|
resource_owner.nickname
end

claim :phone_number do |resource_owner|
resource_owner.phone
end

claim :preferred_username do |resource_owner|
resource_owner.login_name || resource_owner.email
end
end
end
23 changes: 23 additions & 0 deletions config/locales/doorkeeper_openid_connect.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
en:
doorkeeper:
scopes:
openid: 'Authenticate your account'
profile: 'View your profile information'
email: 'View your email address'
address: 'View your physical address'
phone: 'View your phone number'
errors:
messages:
login_required: 'The authorization server requires end-user authentication'
consent_required: 'The authorization server requires end-user consent'
interaction_required: 'The authorization server requires end-user interaction'
account_selection_required: 'The authorization server requires end-user account selection'
openid_connect:
errors:
messages:
# Configuration error messages
resource_owner_from_access_token_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.resource_owner_from_access_token missing configuration.'
auth_time_from_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.auth_time_from_resource_owner missing configuration.'
reauthenticate_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.reauthenticate_resource_owner missing configuration.'
select_account_for_resource_owner_not_configured: 'Failure due to Doorkeeper::OpenidConnect.configure.select_account_for_resource_owner missing configuration.'
subject_not_configured: 'ID Token generation failed due to Doorkeeper::OpenidConnect.configure.subject missing configuration.'
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

scope :auth do
use_doorkeeper
use_doorkeeper_openid_connect

get "/login", to: "auth/sessions#new" # for defining continue
get "/logout", to: "auth/sessions#destroy" # deletes the session
Expand All @@ -18,4 +18,8 @@

get "/authority", to: "auth/authorities#current"
end

get "/.well-known/openid-configuration(.:format)", to: "doorkeeper/openid_connect/discovery#provider"
get "/.well-known/oauth-authorization-server(.:format)", to: "doorkeeper/openid_connect/discovery#provider"
get "/.well-known/webfinger(.:format)", to: "doorkeeper/openid_connect/discovery#webfinger"
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateDoorkeeperOpenidConnectTables < ActiveRecord::Migration[8.1]
def change
create_table :oauth_openid_requests do |t|
t.references :access_grant, null: false, index: true
t.string :nonce, null: false
end

add_foreign_key(
:oauth_openid_requests,
:oauth_access_grants,
column: :access_grant_id,
on_delete: :cascade
)
end
end
Loading