Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6 signed protected #5

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 4 additions & 4 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ Security/YAMLLoad:
Exclude:
- spec/**/*

# Style/HashSyntax:
# Enabled: true
# Exclude:
# - Rakefile
Style/HashSyntax:
Enabled: true
Exclude:
- Rakefile

Style/SymbolArray:
Enabled: true
Expand Down
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ source 'https://rubygems.org'
ruby File.read('.ruby-version').strip

# Web
gem 'puma', '~> 5'
gem 'puma', '~> 5.3.1'
gem 'roda'
gem 'slim'

Expand All @@ -21,6 +21,7 @@ gem 'redis-rack'
gem 'dry-validation'
gem 'rack-ssl-enforcer'
gem 'rbnacl' # assumes libsodium package already installed
gem 'secure_headers'

# Debugging
gem 'pry'
Expand Down
6 changes: 4 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ GEM
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (4.0.7)
puma (5.6.4)
puma (5.3.2)
nio4r (~> 2.0)
rack (2.2.3)
rack-ssl-enforcer (0.2.9)
Expand Down Expand Up @@ -115,6 +115,7 @@ GEM
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
ruby-progressbar (1.11.0)
secure_headers (6.3.3)
slim (4.1.0)
temple (>= 0.7.6, < 0.9)
tilt (>= 2.0.6, < 2.1)
Expand All @@ -141,7 +142,7 @@ DEPENDENCIES
minitest
minitest-rg
pry
puma (~> 5)
puma (~> 5.3.1)
rack-ssl-enforcer
rack-test
rake
Expand All @@ -152,6 +153,7 @@ DEPENDENCIES
roda
rubocop
rubocop-performance
secure_headers
slim
webmock

Expand Down
15 changes: 11 additions & 4 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# frozen_string_literal: true

# rubocop:disable Style/HashSyntax
require 'rake/testtask'
require './require_app'

task :print_env do
puts "Environment: #{ENV.fetch('RACK_ENV', 'development')}"
puts "Environment: #{ENV['RACK_ENV'] || 'development'}"
end

desc 'Run application console (pry)'
Expand Down Expand Up @@ -41,7 +40,6 @@ end

namespace :run do
# Run in development mode
desc 'Run Web App in development mode'
task :dev do
sh 'rackup -p 9292'
end
Expand All @@ -63,6 +61,16 @@ namespace :generate do
end
end

namespace :url do
# usage: $ rake url:integrity URL=http://example.org/script.js
desc 'Generate integrity hash for a URL'
task :integrity do
sha384 = `curl -L -s #{ENV['URL']} | openssl dgst -sha384 -binary | \
openssl enc -base64`
puts "sha384-#{sha384}"
end
end

namespace :session do
desc 'Wipe all sessions stored in Redis'
task :wipe => :load_lib do
Expand All @@ -72,4 +80,3 @@ namespace :session do
puts "#{wiped.count} sessions deleted"
end
end
# rubocop:enable Style/HashSyntax
5 changes: 2 additions & 3 deletions app/controllers/auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def gh_oauth_url(config)
end

route('auth') do |routing|
@oauth_callback = '/auth/sso_callback'
@login_route = '/auth/login'
routing.is 'login' do
# GET /auth/login
Expand Down Expand Up @@ -46,9 +45,9 @@ def gh_oauth_url(config)
flash[:notice] = "Welcome back #{current_account.username}!"
routing.redirect '/projects'
rescue AuthenticateAccount::NotAuthenticatedError
flash[:error] = 'Username and password did not match our records'
flash.now[:error] = 'Username and password did not match our records'
response.status = 401
routing.redirect @login_route
view :login
rescue AuthenticateAccount::ApiServerError => e
App.logger.warn "API server error: #{e.inspect}\n#{e.backtrace}"
flash[:error] = 'Our servers are not responding -- please try later'
Expand Down
1 change: 1 addition & 0 deletions app/controllers/documents.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'roda'
require_relative './app'

module Credence
# Web controller for Credence API
Expand Down
1 change: 1 addition & 0 deletions app/controllers/projects.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'roda'
require_relative './app'

module Credence
# Web controller for Credence API
Expand Down
69 changes: 69 additions & 0 deletions app/controllers/security.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require_relative './app'
require 'roda'
require 'rack/ssl-enforcer'
require 'secure_headers'

module Credence
# Configuration for the API
class App < Roda
plugin :environments
plugin :multi_route

FONT_SRC = %w[https://cdn.jsdelivr.net].freeze
SCRIPT_SRC = %w[https://cdn.jsdelivr.net].freeze
STYLE_SRC = %w[https://bootswatch.com https://cdn.jsdelivr.net].freeze

configure :production do
use Rack::SslEnforcer, hsts: true
end

## Uncomment to drop the login session in case of any violation
# use Rack::Protection, reaction: :drop_session
use SecureHeaders::Middleware

SecureHeaders::Configuration.default do |config|
config.cookies = {
secure: true,
httponly: true,
samesite: {
strict: true
}
}

config.x_frame_options = 'DENY'
config.x_content_type_options = 'nosniff'
config.x_xss_protection = '1'
config.x_permitted_cross_domain_policies = 'none'
config.referrer_policy = 'origin-when-cross-origin'

# note: single-quotes needed around 'self' and 'none' in CSPs
# rubocop:disable Lint/PercentStringArray
config.csp = {
report_only: false,
preserve_schemes: true,
default_src: %w['self'],
child_src: %w['self'],
connect_src: %w[wws:],
img_src: %w['self'],
font_src: %w['self'] + FONT_SRC,
script_src: %w['self'] + SCRIPT_SRC,
style_src: %W['self'] + STYLE_SRC,
form_action: %w['self'],
frame_ancestors: %w['none'],
object_src: %w['none'],
block_all_mixed_content: true,
report_uri: %w[/security/report_csp_violation]
}
# rubocop:enable Lint/PercentStringArray
end

route('security') do |routing|
# POST security/report_csp_violation
routing.post 'report_csp_violation' do
App.logger.warn "CSP VIOLATION: #{request.body.read}"
end
end
end
end
20 changes: 20 additions & 0 deletions app/lib/signed_message.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require 'base64'
require 'rbnacl'

# Encrypt and Decrypt from Database
class SignedMessage
# Call setup once to pass in config variable with SIGNING_KEY attribute
def self.setup(config)
@signing_key = Base64.strict_decode64(config.SIGNING_KEY)
end

def self.sign(message)
signature = RbNaCl::SigningKey.new(@signing_key)
.sign(message.to_json)
.then { |sig| Base64.strict_encode64(sig) }

{ data: message, signature: signature }
end
end
5 changes: 5 additions & 0 deletions app/presentation/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@

.top-buffer {
margin-top:20px;
}

.logo {
max-width:35px;
margin-top: -7px;
}
2 changes: 1 addition & 1 deletion app/presentation/views/document.slim
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ dl
dd class="blockquote"
a data-bs-toggle="collapse" href="#document" aria-expanded="false" aria-controls="document" show document
div id="document" class="collapse"
dov class="card card-body"
div class="card card-body"
pre class="force-wrap" #{document.content}
2 changes: 1 addition & 1 deletion app/presentation/views/document_new_partial.slim
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ div id="newDocumentModal" class="modal fade" role="dialog"
input type='text' name='filename' id='filename_input' class="form-control" placeholder="Filename is required"
div class="form-group"
label for='relative_path_input' Relative File Path:
span class="small" style="margin-left:10px;" (relative to project root)
span class="small ms-2" (relative to project root)
input type='text' name='relative_path' id='relative_path_input' class="form-control" placeholder="(optional)" value="/"
div class="form-group"
label for='description_input' Description:
Expand Down
9 changes: 5 additions & 4 deletions app/presentation/views/layout.slim
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ html

/ Themed Bootstrap CSS (Cerulean Theme) - see bootswatch.com for more themes
/ - default Bootstrap CSS: https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css
link rel="stylesheet" href="https://bootswatch.com/5/cerulean/bootstrap.min.css"
link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/FortAwesome/[email protected]/css/all.min.css"
link rel="shortcut icon" href="#"
link rel="stylesheet" href="https://bootswatch.com/5/cerulean/bootstrap.min.css" integrity="sha384-6PpWmaLt7GOhVYBzVUBXgwhaV2S6iHthYodyuX2lCQlqAmt6RHXOh+wbLF6hunfO" crossorigin="anonymous"
link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/FortAwesome/[email protected]/css/all.min.css" integrity="sha384-SZXxX4whJ79/gErwcOYf+zWLeJdY/qpuqC4cAa9rOGUstPomtqpuNWT9wdPEn2fk" crossorigin="anonymous"

/ Custom CSS
== assets(:css)
body
/ Popper & Bootstrap Javascript (Load in this order)
script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"
script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"
script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-lpyLfhYuitXl2zRZ5Bn2fqnhNAKOAaM/0Kr9laMspuaMiZfGmfwRNFh8HlMy49eQ" crossorigin="anonymous"

== render :nav
div class='container' id='html_body'
Expand Down
7 changes: 2 additions & 5 deletions app/presentation/views/login.slim
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/ Fontawesome and Bootstrap-Social for Github buttons
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/fontawesome.min.css"
link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-social/5.0.0/bootstrap-social.css"

div class="row"
div class="col-sm-1"
div class="col-sm-4"
Expand All @@ -21,7 +18,7 @@ div class="row"
.row
h4.text-center Login with Github
.row
a class="btn-block btn-social btn-lg btn-github" href=gh_oauth_url
a class="btn btn-dark btn-lg" href=gh_oauth_url
span class="fab fa-github"
== "Github Sign-in"
== " Github Sign-in"
.col-sm-1
2 changes: 1 addition & 1 deletion app/presentation/views/nav.slim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
nav role="navigation" class="navbar navbar-expand-sm navbar-dark bg-primary"
div class="container-fluid"
a class="navbar-brand" rel="home" href="/" title="Securely Share Configurations"
img alt="Credence" src="/logo.png" style="max-width:35px; margin-top: -7px;"
img alt="Credence" src="/logo.png" class="logo"
a class="navbar-brand" href="/" Credence
button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavbar" aria-controls="mainNavbar" aria-expanded="false" aria-label="Toggle navigation"
span class="navbar-toggler-icon"
Expand Down
2 changes: 1 addition & 1 deletion app/presentation/views/project.slim
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ div class="row"
div class="col-xs-4"
- if project.policies.can_add_documents
button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newDocumentModal" + New Document
div class="row" style="height:10px;"
div class="row mt-2"
div class="col-lg-3"
div class="panel panel-default"
div class="panel-heading" Collaborators
Expand Down
6 changes: 3 additions & 3 deletions app/presentation/views/projects_all.slim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
div class="row" style="vertical-align: bottom;"
div class="col-xs-9"
div class="row mt-2"
div class="col-md-9"
h3 Projects
div class="col-xs-3"
div class="col-md-3"
button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newProjectModal" + New Project
table class="table"
thead
Expand Down
10 changes: 5 additions & 5 deletions app/services/authenticate_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ class NotAuthenticatedError < StandardError; end
class ApiServerError < StandardError; end

def call(username:, password:)
credentials = { username: username, password: password }

response = HTTP.post("#{ENV['API_URL']}/auth/authenticate",
json: { username:, password: })
json: SignedMessage.sign(credentials))

raise(NotAuthenticatedError) if response.code == 401
raise(ApiServerError) if response.code != 200

account_info = JSON.parse(response.to_s)['data']['attributes']

{
account: account_info['account'],
auth_token: account_info['auth_token']
}
{ account: account_info['account'],
auth_token: account_info['auth_token'] }
end
end
end
16 changes: 9 additions & 7 deletions app/services/authorize_github_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,19 @@ def get_access_token_from_github(code)
end

def get_sso_account_from_api(access_token)
response =
HTTP.post("#{@config.API_URL}/auth/sso",
json: { access_token: access_token })
signed_sso_info = { access_token: access_token }
.then { |sso_info| SignedMessage.sign(sso_info) }

response = HTTP.post(
"#{@config.API_URL}/auth/sso",
json: signed_sso_info
)
raise if response.code >= 400

account_info = JSON.parse(response)['data']['attributes']

{
account: account_info['account'],
auth_token: account_info['auth_token']
}
{ account: account_info['account'],
auth_token: account_info['auth_token'] }
end
end
end
6 changes: 2 additions & 4 deletions app/services/create_account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ def initialize(config)
end

def call(email:, username:, password:)
message = { email:,
username:,
password: }
account = { email: , username: , password: }

response = HTTP.post(
"#{@config.API_URL}/accounts/",
json: message
json: SignedMessage.sign(account)
)

raise InvalidAccount unless response.code == 201
Expand Down
Loading