Skip to content

Commit

Permalink
feat: shard file generator specs
Browse files Browse the repository at this point in the history
fix: add expectation

feat: better error messages

feat: refactor spec_helper2

fix: formatting

fix: require ecr

feat: require crystal 1.6.2

feat: api_authentication generator specs

fix: move fixtures out of spec folder

fix: wrong version

fix: setup_tempfile macro

chore!: replace ameba with gh action
  • Loading branch information
mdwagner committed Oct 17, 2023
1 parent 42738c6 commit 2a27a9b
Show file tree
Hide file tree
Showing 377 changed files with 7,466 additions and 25 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ jobs:
continue-on-error: false
steps:
- uses: actions/checkout@v4
- name: Crystal Ameba Linter
id: crystal-ameba
uses: crystal-ameba/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install Crystal
uses: crystal-lang/install-crystal@v1
with:
Expand All @@ -22,8 +27,8 @@ jobs:
run: shards install
- name: Format
run: crystal tool format --check
- name: Lint
run: ./bin/ameba
#- name: Lint
#run: ./bin/ameba

specs:
strategy:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ lucky
/test-project/
node_modules
/tmp/
!/fixtures/*/expected/**/*

# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require "../../../spec_helper"

describe Api::Me::Show do
it "returns the signed in user" do
user = UserFactory.create

response = ApiClient.auth(user).exec(Api::Me::Show)

response.should send_json(200, email: user.email)
end

it "fails if not authenticated" do
response = ApiClient.exec(Api::Me::Show)

response.status_code.should eq(401)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "../../../spec_helper"

describe Api::SignIns::Create do
it "returns a token" do
UserToken.stub_token("fake-token") do
user = UserFactory.create

response = ApiClient.exec(Api::SignIns::Create, user: valid_params(user))

response.should send_json(200, token: "fake-token")
end
end

it "returns an error if credentials are invalid" do
user = UserFactory.create
invalid_params = valid_params(user).merge(password: "incorrect")

response = ApiClient.exec(Api::SignIns::Create, user: invalid_params)

response.should send_json(
400,
param: "password",
details: "password is wrong"
)
end
end

private def valid_params(user : User)
{
email: user.email,
password: "password",
}
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require "../../../spec_helper"

describe Api::SignUps::Create do
it "creates user on sign up" do
UserToken.stub_token("fake-token") do
response = ApiClient.exec(Api::SignUps::Create, user: valid_params)

response.should send_json(200, token: "fake-token")
new_user = UserQuery.first
new_user.email.should eq(valid_params[:email])
end
end

it "returns error for invalid params" do
invalid_params = valid_params.merge(password_confirmation: "wrong")

response = ApiClient.exec(Api::SignUps::Create, user: invalid_params)

UserQuery.new.select_count.should eq(0)
response.should send_json(
400,
param: "password_confirmation",
details: "password_confirmation must match"
)
end
end

private def valid_params
{
email: "[email protected]",
password: "password",
password_confirmation: "password",
}
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Api::Me::Show < ApiAction
get "/api/me" do
json UserSerializer.new(current_user)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Api::SignIns::Create < ApiAction
include Api::Auth::SkipRequireAuthToken

post "/api/sign_ins" do
SignInUser.run(params) do |operation, user|
if user
json({token: UserToken.generate(user)})
else
raise Avram::InvalidOperationError.new(operation)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Api::SignUps::Create < ApiAction
include Api::Auth::SkipRequireAuthToken

post "/api/sign_ups" do
user = SignUpUser.create!(params)
json({token: UserToken.generate(user)})
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Api::Auth::Helpers
# The 'memoize' macro makes sure only one query is issued to find the user
memoize def current_user? : User?
auth_token.try do |value|
user_from_auth_token(value)
end
end

private def auth_token : String?
bearer_token || token_param
end

private def bearer_token : String?
context.request.headers["Authorization"]?
.try(&.gsub("Bearer", ""))
.try(&.strip)
end

private def token_param : String?
params.get?(:auth_token)
end

private def user_from_auth_token(token : String) : User?
UserToken.decode_user_id(token).try do |user_id|
UserQuery.new.id(user_id).first?
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Api::Auth::RequireAuthToken
macro included
before require_auth_token
end

private def require_auth_token
if current_user?
continue
else
json auth_error_json, 401
end
end

private def auth_error_json
ErrorSerializer.new(
message: "Not authenticated.",
details: auth_error_details
)
end

private def auth_error_details : String
if auth_token
"The provided authentication token was incorrect."
else
"An authentication token is required. Please include a token in an 'auth_token' param or 'Authorization' header."
end
end

# Tells the compiler that the current_user is not nil since we have checked
# that the user is signed in
private def current_user : User
current_user?.as(User)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Api::Auth::SkipRequireAuthToken
macro included
skip require_auth_token
end

# Since sign in is not required, current_user might be nil
def current_user : User?
current_user?
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generates and decodes JSON Web Tokens for Authenticating users.
class UserToken
Habitat.create { setting stubbed_token : String? }
ALGORITHM = JWT::Algorithm::HS256

def self.generate(user : User) : String
payload = {"user_id" => user.id}

settings.stubbed_token || create_token(payload)
end

def self.create_token(payload)
JWT.encode(payload, Lucky::Server.settings.secret_key_base, ALGORITHM)
end

def self.decode_user_id(token : String) : Int64?
payload, _header = JWT.decode(token, Lucky::Server.settings.secret_key_base, ALGORITHM)
payload["user_id"].to_s.to_i64
rescue e : JWT::Error
Lucky::Log.dexter.error { {jwt_decode_error: e.message} }
nil
end

# Used in tests to return a fake token to test against.
def self.stub_token(token : String, &)
temp_config(stubbed_token: token) do
yield
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class UserSerializer < BaseSerializer
def initialize(@user : User)
end

def render
{email: @user.email}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% skip_file unless flag?(:with_sec_tests) %}
# Run these specs with `crystal spec -Dwith_sec_tests`

require "../spec_helper"

describe "SecTester" do
end

private def scan_with_cleanup(&) : Nil
scanner = LuckySecTester.new
yield scanner
ensure
scanner.try &.cleanup
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "lucky_sec_tester"

# Signup for a `BRIGHT_TOKEN` at
# [NeuraLegion](https://app.neuralegion.com/signup)
# Read more about the SecTester on https://github.com/luckyframework/lucky_sec_tester
LuckySecTester.configure do |setting|
setting.bright_token = ENV["BRIGHT_TOKEN"]
setting.project_id = ENV["BRIGHT_PROJECT_ID"]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% skip_file unless flag?(:with_sec_tests) %}
# Run these specs with `crystal spec -Dwith_sec_tests`

require "../spec_helper"

describe "SecTester" do
it "tests the home page general infra issues" do
scan_with_cleanup do |scanner|
target = scanner.build_target(Home::Index)
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
severity_threshold: SecTester::Severity::Medium,
tests: [
"header_security", # Testing for header security issues (https://docs.brightsec.com/docs/misconfigured-security-headers)
"cookie_security", # Testing for Cookie Security issues (https://docs.brightsec.com/docs/sensitive-cookie-in-https-session-without-secure-attribute)
"proto_pollution", # Testing for proto pollution based vulnerabilities (https://docs.brightsec.com/docs/prototype-pollution)
"open_buckets", # Testing for open buckets (https://docs.brightsec.com/docs/open-bucket)
],
target: target
)
end
end

it "tests app.js for 3rd party issues" do
scan_with_cleanup do |scanner|
target = SecTester::Target.new(Lucky::RouteHelper.settings.base_uri + Lucky::AssetHelpers.asset("js/app.js"))
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: [
"retire_js", # Testing for 3rd party issues (https://docs.brightsec.com/docs/javascript-component-with-known-vulnerabilities)
"cve_test", # Testing for known CVEs (https://docs.brightsec.com/docs/cves)
],
target: target
)
end
end
end

private def scan_with_cleanup(&) : Nil
scanner = LuckySecTester.new
yield scanner
ensure
scanner.try &.cleanup
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "lucky_sec_tester"

# Signup for a `BRIGHT_TOKEN` at
# [NeuraLegion](https://app.neuralegion.com/signup)
# Read more about the SecTester on https://github.com/luckyframework/lucky_sec_tester
LuckySecTester.configure do |setting|
setting.bright_token = ENV["BRIGHT_TOKEN"]
setting.project_id = ENV["BRIGHT_PROJECT_ID"]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% skip_file unless flag?(:with_sec_tests) %}
# Run these specs with `crystal spec -Dwith_sec_tests`

require "../spec_helper"

describe "SecTester" do
it "tests the sign_in API for SQLi, and JWT attacks" do
scan_with_cleanup do |scanner|
api_headers = HTTP::Headers{"Content-Type" => "application/json", "Accept" => "application/json"}
target = scanner.build_target(Api::SignIns::Create, headers: api_headers) do |t|
t.body = {"user" => {"email" => "[email protected]", "password" => "123456789"}}.to_json
end
scanner.run_check(
scan_name: "ref: #{ENV["GITHUB_REF"]?} commit: #{ENV["GITHUB_SHA"]?} run id: #{ENV["GITHUB_RUN_ID"]?}",
tests: [
"sqli", # Testing for SQL Injection issues (https://docs.brightsec.com/docs/sql-injection)
"jwt", # Testing JWT usage (https://docs.brightsec.com/docs/broken-jwt-authentication)
"xss", # Testing for Cross Site Scripting attacks (https://docs.brightsec.com/docs/reflective-cross-site-scripting-rxss)
"ssrf", # Testing for SSRF (https://docs.brightsec.com/docs/server-side-request-forgery-ssrf)
"mass_assignment", # Testing for Mass Assignment issues (https://docs.brightsec.com/docs/mass-assignment)
"full_path_disclosure", # Testing for full path disclourse on api error (https://docs.brightsec.com/docs/full-path-disclosure)
],
target: target
)
end
end
end

private def scan_with_cleanup(&) : Nil
scanner = LuckySecTester.new
yield scanner
ensure
scanner.try &.cleanup
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "lucky_sec_tester"

# Signup for a `BRIGHT_TOKEN` at
# [NeuraLegion](https://app.neuralegion.com/signup)
# Read more about the SecTester on https://github.com/luckyframework/lucky_sec_tester
LuckySecTester.configure do |setting|
setting.bright_token = ENV["BRIGHT_TOKEN"]
setting.project_id = ENV["BRIGHT_PROJECT_ID"]
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "./server"

Authentic.configure do |settings|
settings.secret_key = Lucky::Server.settings.secret_key_base

unless LuckyEnv.production?
# This value can be between 4 and 31
fastest_encryption_possible = 4
settings.encryption_cost = fastest_encryption_possible
end
end
Empty file.
Loading

0 comments on commit 2a27a9b

Please sign in to comment.