Compare commits
16 Commits
d130f2f68b
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
462dd24da3
|
||
|
|
8eb5f093a4
|
||
| de45d070aa | |||
| c0b1112e49 | |||
|
|
2f90393eb6
|
||
|
|
8b87072485
|
||
|
|
82019f47be
|
||
|
|
259e72167b
|
||
|
|
7000908891
|
||
|
|
df0c13b400
|
||
|
|
387a2fa2e6
|
||
| 68eba80fd7 | |||
|
|
7e05530ab7
|
||
|
|
745a319b3d
|
||
|
|
f829bb3379
|
||
|
|
19bafe081f
|
@@ -19,6 +19,8 @@ LDAP_SUFFIX='dc=kosmos,dc=org'
|
||||
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
|
||||
|
||||
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
|
||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||
|
||||
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
|
||||
MASTODON_PUBLIC_URL='https://kosmos.social'
|
||||
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
DISCOURSE_PUBLIC_URL='http://discourse.example.com'
|
||||
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
|
||||
|
||||
EJABBERD_API_URL='http://xmpp.example.com/api'
|
||||
|
||||
BTCPAY_API_URL='http://btcpay.example.com/api/v1'
|
||||
|
||||
3
Gemfile
3
Gemfile
@@ -51,6 +51,9 @@ gem 'faraday'
|
||||
gem 'sidekiq', '< 7'
|
||||
gem 'sidekiq-scheduler'
|
||||
|
||||
# Service integrations
|
||||
gem 'discourse_api'
|
||||
|
||||
# Monitoring
|
||||
gem "sentry-ruby"
|
||||
gem "sentry-rails"
|
||||
|
||||
11
Gemfile.lock
11
Gemfile.lock
@@ -108,6 +108,11 @@ GEM
|
||||
devise (>= 3.4.1)
|
||||
net-ldap (>= 0.16.0)
|
||||
diff-lcs (1.5.0)
|
||||
discourse_api (2.0.0)
|
||||
faraday (~> 2.7)
|
||||
faraday-follow_redirects
|
||||
faraday-multipart
|
||||
rack (>= 1.6)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
@@ -126,6 +131,10 @@ GEM
|
||||
faraday (2.7.1)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (3.0.2)
|
||||
ffi (1.15.5)
|
||||
flipper (0.28.0)
|
||||
@@ -183,6 +192,7 @@ GEM
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.16.3)
|
||||
multipart-post (2.3.0)
|
||||
net-imap (0.3.1)
|
||||
net-protocol
|
||||
net-ldap (0.17.1)
|
||||
@@ -386,6 +396,7 @@ DEPENDENCIES
|
||||
database_cleaner
|
||||
devise (~> 4.9.0)
|
||||
devise_ldap_authenticatable
|
||||
discourse_api
|
||||
dotenv-rails
|
||||
factory_bot_rails
|
||||
faker
|
||||
|
||||
3
app/components/header_tab_link_component.html.erb
Normal file
3
app/components/header_tab_link_component.html.erb
Normal file
@@ -0,0 +1,3 @@
|
||||
<%= link_to @path, class: @link_class do %>
|
||||
<%= @name %>
|
||||
<% end %>
|
||||
20
app/components/header_tab_link_component.rb
Normal file
20
app/components/header_tab_link_component.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class HeaderTabLinkComponent < ViewComponent::Base
|
||||
def initialize(name:, path:, active: false, disabled: false)
|
||||
@name = name
|
||||
@path = path
|
||||
@active = active
|
||||
@disabled = disabled
|
||||
@link_class = class_names_link(path)
|
||||
end
|
||||
|
||||
def class_names_link(path)
|
||||
common = "block md:inline-block px-5 py-2 rounded-md font-medium text-base md:text-xl"
|
||||
if @active
|
||||
"#{common} bg-gray-900/50 text-white"
|
||||
else
|
||||
"#{common} text-gray-300 hover:bg-gray-900/30 hover:text-white active:bg-gray-900/30 active:text-white"
|
||||
end
|
||||
end
|
||||
end
|
||||
12
app/components/header_with_tabs_component.html.erb
Normal file
12
app/components/header_with_tabs_component.html.erb
Normal file
@@ -0,0 +1,12 @@
|
||||
<header class="py-10">
|
||||
<div class="max-w-6xl md:flex md:gap-x-10 mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<% if @title.present? %>
|
||||
<h1 class="text-3xl font-bold text-white">
|
||||
<%= @title %>
|
||||
</h1>
|
||||
<% end %>
|
||||
<nav class="md:grow flex gap-x-4 <%= @title.present? ? "justify-end" : "justify-start" %>" aria-label="Tabs">
|
||||
<%= render partial: @tabnav_partial %>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
8
app/components/header_with_tabs_component.rb
Normal file
8
app/components/header_with_tabs_component.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class HeaderWithTabsComponent < ViewComponent::Base
|
||||
def initialize(title: nil, tabnav_partial:)
|
||||
@title = title
|
||||
@tabnav_partial = tabnav_partial
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class AccountController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@current_section = :account
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Contributions::DonationsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_user!
|
||||
|
||||
# GET /donations
|
||||
# GET /donations.json
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Contributions::ProjectsController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_user!
|
||||
|
||||
# GET /contributions
|
||||
def index
|
||||
|
||||
17
app/controllers/discourse/sso_controller.rb
Normal file
17
app/controllers/discourse/sso_controller.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Discourse::SsoController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def connect
|
||||
secret = Setting.discourse_connect_secret
|
||||
sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret)
|
||||
sso.external_id = current_user.id
|
||||
sso.email = current_user.email
|
||||
sso.username = current_user.cn
|
||||
sso.name = current_user.display_name
|
||||
sso.admin = current_user.is_admin?
|
||||
sso.sso_secret = secret
|
||||
|
||||
redirect_to sso.to_url("#{Setting.discourse_public_url}/session/sso_login"),
|
||||
allow_other_host: true
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class InvitationsController < ApplicationController
|
||||
before_action :require_user_signed_in, except: ["show"]
|
||||
before_action :authenticate_user!, except: ["show"]
|
||||
before_action :require_user_signed_out, only: ["show"]
|
||||
|
||||
# GET /invitations
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require "rqrcode"
|
||||
|
||||
class Services::LightningController < ApplicationController
|
||||
before_action :require_user_signed_in
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_with_lndhub
|
||||
before_action :set_current_section
|
||||
before_action :fetch_balance
|
||||
@@ -37,8 +37,8 @@ class Services::LightningController < ApplicationController
|
||||
session[:ln_auth_token] = auth_token
|
||||
@ln_auth_token = auth_token
|
||||
end
|
||||
rescue
|
||||
# TODO add exception tracking
|
||||
rescue => e
|
||||
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@@ -49,9 +49,9 @@ class Services::LightningController < ApplicationController
|
||||
lndhub = Lndhub.new
|
||||
data = lndhub.balance @ln_auth_token
|
||||
@balance = data["BTC"]["AvailableBalance"] rescue nil
|
||||
rescue
|
||||
rescue AuthError
|
||||
authenticate_with_lndhub(force_reauth: true)
|
||||
return nil if @fetch_balance_retried
|
||||
raise if @fetch_balance_retried
|
||||
@fetch_balance_retried = true
|
||||
fetch_balance
|
||||
end
|
||||
@@ -61,9 +61,9 @@ class Services::LightningController < ApplicationController
|
||||
txs = lndhub.gettxs @ln_auth_token
|
||||
invoices = lndhub.getuserinvoices(@ln_auth_token).select{|i| i["ispaid"]}
|
||||
process_transactions(txs + invoices)
|
||||
rescue
|
||||
rescue AuthError
|
||||
authenticate_with_lndhub(force_reauth: true)
|
||||
return [] if @fetch_transactions_retried
|
||||
raise if @fetch_transactions_retried
|
||||
@fetch_transactions_retried = true
|
||||
fetch_transactions
|
||||
end
|
||||
@@ -86,6 +86,10 @@ class Services::LightningController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# Handle an edge case where lndhub.go includes a failed payment in the
|
||||
# list, which wasn't actually booked
|
||||
txs.reject!{ |tx| tx["type"] == "paid_invoice" && tx["payment_preimage"].blank? }
|
||||
|
||||
txs.sort{ |a,b| b["datetime"] <=> a["datetime"] }
|
||||
end
|
||||
end
|
||||
|
||||
1
app/errors/auth_error.rb
Normal file
1
app/errors/auth_error.rb
Normal file
@@ -0,0 +1 @@
|
||||
class AuthError < StandardError; end
|
||||
@@ -2,6 +2,9 @@
|
||||
class Setting < RailsSettings::Base
|
||||
cache_prefix { "v1" }
|
||||
|
||||
field :accounts_domain, type: :string,
|
||||
default: ENV["AKKOUNTS_DOMAIN"].presence
|
||||
|
||||
#
|
||||
# Internal services
|
||||
#
|
||||
@@ -41,6 +44,9 @@ class Setting < RailsSettings::Base
|
||||
field :discourse_enabled, type: :boolean,
|
||||
default: (ENV["DISCOURSE_PUBLIC_URL"].present?.to_s || false)
|
||||
|
||||
field :discourse_connect_secret, type: :string, readonly: true,
|
||||
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
||||
|
||||
#
|
||||
# ejabberd
|
||||
#
|
||||
|
||||
@@ -12,12 +12,7 @@ class Lndhub
|
||||
end
|
||||
|
||||
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
|
||||
|
||||
if res.status != 200
|
||||
Rails.logger.error "[lndhub] API request failed:"
|
||||
Rails.logger.error res.body
|
||||
#TODO add some kind of exception tracking/notifications
|
||||
end
|
||||
log_error(res) if res.status != 200
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
@@ -31,7 +26,7 @@ class Lndhub
|
||||
data = JSON.parse(res.body)
|
||||
|
||||
if data.is_a?(Hash) && data["error"] && data["message"] == "bad auth"
|
||||
raise "BAD_AUTH"
|
||||
raise AuthError
|
||||
else
|
||||
data
|
||||
end
|
||||
@@ -68,4 +63,13 @@ class Lndhub
|
||||
|
||||
invoice["payment_request"]
|
||||
end
|
||||
|
||||
def log_error(res)
|
||||
Rails.logger.error "[lndhub] API request failed:"
|
||||
Rails.logger.error res.body
|
||||
|
||||
if Setting.sentry_enabled?
|
||||
Sentry.capture_message("Lndhub API request failed: #{res.body}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
class LndhubV2
|
||||
attr_accessor :auth_token
|
||||
|
||||
def initialize
|
||||
@base_url = ENV["LNDHUB_API_URL"]
|
||||
end
|
||||
class LndhubV2 < Lndhub
|
||||
|
||||
def post(endpoint, payload, options={})
|
||||
headers = { "Content-Type" => "application/json" }
|
||||
@@ -12,64 +7,12 @@ class LndhubV2
|
||||
elsif options[:admin_token]
|
||||
headers.merge!({ "Authorization" => "Bearer #{options[:admin_token]}" })
|
||||
end
|
||||
|
||||
res = Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, headers
|
||||
|
||||
if res.status != 200
|
||||
Rails.logger.error "[lndhub] API request failed:"
|
||||
Rails.logger.error res.body
|
||||
#TODO add some kind of exception tracking/notifications
|
||||
end
|
||||
log_error(res) if res.status != 200
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def get(endpoint, auth_token)
|
||||
res = Faraday.get("#{@base_url}/#{endpoint}", {}, {
|
||||
"Content-Type" => "application/json",
|
||||
"Accept" => "application/json",
|
||||
"Authorization" => "Bearer #{auth_token}"
|
||||
})
|
||||
|
||||
JSON.parse(res.body)
|
||||
end
|
||||
|
||||
def create(payload)
|
||||
post "create", payload
|
||||
end
|
||||
|
||||
def authenticate(user)
|
||||
credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
|
||||
self.auth_token = credentials["access_token"]
|
||||
self.auth_token
|
||||
end
|
||||
|
||||
def balance(user_token=nil)
|
||||
get "balance", user_token || auth_token
|
||||
end
|
||||
|
||||
def gettxs(user_token)
|
||||
get "gettxs", user_token || auth_token
|
||||
end
|
||||
|
||||
def getuserinvoices(user_token)
|
||||
get "getuserinvoices", user_token || auth_token
|
||||
end
|
||||
|
||||
def addinvoice(payload)
|
||||
invoice = post "addinvoice", {
|
||||
amt: payload[:amount],
|
||||
memo: payload[:memo],
|
||||
description_hash: payload[:description_hash]
|
||||
}
|
||||
|
||||
invoice["payment_request"]
|
||||
end
|
||||
|
||||
#
|
||||
# V2
|
||||
#
|
||||
|
||||
def create_account(payload={})
|
||||
post "v2/users", payload, admin_token: Rails.application.credentials.lndhub[:admin_token]
|
||||
end
|
||||
@@ -78,4 +21,5 @@ class LndhubV2
|
||||
# Payload: { amount: 1000, description: "", description_hash: "" }
|
||||
post "v2/invoices", payload
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -7,11 +7,46 @@
|
||||
title: "Enable Discourse integration",
|
||||
description: "Discourse configuration present and features enabled"
|
||||
) %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :discourse_public_url,
|
||||
value: Setting.discourse_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Public URL") do %>
|
||||
<%= f.text_field :discourse_public_url,
|
||||
value: Setting.discourse_public_url,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetComponent.new(title: "Connect secret") do %>
|
||||
<%= f.password_field :discourse_connect_secret,
|
||||
value: Setting.discourse_connect_secret,
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% if Setting.discourse_enabled? %>
|
||||
<% content_for :documentation do %>
|
||||
<h3 class="mt-8">How to configure Discourse</h3>
|
||||
<ol class="list-decimal list-inside">
|
||||
<li class="mb-6">
|
||||
Set the <strong>Discourse Connect URL</strong> to the following URL:
|
||||
</li>
|
||||
<li data-controller="clipboard" class="mb-6 flex gap-1">
|
||||
<input type="text" class="grow" disabled="disabled"
|
||||
value="https://<%= Setting.accounts_domain %>/discourse/connect"
|
||||
data-clipboard-target="source" />
|
||||
<button class="btn-md btn-icon btn-blue shrink-0"
|
||||
data-clipboard-target="trigger" data-action="clipboard#copy"
|
||||
title="Copy to clipboard">
|
||||
<span class="content-initial">
|
||||
<%= render partial: "icons/copy", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
<span class="content-active hidden">
|
||||
<%= render partial: "icons/check", locals: { custom_class: "text-white h-4 w-4 inline" } %>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="mb-6">
|
||||
Set the <strong>Discourse Connect Secret</strong> to the value above.
|
||||
</li>
|
||||
<li>
|
||||
Enable Discourse Connect.
|
||||
</li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
class: "w-full", disabled: true %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<h3 class="mt-8">User default settings</h3>
|
||||
<h3 class="mt-10">User default settings</h3>
|
||||
<ul role="list">
|
||||
<%= render FormElements::FieldsetComponent.new(
|
||||
title: "Default rooms",
|
||||
|
||||
@@ -20,4 +20,10 @@
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if content_for?(:documentation) %>
|
||||
<section>
|
||||
<%= yield :documentation %>
|
||||
</section>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<%= render HeaderComponent.new(title: "Contributions") %>
|
||||
<%# <%= render HeaderComponent.new(title: "Contributions") %>
|
||||
<%= render HeaderWithTabsComponent.new(
|
||||
# title: "Contributions",
|
||||
tabnav_partial: "shared/tabnav_contributions"
|
||||
) %>
|
||||
|
||||
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<% if @donations.any? %>
|
||||
<p class="mb-12">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<%= render HeaderComponent.new(title: "Contributions") %>
|
||||
<%= render HeaderWithTabsComponent.new(
|
||||
# title: "Contributions",
|
||||
tabnav_partial: "shared/tabnav_contributions"
|
||||
) %>
|
||||
|
||||
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
|
||||
<%= render MainSimpleComponent.new do %>
|
||||
<section>
|
||||
<p class="mb-8">
|
||||
Project contributions are how we develop and run all Kosmos software and
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="border border-gray-300 rounded-md hover:border-gray-400
|
||||
bg-[length:95%] bg-center bg-no-repeat
|
||||
bg-[url(/img/logos/icon_discourse.svg)]">
|
||||
<%= link_to "https://community.kosmos.org",
|
||||
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
|
||||
class: "block h-full px-6 py-6 rounded-md" do %>
|
||||
<h3 class="mb-3.5">Discourse</h3>
|
||||
<p class="text-gray-600">
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<%
|
||||
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed
|
||||
enable_turbo = !session[:user_return_to] || !session[:user_return_to].match?('/discourse/connect')
|
||||
%>
|
||||
|
||||
<%= render HeaderCompactComponent.new(title: "Log in") %>
|
||||
|
||||
<%= render MainCompactComponent.new do %>
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name),
|
||||
data: { turbo: enable_turbo.to_s }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
<div class="mb-6">
|
||||
<%= f.label :cn, 'User', class: 'block mb-2 font-bold' %>
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex" aria-label="Tabs">
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Donations", path: contributions_donations_path,
|
||||
active: current_page?(contributions_donations_path)
|
||||
) %>
|
||||
<%= render TabnavLinkComponent.new(
|
||||
name: "Projects", path: contributions_projects_path,
|
||||
active: current_page?(contributions_projects_path)
|
||||
) %>
|
||||
</nav>
|
||||
</div>
|
||||
<%= render HeaderTabLinkComponent.new(
|
||||
name: "Donations", path: contributions_donations_path,
|
||||
active: current_page?(contributions_donations_path)
|
||||
) %>
|
||||
<%= render HeaderTabLinkComponent.new(
|
||||
name: "Projects", path: contributions_projects_path,
|
||||
active: current_page?(contributions_projects_path)
|
||||
) %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'sidekiq/web'
|
||||
|
||||
Rails.application.routes.draw do
|
||||
devise_for :users, controllers: { confirmations: "users/confirmations" }
|
||||
devise_for :users, controllers: { confirmations: 'users/confirmations' }
|
||||
|
||||
get 'welcome', to: 'welcome#index'
|
||||
get 'check_your_email', to: 'welcome#check_your_email'
|
||||
@@ -61,16 +61,20 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
get ".well-known/webfinger" => "webfinger#show"
|
||||
get ".well-known/webfinger", to: 'webfinger#show'
|
||||
|
||||
namespace :discourse do
|
||||
get "connect", to: 'sso#connect'
|
||||
end
|
||||
|
||||
authenticate :user, ->(user) { user.is_admin? } do
|
||||
mount Sidekiq::Web => '/sidekiq'
|
||||
mount Flipper::UI.app(Flipper) => '/flipper'
|
||||
mount Sidekiq::Web, at: '/sidekiq'
|
||||
mount Flipper::UI.app(Flipper), at: '/flipper'
|
||||
end
|
||||
|
||||
# Letter Opener (open "sent" emails in dev and staging)
|
||||
if Rails.env.match(/staging|development/)
|
||||
mount LetterOpenerWeb::Engine, at: "letter_opener"
|
||||
mount LetterOpenerWeb::Engine, at: '/letter_opener'
|
||||
end
|
||||
|
||||
root to: 'dashboard#index'
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"postcss-preset-env": "^7.8.3",
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"scripts": {
|
||||
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
|
||||
"build:css": "yarn run build:css:tailwind"
|
||||
|
||||
41
spec/requests/discourse/sso_spec.rb
Normal file
41
spec/requests/discourse/sso_spec.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
require 'rails_helper'
|
||||
require 'webmock/rspec'
|
||||
|
||||
RSpec.describe "Discourse SSO", type: :request do
|
||||
|
||||
describe "GET /discourse/connect" do
|
||||
let(:user) { create :user, cn: 'jimmy', ou: 'kosmos.org' }
|
||||
|
||||
before do
|
||||
Warden.test_mode!
|
||||
login_as user, scope: :user
|
||||
allow(user).to receive(:display_name).and_return('Jimbo')
|
||||
allow(user).to receive(:is_admin?).and_return(false)
|
||||
end
|
||||
|
||||
after do
|
||||
Warden.test_reset!
|
||||
end
|
||||
|
||||
context "with invalid SSO credentials" do
|
||||
it "results in a failed signature check" do
|
||||
expect {
|
||||
get discourse_connect_path(
|
||||
sso: "bm9uY2U9ODk2N2NiMmFlZTdlMjdjNzZiZTNkZWQ5ODIwYzMzN2QmcmV0dXJuX3Nzb191cmw9aHR0cCUzQSUyRiUyRmxvY2FsaG9zdCUzQTMwMDAlMkZzZXNzaW9uJTJGc3NvX2xvZ2lu",
|
||||
sig: "01fc008ff7b51855217e879b6f14aaddefbbd4df2d128951f7bb70cfde834c2a"
|
||||
)
|
||||
}.to raise_error(DiscourseApi::SingleSignOn::ParseError)
|
||||
end
|
||||
end
|
||||
|
||||
context "valid SSO credentials" do
|
||||
it "redirects to the Discourse SSO endpoint" do
|
||||
get discourse_connect_path(
|
||||
sso: "bm9uY2U9YjQwYWZmYzg0YWQ2NWE1ZTk5MjdlZWU1NWEzMjdhMTQmcmV0dXJuX3Nzb191cmw9aHR0cCUzQSUyRiUyRmxvY2FsaG9zdCUzQTMwMDAlMkZzZXNzaW9uJTJGc3NvX2xvZ2lu",
|
||||
sig: "b7905c5db612391293249ad5272dac493681efcd255133f6c2aff91ba654a319"
|
||||
)
|
||||
expect(response).to redirect_to('http://discourse.example.com/session/sso_login?sso=YWRtaW49ZmFsc2UmZW1haWw9amltbXklNDBleGFtcGxlLmNvbSZleHRlcm5hbF9pZD0xJm5hbWU9SmltYm8mbm9uY2U9YjQwYWZmYzg0YWQ2NWE1ZTk5MjdlZWU1NWEzMjdhMTQmcmV0dXJuX3Nzb191cmw9aHR0cCUzQSUyRiUyRmxvY2FsaG9zdCUzQTMwMDAlMkZzZXNzaW9uJTJGc3NvX2xvZ2luJnVzZXJuYW1lPWppbW15&sig=d5f8b1d6db66569bef789fda4a3216119c2d42b84725d043c9a57dde1e528842')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user