diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b110b23 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,30 @@ +name: Ruby + +on: + push: + branches: + - main + + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + name: Ruby ${{ matrix.ruby }} + strategy: + matrix: + ruby: + - "2.7.8" + - "3.0.6" + - "3.1.4" + - "3.2.2" + + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run the default task + run: bundle exec rake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fcd9ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.bundle/ +/doc/ +/log/*.log +/pkg/ +/tmp/ +/spec/dummy/db/*.sqlite3 +/spec/dummy/db/*.sqlite3-* +/spec/dummy/log/*.log +/spec/dummy/storage/ +/spec/dummy/tmp/ + +Gemfile.lock diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d4fe518 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## [Unreleased] + +## [0.1.0] - 2023-11-29 + +- Initial release diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..306aa36 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,84 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at richard@appercept.com. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, +available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..72f5e35 --- /dev/null +++ b/Gemfile @@ -0,0 +1,18 @@ +source "https://rubygems.org" +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +# Specify your gem's dependencies in cognito_idp_rails.gemspec. +gemspec + +gem "puma" + +gem "sqlite3" + +gem "sprockets-rails" + +# Start debugger with binding.b [https://github.com/ruby/debug] +# gem "debug", ">= 1.0.0" + +gem "rspec-rails", "~> 6.1" + +gem "standard", "~> 1.32" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d66250 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Appercept Limited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..11cfe39 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# CognitoIdpRails + +Simple integration of Amazon Cognito IdP (User Pools) for Rails applications. + +## Installation + +Install the gem and add to the application's Gemfile by executing: + + $ bundle add cognito_idp_rails + +If bundler is not being used to manage dependencies, install the gem by executing: + + $ gem install cognito_idp_rails + +## Usage + +After adding the gem to your application, run the install generator: + + $ rails generate cognito_idp:install + +This generator will add `cognito_idp` to your routes and install an initializer at `config/initializers/cognito_idp.rb`. + +Be sure to review and edit the initialized to configure options for your Amazon Cognito User Pool configuration. You +must also provide an implementation for the `on_valid_login` function in the initializer appropriate for any actions you +want to take when a user signed in. + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/appercept/cognito_idp_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/appercept/cognito_idp_rails/blob/main/CODE_OF_CONDUCT.md). + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). + +## Code of Conduct + +Everyone interacting in the CognitoIdpRails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/appercept/cognito_idp_rails/blob/main/CODE_OF_CONDUCT.md). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..416b023 --- /dev/null +++ b/Rakefile @@ -0,0 +1,8 @@ +require "bundler/setup" + +APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__) +load "rails/tasks/engine.rake" + +load "rails/tasks/statistics.rake" + +require "bundler/gem_tasks" diff --git a/app/assets/config/cognito_idp_rails_manifest.js b/app/assets/config/cognito_idp_rails_manifest.js new file mode 100644 index 0000000..1592e80 --- /dev/null +++ b/app/assets/config/cognito_idp_rails_manifest.js @@ -0,0 +1 @@ +//= link_directory ../stylesheets/cognito_idp_rails .css diff --git a/app/assets/images/cognito_idp_rails/.keep b/app/assets/images/cognito_idp_rails/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/cognito_idp_rails/application.css b/app/assets/stylesheets/cognito_idp_rails/application.css new file mode 100644 index 0000000..0ebd7fe --- /dev/null +++ b/app/assets/stylesheets/cognito_idp_rails/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/controllers/cognito_idp_rails/application_controller.rb b/app/controllers/cognito_idp_rails/application_controller.rb new file mode 100644 index 0000000..a7490c0 --- /dev/null +++ b/app/controllers/cognito_idp_rails/application_controller.rb @@ -0,0 +1,4 @@ +module CognitoIdpRails + class ApplicationController < ActionController::Base + end +end diff --git a/app/controllers/cognito_idp_rails/sessions_controller.rb b/app/controllers/cognito_idp_rails/sessions_controller.rb new file mode 100644 index 0000000..26a13ef --- /dev/null +++ b/app/controllers/cognito_idp_rails/sessions_controller.rb @@ -0,0 +1,61 @@ +require "cognito_idp" + +module CognitoIdpRails + class SessionsController < ApplicationController + before_action :verify_state, only: [:login_callback] + + def login + redirect_to authorization_url, allow_other_host: true + end + + def login_callback + client.get_token(grant_type: :authorization_code, code: params[:code], redirect_uri: auth_login_callback_url) do |token| + client.get_user_info(token) do |user_info| + reset_session + configuration.on_valid_login.call(token, user_info, session) + redirect_to configuration.after_login_route, notice: "You have been successfully logged in." + return + end + end + redirect_to configuration.after_login_route, notice: "Login failed." + end + + def logout + redirect_to client.logout_uri(logout_uri: auth_logout_callback_url), allow_other_host: true + end + + def logout_callback + configuration.on_logout.call(session) + reset_session + redirect_to configuration.after_logout_route, notice: "You have been successfully logged out." + end + + private + + def authorization_url + client.authorization_uri(redirect_uri: auth_login_callback_url, scope: scope, state: login_state) + end + + def client + CognitoIdpRails.client + end + + def configuration + CognitoIdpRails.configuration + end + + def scope + configuration.scope + end + + def login_state + session[:login_state] ||= SecureRandom.urlsafe_base64 + end + + def verify_state + return if params[:state] == login_state + + redirect_to configuration.after_login_route, notice: "Login failed." + end + end +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/helpers/cognito_idp_rails/application_helper.rb b/app/helpers/cognito_idp_rails/application_helper.rb new file mode 100644 index 0000000..c353aae --- /dev/null +++ b/app/helpers/cognito_idp_rails/application_helper.rb @@ -0,0 +1,4 @@ +module CognitoIdpRails + module ApplicationHelper + end +end diff --git a/app/jobs/cognito_idp_rails/application_job.rb b/app/jobs/cognito_idp_rails/application_job.rb new file mode 100644 index 0000000..dc3c474 --- /dev/null +++ b/app/jobs/cognito_idp_rails/application_job.rb @@ -0,0 +1,4 @@ +module CognitoIdpRails + class ApplicationJob < ActiveJob::Base + end +end diff --git a/app/mailers/cognito_idp_rails/application_mailer.rb b/app/mailers/cognito_idp_rails/application_mailer.rb new file mode 100644 index 0000000..0b455d0 --- /dev/null +++ b/app/mailers/cognito_idp_rails/application_mailer.rb @@ -0,0 +1,6 @@ +module CognitoIdpRails + class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" + end +end diff --git a/app/models/cognito_idp_rails/application_record.rb b/app/models/cognito_idp_rails/application_record.rb new file mode 100644 index 0000000..353205c --- /dev/null +++ b/app/models/cognito_idp_rails/application_record.rb @@ -0,0 +1,5 @@ +module CognitoIdpRails + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/views/layouts/cognito_idp_rails/application.html.erb b/app/views/layouts/cognito_idp_rails/application.html.erb new file mode 100644 index 0000000..cfc4c86 --- /dev/null +++ b/app/views/layouts/cognito_idp_rails/application.html.erb @@ -0,0 +1,15 @@ + + + + Cognito idp rails + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "cognito_idp_rails/application", media: "all" %> + + + +<%= yield %> + + + diff --git a/bin/rails b/bin/rails new file mode 100755 index 0000000..cde485e --- /dev/null +++ b/bin/rails @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path("..", __dir__) +ENGINE_PATH = File.expand_path("../lib/cognito_idp_rails/engine", __dir__) +APP_PATH = File.expand_path("../spec/dummy/config/application", __dir__) + +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) + +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 "rails/engine/commands" diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..cb53ebe --- /dev/null +++ b/bin/rspec @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/standardrb b/bin/standardrb new file mode 100755 index 0000000..b329561 --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'standardrb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("standard", "standardrb") diff --git a/cognito_idp_rails.gemspec b/cognito_idp_rails.gemspec new file mode 100644 index 0000000..df9d944 --- /dev/null +++ b/cognito_idp_rails.gemspec @@ -0,0 +1,23 @@ +require_relative "lib/cognito_idp_rails/version" + +Gem::Specification.new do |spec| + spec.name = "cognito_idp_rails" + spec.version = CognitoIdpRails::VERSION + spec.authors = ["Richard Hatherall"] + spec.email = ["richard@appercept.com"] + spec.homepage = "https://github.com/appercept/cognito_idp_rails" + spec.summary = "Simple Rails integration for Amazon Cognito IdP (User Pools)" + spec.description = "Simple Rails integration for authentication through Amazon Cognito IdP (User Pools)" + spec.license = "MIT" + + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md" + + spec.files = Dir.chdir(File.expand_path(__dir__)) do + Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] + end + + spec.add_dependency "cognito_idp", ">= 0.1.1" + spec.add_dependency "rails", ">= 7.0.0" +end diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..0eac7c0 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,2 @@ +CognitoIdpRails::Engine.routes.draw do +end diff --git a/lib/cognito_idp_rails.rb b/lib/cognito_idp_rails.rb new file mode 100644 index 0000000..234a61b --- /dev/null +++ b/lib/cognito_idp_rails.rb @@ -0,0 +1,29 @@ +require "cognito_idp_rails/engine" +require "cognito_idp_rails/version" +require "cognito_idp" + +module CognitoIdpRails + autoload :Configuration, "cognito_idp_rails/configuration" + + module Routing + autoload :MapperExtensions, "cognito_idp_rails/routing/mapper_extensions" + end + + class << self + def client + @client ||= CognitoIdp::Client.new( + client_id: configuration.client_id, + client_secret: configuration.client_secret, + domain: configuration.domain + ) + end + + def configuration + @configuration ||= Configuration.new + end + + def configure + yield(configuration) + end + end +end diff --git a/lib/cognito_idp_rails/configuration.rb b/lib/cognito_idp_rails/configuration.rb new file mode 100644 index 0000000..0e36524 --- /dev/null +++ b/lib/cognito_idp_rails/configuration.rb @@ -0,0 +1,13 @@ +module CognitoIdpRails + class Configuration + attr_accessor :after_login_route, :after_logout_route, :domain, :client_id, + :client_secret, :on_logout, :on_valid_login, :scope + + def initialize + @after_login_route = "/" + @after_logout_route = "/" + @on_valid_login = lambda { |token, user_info, session| } + @on_logout = lambda { |session| } + end + end +end diff --git a/lib/cognito_idp_rails/engine.rb b/lib/cognito_idp_rails/engine.rb new file mode 100644 index 0000000..5bc3c1c --- /dev/null +++ b/lib/cognito_idp_rails/engine.rb @@ -0,0 +1,11 @@ +module CognitoIdpRails + class Engine < ::Rails::Engine + initializer "cognito_idp_rails.add_routing_paths" do |app| + ActionDispatch::Routing::Mapper.send(:include, CognitoIdpRails::Routing::MapperExtensions) + end + + config.generators do |g| + g.test_framework :rspec + end + end +end diff --git a/lib/cognito_idp_rails/routing/mapper_extensions.rb b/lib/cognito_idp_rails/routing/mapper_extensions.rb new file mode 100644 index 0000000..9f4390f --- /dev/null +++ b/lib/cognito_idp_rails/routing/mapper_extensions.rb @@ -0,0 +1,12 @@ +module CognitoIdpRails + module Routing + module MapperExtensions + def cognito_idp + get("/login", to: "cognito_idp_rails/sessions#login") + get("/auth/login_callback", to: "cognito_idp_rails/sessions#login_callback") + get("/logout", to: "cognito_idp_rails/sessions#logout") + get("/auth/logout_callback", to: "cognito_idp_rails/sessions#logout_callback") + end + end + end +end diff --git a/lib/cognito_idp_rails/version.rb b/lib/cognito_idp_rails/version.rb new file mode 100644 index 0000000..787c9f6 --- /dev/null +++ b/lib/cognito_idp_rails/version.rb @@ -0,0 +1,3 @@ +module CognitoIdpRails + VERSION = "0.1.0" +end diff --git a/lib/generators/cognito_idp_rails/install_generator.rb b/lib/generators/cognito_idp_rails/install_generator.rb new file mode 100644 index 0000000..560c8f2 --- /dev/null +++ b/lib/generators/cognito_idp_rails/install_generator.rb @@ -0,0 +1,18 @@ +require "rails/generators" + +module CognitoIdpRails + module Generators + class InstallGenerator < Rails::Generators::Base + desc "Add an initializer and routes for Cognito IdP to your app" + source_root File.expand_path("templates", __dir__) + + def copy_initializer + template "cognito_idp_rails_initializer.rb.tt", "config/initializers/cognito_idp.rb" + end + + def add_routes + route "cognito_idp" + end + end + end +end diff --git a/lib/generators/cognito_idp_rails/templates/cognito_idp_rails_initializer.rb.tt b/lib/generators/cognito_idp_rails/templates/cognito_idp_rails_initializer.rb.tt new file mode 100644 index 0000000..dd44909 --- /dev/null +++ b/lib/generators/cognito_idp_rails/templates/cognito_idp_rails_initializer.rb.tt @@ -0,0 +1,17 @@ +CognitoIdpRails.configure do |config| + config.client_id = ENV["COGNITO_CLIENT_ID"] + config.client_secret = ENV["COGNITO_CLIENT_SECRET"] + config.domain = ENV["COGNITO_DOMAIN"] + config.on_valid_login = lambda do |token, user_info, session| + # 1. Find or create a user. + # user = User.where(identifier: user_info.sub).find_or_create do |user| + # user.email = user_info.email + # end + + # 2. Set any session data for the user. + # session[:user_id] = user.id + end + config.on_logout = lambda do |session| + # Your last chance to do something before the session is reset. + end +end diff --git a/lib/tasks/cognito_idp_rails_tasks.rake b/lib/tasks/cognito_idp_rails_tasks.rake new file mode 100644 index 0000000..2ce95bf --- /dev/null +++ b/lib/tasks/cognito_idp_rails_tasks.rake @@ -0,0 +1,2 @@ +default_install_migrations_task_name = "cognito_idp_rails:install:migrations" +Rake::Task[default_install_migrations_task_name].clear if Rake::Task.task_defined?(default_install_migrations_task_name) diff --git a/spec/cognito_idp_rails/configuration_spec.rb b/spec/cognito_idp_rails/configuration_spec.rb new file mode 100644 index 0000000..66f3652 --- /dev/null +++ b/spec/cognito_idp_rails/configuration_spec.rb @@ -0,0 +1,137 @@ +require "rails_helper" + +RSpec.describe CognitoIdpRails::Configuration do + subject(:configuration) { described_class.new } + + describe "#after_login_route" do + subject(:after_login_route) { configuration.after_login_route } + + it "defaults to /" do + is_expected.to eq("/") + end + + context "when specified" do + before do + configuration.after_login_route = new_route + end + + let(:new_route) { "/new_route" } + + it { is_expected.to eq(new_route) } + end + end + + describe "#after_logout_route" do + subject(:after_logout_route) { configuration.after_logout_route } + + it "defaults to /" do + is_expected.to eq("/") + end + + context "when specified" do + before do + configuration.after_logout_route = new_route + end + + let(:new_route) { "/new_route" } + + it { is_expected.to eq(new_route) } + end + end + + describe "#domain" do + subject(:domain) { configuration.domain } + + it { is_expected.to be_nil } + + context "when specified" do + before do + configuration.domain = new_domain + end + + let(:new_domain) { "new.example.com" } + + it { is_expected.to eq(new_domain) } + end + end + + describe "#client_id" do + subject(:client_id) { configuration.client_id } + + it { is_expected.to be_nil } + + context "when specified" do + before do + configuration.client_id = new_client_id + end + + let(:new_client_id) { "new-client-id" } + + it { is_expected.to eq(new_client_id) } + end + end + + describe "#client_secret" do + subject(:client_secret) { configuration.client_secret } + + it { is_expected.to be_nil } + + context "when specified" do + before do + configuration.client_secret = new_client_secret + end + + let(:new_client_secret) { "new-client-secret" } + + it { is_expected.to eq(new_client_secret) } + end + end + + describe "#on_logout" do + subject(:on_logout) { configuration.on_logout } + + it { is_expected.to be_a(Proc) } + + context "when specified" do + before do + configuration.on_logout = new_on_logout + end + + let(:new_on_logout) { instance_double(Proc) } + + it { is_expected.to eq(new_on_logout) } + end + end + + describe "#on_valid_login" do + subject(:on_valid_login) { configuration.on_valid_login } + + it { is_expected.to be_a(Proc) } + + context "when specified" do + before do + configuration.on_valid_login = new_on_valid_login + end + + let(:new_on_valid_login) { instance_double(Proc) } + + it { is_expected.to eq(new_on_valid_login) } + end + end + + describe "#scope" do + subject(:scope) { configuration.scope } + + it { is_expected.to be_nil } + + context "when specified" do + before do + configuration.scope = new_scope + end + + let(:new_scope) { "new scope" } + + it { is_expected.to eq(new_scope) } + end + end +end diff --git a/spec/cognito_idp_rails/configure_spec.rb b/spec/cognito_idp_rails/configure_spec.rb new file mode 100644 index 0000000..71c2ee0 --- /dev/null +++ b/spec/cognito_idp_rails/configure_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe CognitoIdpRails do + describe ".client" do + subject(:client) { described_class.client } + + let(:client_id) { "client-1" } + let(:client_secret) { "client-secret" } + let(:domain) { "id.example.com" } + + before do + CognitoIdpRails.configure do |config| + config.client_id = client_id + config.client_secret = client_secret + config.domain = domain + end + end + + it { expect(client.client_id).to eq(client_id) } + it { expect(client.client_secret).to eq(client_secret) } + it { expect(client.domain).to eq(domain) } + end + + describe ".configure" do + it "yields configuration to a block" do + expect { |b| described_class.configure(&b) }.to yield_with_args(described_class.configuration) + end + end +end diff --git a/spec/dummy/Rakefile b/spec/dummy/Rakefile new file mode 100644 index 0000000..9a5ea73 --- /dev/null +++ b/spec/dummy/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/spec/dummy/app/assets/config/manifest.js b/spec/dummy/app/assets/config/manifest.js new file mode 100644 index 0000000..c6ec687 --- /dev/null +++ b/spec/dummy/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link cognito_idp_rails_manifest.js diff --git a/spec/dummy/app/assets/images/.keep b/spec/dummy/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/app/assets/stylesheets/application.css b/spec/dummy/app/assets/stylesheets/application.css new file mode 100644 index 0000000..0ebd7fe --- /dev/null +++ b/spec/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/spec/dummy/app/channels/application_cable/channel.rb b/spec/dummy/app/channels/application_cable/channel.rb new file mode 100644 index 0000000..d672697 --- /dev/null +++ b/spec/dummy/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/spec/dummy/app/channels/application_cable/connection.rb b/spec/dummy/app/channels/application_cable/connection.rb new file mode 100644 index 0000000..0ff5442 --- /dev/null +++ b/spec/dummy/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/spec/dummy/app/controllers/application_controller.rb b/spec/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000..09705d1 --- /dev/null +++ b/spec/dummy/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/spec/dummy/app/controllers/concerns/.keep b/spec/dummy/app/controllers/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/app/helpers/application_helper.rb b/spec/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/spec/dummy/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/spec/dummy/app/jobs/application_job.rb b/spec/dummy/app/jobs/application_job.rb new file mode 100644 index 0000000..d394c3d --- /dev/null +++ b/spec/dummy/app/jobs/application_job.rb @@ -0,0 +1,7 @@ +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end diff --git a/spec/dummy/app/mailers/application_mailer.rb b/spec/dummy/app/mailers/application_mailer.rb new file mode 100644 index 0000000..3c34c81 --- /dev/null +++ b/spec/dummy/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: "from@example.com" + layout "mailer" +end diff --git a/spec/dummy/app/models/application_record.rb b/spec/dummy/app/models/application_record.rb new file mode 100644 index 0000000..b63caeb --- /dev/null +++ b/spec/dummy/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + primary_abstract_class +end diff --git a/spec/dummy/app/models/concerns/.keep b/spec/dummy/app/models/concerns/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000..f72b4ef --- /dev/null +++ b/spec/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + + Dummy + + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag "application" %> + + + + <%= yield %> + + diff --git a/spec/dummy/app/views/layouts/mailer.html.erb b/spec/dummy/app/views/layouts/mailer.html.erb new file mode 100644 index 0000000..3aac900 --- /dev/null +++ b/spec/dummy/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/spec/dummy/app/views/layouts/mailer.text.erb b/spec/dummy/app/views/layouts/mailer.text.erb new file mode 100644 index 0000000..37f0bdd --- /dev/null +++ b/spec/dummy/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/spec/dummy/bin/rails b/spec/dummy/bin/rails new file mode 100755 index 0000000..efc0377 --- /dev/null +++ b/spec/dummy/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/spec/dummy/bin/rake b/spec/dummy/bin/rake new file mode 100755 index 0000000..4fbf10b --- /dev/null +++ b/spec/dummy/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/spec/dummy/bin/setup b/spec/dummy/bin/setup new file mode 100755 index 0000000..3ec5486 --- /dev/null +++ b/spec/dummy/bin/setup @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require "fileutils" + +# path to your application root. +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*) + system(*, exception: true) +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # Add necessary setup steps to this file. + + puts "== Installing dependencies ==" + system! "gem install bundler --conservative" + system("bundle check") || system!("bundle install") + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + puts "\n== Removing old logs and tempfiles ==" + system! "bin/rails log:clear tmp:clear" + + puts "\n== Restarting application server ==" + system! "bin/rails restart" +end diff --git a/spec/dummy/config.ru b/spec/dummy/config.ru new file mode 100644 index 0000000..4a3c09a --- /dev/null +++ b/spec/dummy/config.ru @@ -0,0 +1,6 @@ +# This file is used by Rack-based servers to start the application. + +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb new file mode 100644 index 0000000..5fc080b --- /dev/null +++ b/spec/dummy/config/application.rb @@ -0,0 +1,44 @@ +require_relative "boot" + +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. +Bundler.require(*Rails.groups) + +module Dummy + class Application < Rails::Application + config.load_defaults Rails::VERSION::STRING.to_f + + # For compatibility with applications that use this config + config.action_controller.include_all_helpers = false + + # Please, add to the `ignore` list any other `lib` subdirectories that do + # not contain `.rb` files, or that should not be reloaded or eager loaded. + # Common ones are `templates`, `generators`, or `middleware`, for example. + config.autoload_lib(ignore: %w[assets tasks]) + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") + + # Don't generate system test files. + config.generators.system_tests = nil + end +end diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb new file mode 100644 index 0000000..116591a --- /dev/null +++ b/spec/dummy/config/boot.rb @@ -0,0 +1,5 @@ +# Set up gems listed in the Gemfile. +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) + +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) +$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) diff --git a/spec/dummy/config/cable.yml b/spec/dummy/config/cable.yml new file mode 100644 index 0000000..98367f8 --- /dev/null +++ b/spec/dummy/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: dummy_production diff --git a/spec/dummy/config/database.yml b/spec/dummy/config/database.yml new file mode 100644 index 0000000..796466b --- /dev/null +++ b/spec/dummy/config/database.yml @@ -0,0 +1,25 @@ +# SQLite. Versions 3.8.0 and up are supported. +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem "sqlite3" +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: storage/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: storage/test.sqlite3 + +production: + <<: *default + database: storage/production.sqlite3 diff --git a/spec/dummy/config/environment.rb b/spec/dummy/config/environment.rb new file mode 100644 index 0000000..cac5315 --- /dev/null +++ b/spec/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative "application" + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb new file mode 100644 index 0000000..2e7fb48 --- /dev/null +++ b/spec/dummy/config/environments/development.rb @@ -0,0 +1,76 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded any time + # it changes. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.enable_reloading = true + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # 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 + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Highlight code that enqueued background job in logs. + config.active_job.verbose_enqueue_logs = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true + + # Raise error when a before_action's only/except options reference missing actions + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/spec/dummy/config/environments/production.rb b/spec/dummy/config/environments/production.rb new file mode 100644 index 0000000..84ffe34 --- /dev/null +++ b/spec/dummy/config/environments/production.rb @@ -0,0 +1,97 @@ +require "active_support/core_ext/integer/time" + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.enable_reloading = false + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in ENV["RAILS_MASTER_KEY"], config/master.key, or an environment + # key such as config/credentials/production.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. + # config.public_file_server.enabled = false + + # Compress CSS using a preprocessor. + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.asset_host = "http://assets.example.com" + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache + # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain. + # config.action_cable.mount_path = nil + # config.action_cable.url = "wss://example.com/cable" + # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] + + # Assume all access to the app is happening through a SSL-terminating reverse proxy. + # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. + # config.assume_ssl = true + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # Log to STDOUT by default + config.logger = ActiveSupport::Logger.new(STDOUT) + .tap { |logger| logger.formatter = ::Logger::Formatter.new } + .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Info include generic and useful information about system operation, but avoids logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). If you + # want to log everything, set the level to "debug". + config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment). + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "dummy_production" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Don't log any deprecations. + config.active_support.report_deprecations = false + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + # Enable DNS rebinding protection and other `Host` header attacks. + # config.hosts = [ + # "example.com", # Allow requests from example.com + # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` + # ] + # Skip DNS rebinding protection for the default health check endpoint. + # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } +end diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb new file mode 100644 index 0000000..adbb4a6 --- /dev/null +++ b/spec/dummy/config/environments/test.rb @@ -0,0 +1,64 @@ +require "active_support/core_ext/integer/time" + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # While tests run files are not watched, reloading is not necessary. + config.enable_reloading = false + + # Eager loading loads your entire application. When running a single test locally, + # this is usually not necessary, and can slow down your test suite. However, it's + # recommended that you enable it in continuous integration systems to ensure eager + # loading is working properly before deploying your code. + config.eager_load = ENV["CI"].present? + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + config.cache_store = :null_store + + # Render exception templates for rescuable exceptions and raise for other exceptions. + config.action_dispatch.show_exceptions = :rescuable + + # 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 + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + + # Raise error when a before_action's only/except options reference missing actions + config.action_controller.raise_on_missing_callback_actions = true +end diff --git a/spec/dummy/config/initializers/assets.rb b/spec/dummy/config/initializers/assets.rb new file mode 100644 index 0000000..2eeef96 --- /dev/null +++ b/spec/dummy/config/initializers/assets.rb @@ -0,0 +1,12 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = "1.0" + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/spec/dummy/config/initializers/content_security_policy.rb b/spec/dummy/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..b3076b3 --- /dev/null +++ b/spec/dummy/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end +# +# # Generate session nonces for permitted importmap, inline scripts, and inline styles. +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src style-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/spec/dummy/config/initializers/filter_parameter_logging.rb b/spec/dummy/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..c2d89e2 --- /dev/null +++ b/spec/dummy/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. +# Use this to limit dissemination of sensitive information. +# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/spec/dummy/config/initializers/inflections.rb b/spec/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000..3860f65 --- /dev/null +++ b/spec/dummy/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" +# end diff --git a/spec/dummy/config/initializers/permissions_policy.rb b/spec/dummy/config/initializers/permissions_policy.rb new file mode 100644 index 0000000..7db3b95 --- /dev/null +++ b/spec/dummy/config/initializers/permissions_policy.rb @@ -0,0 +1,13 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide HTTP permissions policy. For further +# information see: https://developers.google.com/web/updates/2018/06/feature-policy + +# Rails.application.config.permissions_policy do |policy| +# policy.camera :none +# policy.gyroscope :none +# policy.microphone :none +# policy.usb :none +# policy.fullscreen :self +# policy.payment :self, "https://secure.example.com" +# end diff --git a/spec/dummy/config/locales/en.yml b/spec/dummy/config/locales/en.yml new file mode 100644 index 0000000..6c349ae --- /dev/null +++ b/spec/dummy/config/locales/en.yml @@ -0,0 +1,31 @@ +# Files in the config/locales directory are used for internationalization and +# are automatically loaded by Rails. If you want to use locales other than +# English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t "hello" +# +# In views, this is aliased to just `t`: +# +# <%= t("hello") %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more about the API, please read the Rails Internationalization guide +# at https://guides.rubyonrails.org/i18n.html. +# +# Be aware that YAML interprets the following case-insensitive strings as +# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings +# must be quoted to be interpreted as strings. For example: +# +# en: +# "yes": yup +# enabled: "ON" + +en: + hello: "Hello world" diff --git a/spec/dummy/config/puma.rb b/spec/dummy/config/puma.rb new file mode 100644 index 0000000..afa809b --- /dev/null +++ b/spec/dummy/config/puma.rb @@ -0,0 +1,35 @@ +# This configuration file will be evaluated by Puma. The top-level methods that +# are invoked here are part of Puma's configuration DSL. For more information +# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. + +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +# Specifies that the worker count should equal the number of processors in production. +if ENV["RAILS_ENV"] == "production" + require "concurrent-ruby" + worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count }) + workers worker_count if worker_count > 1 +end + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + +# Allow puma to be restarted by `bin/rails restart` command. +plugin :tmp_restart diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb new file mode 100644 index 0000000..e986010 --- /dev/null +++ b/spec/dummy/config/routes.rb @@ -0,0 +1,3 @@ +Rails.application.routes.draw do + cognito_idp +end diff --git a/spec/dummy/config/storage.yml b/spec/dummy/config/storage.yml new file mode 100644 index 0000000..4942ab6 --- /dev/null +++ b/spec/dummy/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket-<%= Rails.env %> + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket-<%= Rails.env %> + +# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name-<%= Rails.env %> + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/spec/dummy/lib/assets/.keep b/spec/dummy/lib/assets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/log/.keep b/spec/dummy/log/.keep new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/public/404.html b/spec/dummy/public/404.html new file mode 100644 index 0000000..2be3af2 --- /dev/null +++ b/spec/dummy/public/404.html @@ -0,0 +1,67 @@ + + + + The page you were looking for doesn't exist (404) + + + + + + +
+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/422.html b/spec/dummy/public/422.html new file mode 100644 index 0000000..c08eac0 --- /dev/null +++ b/spec/dummy/public/422.html @@ -0,0 +1,67 @@ + + + + The change you wanted was rejected (422) + + + + + + +
+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/500.html b/spec/dummy/public/500.html new file mode 100644 index 0000000..78a030a --- /dev/null +++ b/spec/dummy/public/500.html @@ -0,0 +1,66 @@ + + + + We're sorry, but something went wrong (500) + + + + + + +
+
+

We're sorry, but something went wrong.

+
+

If you are the application owner check the logs for more information.

+
+ + diff --git a/spec/dummy/public/apple-touch-icon-precomposed.png b/spec/dummy/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/public/apple-touch-icon.png b/spec/dummy/public/apple-touch-icon.png new file mode 100644 index 0000000..e69de29 diff --git a/spec/dummy/public/favicon.ico b/spec/dummy/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..72ea3f3 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,67 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require "spec_helper" +ENV["RAILS_ENV"] ||= "test" +require_relative "../spec/dummy/config/environment" +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require "rspec/rails" +# Add additional requires below this line. Rails is not loaded until this point! + +require "cognito_idp_rails" + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Rails.root.glob('spec/support/**/*.rb').sort.each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + abort e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + # config.fixture_paths = [ + # Rails.root.join("spec/fixtures") + # ] + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://rspec.info/features/6-0/rspec-rails + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/requests/cognito_idp_rails/sessions_spec.rb b/spec/requests/cognito_idp_rails/sessions_spec.rb new file mode 100644 index 0000000..464f6dc --- /dev/null +++ b/spec/requests/cognito_idp_rails/sessions_spec.rb @@ -0,0 +1,247 @@ +require "rails_helper" + +RSpec.describe "Sessions", type: :request do + before do + allow(CognitoIdpRails).to receive(:client).and_return(client) + allow(configuration).to receive(:on_valid_login).and_return(on_valid_login) + allow(on_valid_login).to receive(:call) + allow(configuration).to receive(:on_logout).and_return(on_logout) + allow(on_logout).to receive(:call) + end + + let(:configuration) { CognitoIdpRails.configuration } + let(:client) { CognitoIdp::Client.new(client_id: client_id, client_secret: client_secret, domain: domain) } + let(:client_id) { "client-1" } + let(:client_secret) { "SECRET" } + let(:domain) { "auth.example.com" } + let(:redirect_uri) { "http://www.example.com/auth/login_callback" } + let(:on_valid_login) do + lambda { |token, user_info, session| } + end + let(:on_logout) do + lambda { |session| } + end + + describe "GET /login" do + let(:response_location) { URI(response.headers["location"]) } + let(:redirect_params) { URI.decode_www_form(response_location.query) } + + it "redirects to the authorization server" do + get "/login" + + expect(response).to redirect_to(%r{\Ahttps://auth.example.com/oauth2/authorize}) + end + + it "redirects with a client_id" do + get "/login" + + expect(redirect_params).to include(["client_id", client_id]) + end + + it "redirects with a redirect_uri" do + get "/login" + + expect(redirect_params).to include(["redirect_uri", redirect_uri]) + end + + it "redirects with a response_type" do + get "/login" + + expect(redirect_params).to include(["response_type", "code"]) + end + + it "redirects with a state" do + get "/login" + + expect(redirect_params).to include(["state", String]) + end + + it "remembers the login_state" do + get "/login" + + expect(session[:login_state]).to be_present + end + end + + describe "GET /auth/login_callback" do + let(:path) { "/auth/login_callback?state=#{state}&code=#{code}" } + let(:state) do + get "/login" + session[:login_state] + end + let(:code) { "CODE" } + + shared_examples "successful login" do + it "redirects to the after_login_route" do + get path + + expect(response).to redirect_to(configuration.after_login_route) + end + + it "presents a success notice" do + get path + + expect(flash[:notice]).to eq("You have been successfully logged in.") + end + end + + shared_examples "unsuccessful login" do + it "redirects to the after_login_route" do + state + + get path + + expect(response).to redirect_to(configuration.after_login_route) + end + + it "presents a failure notice" do + state + + get path + + expect(flash[:notice]).to eq("Login failed.") + end + end + + context "when valid params are returned" do + let(:valid_token) { CognitoIdp::Token.new(access_token: access_token) } + let(:access_token) { "access-token" } + let(:user_info) { CognitoIdp::UserInfo.new(user_info_params) } + let(:user_info_params) do + {sub: "SUB"} + end + + before do + allow(client).to receive(:get_token) + .with(grant_type: :authorization_code, code: code, redirect_uri: redirect_uri) + .and_yield(valid_token) + allow(client).to receive(:get_user_info).with(valid_token).and_yield(user_info) + end + + it "requests a token" do + get path + + expect(client).to have_received(:get_token) + .with(grant_type: :authorization_code, code: code, redirect_uri:) + end + + context "when a token is received" do + it "requests user_info" do + get path + + expect(client).to have_received(:get_user_info).with(valid_token) + end + + context "when user_info is received" do + include_examples "successful login" + + it "resets the session" do + state + original_session_id = session[:session_id] + + get path + + expect(session[:session_id]).not_to eq(original_session_id) + end + + it "calls back to on_valid_login" do + get path + + expect(on_valid_login).to have_received(:call).with(valid_token, user_info, ActionDispatch::Request::Session) + end + end + + context "when user_info is not received" do + before do + allow(client).to receive(:get_token) + .with(grant_type: :authorization_code, code: code, redirect_uri: redirect_uri) + .and_yield(valid_token) + allow(client).to receive(:get_user_info).with(valid_token).and_return(nil) + end + + include_examples "unsuccessful login" + + it "does not call back to on_valid_login" do + expect(on_valid_login).not_to have_received(:call) + end + end + end + + context "when a token is not received" do + before do + allow(client).to receive(:get_token) + .with(grant_type: :authorization_code, code: code, redirect_uri: redirect_uri) + .and_return(nil) + end + + include_examples "unsuccessful login" + + it "does not request user_info" do + expect(client).not_to have_received(:get_user_info).with(valid_token) + end + + it "does not call back to on_valid_login" do + expect(on_valid_login).not_to have_received(:call) + end + end + end + + context "when a valid state is not returned" do + let(:path) { "/auth/login_callback?state=INVALID" } + + include_examples "unsuccessful login" + end + + context "when state is not returned" do + let(:path) { "/auth/login_callback" } + + include_examples "unsuccessful login" + end + end + + describe "GET /logout" do + let(:response_location) { URI(response.headers["location"]) } + let(:redirect_params) { URI.decode_www_form(response_location.query) } + + it "redirects to the authorization server" do + get "/logout" + + expect(response).to redirect_to(%r{\Ahttps://auth.example.com/logout}) + end + + it "redirects with a logout_uri" do + get "/logout" + + expect(redirect_params).to include(["logout_uri", "http://www.example.com/auth/logout_callback"]) + end + end + + describe "GET /auth/logout_callback" do + it "calls back to on_valid_login" do + get "/auth/logout_callback" + + expect(on_logout).to have_received(:call).with(ActionDispatch::Request::Session) + end + + it "redirects to the after_logout_route" do + get "/auth/logout_callback" + + expect(response).to redirect_to(configuration.after_logout_route) + end + + it "presents a notice" do + get "/auth/logout_callback" + + expect(flash[:notice]).to eq("You have been successfully logged out.") + end + + it "resets the session" do + get "/login" + original_session_id = session[:session_id] + + get "/auth/logout_callback" + + expect(session[:session_id]).not_to eq(original_session_id) + end + end +end diff --git a/spec/routing/mapper_extensions_spec.rb b/spec/routing/mapper_extensions_spec.rb new file mode 100644 index 0000000..e054050 --- /dev/null +++ b/spec/routing/mapper_extensions_spec.rb @@ -0,0 +1,30 @@ +require "rails_helper" + +RSpec.describe CognitoIdpRails::Routing::MapperExtensions, type: :routing do + it "defines cognito_idp method" do + expect(ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new)).to respond_to(:cognito_idp) + end + + before do + Rails.application.routes.draw do + cognito_idp + end + Rails.application.reload_routes! + end + + it "adds login route" do + expect(Rails.application.routes.recognize_path("/login")).to match(controller: "cognito_idp_rails/sessions", action: "login") + end + + it "adds auth_login_callback route" do + expect(Rails.application.routes.recognize_path("/auth/login_callback")).to match(controller: "cognito_idp_rails/sessions", action: "login_callback") + end + + it "adds logout route" do + expect(Rails.application.routes.recognize_path("/logout")).to match(controller: "cognito_idp_rails/sessions", action: "logout") + end + + it "adds auth_logout_callback route" do + expect(Rails.application.routes.recognize_path("/auth/logout_callback")).to match(controller: "cognito_idp_rails/sessions", action: "logout_callback") + end +end diff --git a/spec/routing/sessions_routes_spec.rb b/spec/routing/sessions_routes_spec.rb new file mode 100644 index 0000000..e2ad803 --- /dev/null +++ b/spec/routing/sessions_routes_spec.rb @@ -0,0 +1,8 @@ +require "rails_helper" + +RSpec.describe "Cognito IdP sessions routes", type: :routing do + it { expect(get("/login")).to route_to("cognito_idp_rails/sessions#login") } + it { expect(get("/auth/login_callback")).to route_to("cognito_idp_rails/sessions#login_callback") } + it { expect(get("/logout")).to route_to("cognito_idp_rails/sessions#logout") } + it { expect(get("/auth/logout_callback")).to route_to("cognito_idp_rails/sessions#logout_callback") } +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..9c96a9b --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,92 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed +end