diff --git a/.circleci/config.yml b/.circleci/config.yml index 8eabd517846..da2c737364e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -152,6 +152,7 @@ jobs: command: | mkdir /tmp/test-results ./cc-test-reporter before-build + bin/rails zeitwerk:check bundle exec rake knapsack:rspec yarn test @@ -182,9 +183,8 @@ jobs: yarn run lint yarn run typecheck bundle exec rubocop - bundle exec slim-lint app/views make check_asset_strings - ./bin/webpack && yarn es5-safe + NODE_ENV=production ./bin/webpack && yarn es5-safe build-release-container: working_directory: ~/identity-idp docker: @@ -233,7 +233,9 @@ jobs: environment: MONITOR_ENV: STAGING steps: - - checkout + - jq/install + - checkout-deployed-sha: + sha_url: https://idp.staging.login.gov/api/deploy.json - bundle-yarn-install - run: name: "Smoke tests" @@ -302,7 +304,7 @@ workflows: filters: branches: only: - - stages/staging + - master jobs: - smoketest-staging diff --git a/.codeclimate.yml b/.codeclimate.yml index 90f2ff87a0c..d8b71800205 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -32,11 +32,6 @@ checks: plugins: brakeman: enabled: true - exclude_patterns: - # Excluding User Flows tools since these are not loaded - # except when explicitly called from the User Flow rake tasks - - 'lib/user_flow_exporter.rb' - - 'lib/rspec/formatters/user_flow_formatter.rb' bundler-audit: enabled: true coffeelint: @@ -53,8 +48,6 @@ plugins: - 'node_modules/**/*' - 'db/schema.rb' - 'app/forms/password_form.rb' - - 'lib/user_flow_exporter.rb' - - 'lib/rspec/formatters/user_flow_formatter.rb' fixme: enabled: true exclude_patterns: @@ -81,9 +74,7 @@ exclude_patterns: - 'db/schema.rb' - 'node_modules/' - 'lib/proofer_mocks/' - - 'lib/rspec/formatters/user_flow_formatter.rb' - 'lib/tasks/create_test_accounts.rb' - - 'lib/user_flow_exporter.rb' - 'scripts/load_testing/' - 'tmp/' - 'config/initializers/jwt.rb' diff --git a/.eslintrc b/.eslintrc index 6a472bfd8b0..9a047b4141e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -25,6 +25,7 @@ "indent": "off", "max-classes-per-file": "off", "newline-per-chained-call": "off", + "no-empty": ["error", { "allowEmptyCatch": true }], "no-param-reassign": ["off", "never"], "no-confusing-arrow": "off", "no-plusplus": "off", diff --git a/.rubocop.yml b/.rubocop.yml index 47654b1455d..622837096d8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,30 +10,18 @@ AllCops: - 'bin/**/*' - 'db/migrate/*' - 'db/schema.rb' - - 'lib/rspec/user_flow_formatter.rb' - 'lib/tasks/create_test_accounts.rb' - - 'lib/user_flow_exporter.rb' - 'node_modules/**/*' - 'tmp/**/*' - 'vendor/**/*' TargetRubyVersion: 2.6 - TargetRailsVersion: 5.2 + TargetRailsVersion: 6.0 UseCache: true + DisabledByDefault: true Rails: Enabled: true -Metrics/AbcSize: - Description: A calculated magnitude based on number of assignments, branches, and - conditions. - Enabled: false - Max: 15 - Exclude: - - spec/**/* - -Metrics/CyclomaticComplexity: - Enabled: false - Metrics/BlockLength: CountComments: false # count full line comments? Enabled: true @@ -47,12 +35,6 @@ Metrics/BlockLength: - 'config/routes.rb' - 'spec/**/*.rb' -Metrics/ClassLength: - Description: Avoid classes longer than 100 lines of code. - Enabled: false - CountComments: false - Max: 100 - Layout/LineLength: Description: Limit lines to 100 characters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#80-character-limits @@ -65,16 +47,6 @@ Layout/LineLength: Exclude: - 'config/routes.rb' -Metrics/MethodLength: - Description: Avoid methods longer than 15 lines of code. - StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods - Enabled: false - CountComments: false - Max: 15 - Exclude: - - 'db/migrate/*' - - spec/**/* - Metrics/ModuleLength: CountComments: false Max: 200 @@ -93,6 +65,9 @@ Naming/VariableName: Rails/FilePath: Enabled: false +Rails/ApplicationMailer: + Enabled: false + Rails/HttpPositionalArguments: Description: 'Use keyword arguments instead of positional arguments in http method calls.' Enabled: true @@ -150,13 +125,6 @@ Style/AndOr: - always - conditionals -Style/Documentation: - Description: Document classes and non-namespace modules. - Enabled: false - Exclude: - - 'spec/**/*' - - 'test/**/*' - Layout/DotPosition: Description: Checks the position of the dot in multi-line method calls. StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains @@ -165,9 +133,6 @@ Layout/DotPosition: - leading - trailing -Style/DoubleNegation: - Enabled: false - # Warn on empty else statements # empty - warn only on empty else # nil - warn on else with nil in it @@ -187,12 +152,6 @@ Layout/ExtraSpacing: # When true, forces the alignment of = in assignments on consecutive lines. ForceEqualSignAlignment: false -Style/FrozenStringLiteralComment: - Description: >- - Add the frozen_string_literal comment to the top of files - to help transition from Ruby 2.3.0 to Ruby 3.0. - Enabled: false - Style/IfUnlessModifier: Description: Favor modifier if/unless usage when you have a single-line body. StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier @@ -250,9 +209,6 @@ Style/StringLiterals: - double_quotes ConsistentQuotesInMultiline: true -Style/RegexpLiteral: - Enabled: false - Style/TrailingCommaInArguments: # If `comma`, the cop requires a comma after the last argument, but only for # parenthesized method calls where each argument is on its own line. @@ -289,42 +245,6 @@ Style/TrailingCommaInHashLiteral: Naming/MethodParameterName: MinNameLength: 2 -Style/ExpandPathArguments: - Enabled: false - -Style/FormatStringToken: - Enabled: false - -Style/SingleLineBlockParams: - Enabled: false - -Layout/EmptyLineAfterGuardClause: - Enabled: false - -Naming/MemoizedInstanceVariableName: - Enabled: false - -Naming/RescuedExceptionsVariableName: - Enabled: false - -Style/HashEachMethods: - Enabled: false - -Style/HashTransformKeys: - Enabled: false - -Style/HashTransformValues: - Enabled: false - -Lint/SuppressedException: - Enabled: false - -Lint/SendWithMixinArgument: - Enabled: false - -Layout/EmptyLinesAroundAttributeAccessor: - Enabled: false - Layout/SpaceAroundMethodCallOperator: Enabled: true @@ -337,24 +257,9 @@ Lint/MixedRegexpCaptureTypes: Lint/RaiseException: Enabled: true -Lint/StructNewOverride: - Enabled: false - -Style/ExponentialNotation: - Enabled: false - Style/RedundantRegexpCharacterClass: Enabled: true -Style/RedundantRegexpEscape: - Enabled: false - -Style/SlicingWithRange: - Enabled: false - -Rails/ApplicationMailer: - Enabled: false - Lint/BinaryOperatorWithIdenticalOperands: Enabled: true @@ -367,9 +272,6 @@ Lint/DuplicateRescueException: Lint/FloatComparison: Enabled: true -Lint/MissingSuper: - Enabled: false - Lint/OutOfRangeRegexpRef: Enabled: true @@ -388,9 +290,6 @@ Lint/SelfAssignment: Lint/TopLevelReturnWithArgument: Enabled: true -Style/GlobalStdStream: - Enabled: false - Style/RedundantAssignment: Enabled: true @@ -400,42 +299,6 @@ Style/RedundantFetchBlock: Style/RedundantFileExtensionInRequire: Enabled: true -Lint/EmptyConditionalBody: - Enabled: false - -Lint/UnreachableLoop: - Enabled: false - -Style/AccessorGrouping: - Enabled: false - -Style/ArrayCoercion: - Enabled: false - -Style/BisectedAttrAccessor: - Enabled: false - -Style/CaseLikeIf: - Enabled: false - -Style/ExplicitBlockArgument: - Enabled: false - -Style/HashAsLastArrayItem: - Enabled: false - -Style/HashLikeCase: - Enabled: false - -Style/OptionalBooleanParameter: - Enabled: false - -Style/SingleArgumentDig: - Enabled: false - -Style/StringConcatenation: - Enabled: false - Style/MultilineWhenThen: Enabled: true @@ -448,30 +311,12 @@ Lint/DuplicateRequire: Lint/TrailingCommaInAttributeDeclaration: Enabled: true -Lint/EmptyFile: - Enabled: false - -Lint/UselessMethodDefinition: - Enabled: false - -Style/CombinableLoops: - Enabled: false - -Style/RedundantSelfAssignment: - Enabled: false - -Style/SoleNestedConditional: - Enabled: false - Lint/UselessTimes: Enabled: true Layout/BeginEndAlignment: Enabled: true -Lint/ConstantDefinitionInBlock: - Enabled: false - Lint/IdentityComparison: Enabled: true @@ -480,3 +325,12 @@ Bundler/DuplicatedGem: Naming/BinaryOperatorParameterName: Enabled: true + +Lint/DuplicateRegexpCharacterClassElement: + Enabled: true + +Style/ArgumentsForwarding: + Enabled: true + +Style/NilLambda: + Enabled: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 160893ca0e7..8fd9ebfe4d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,13 @@ ## Welcome! -We're so glad you're thinking about contributing to an 18F open source project! -If you're unsure about anything, just ask — or submit the issue or pull request -anyway. The worst that can happen is you'll be politely asked to change -something. We love all friendly contributions. +We’re so glad you’re thinking about contributing to a Technology Transformation Services (TTS) open source project! If you’re unsure about anything, just ask — or submit your issue or pull request anyway. The worst that can happen is we’ll politely ask you to change something. We appreciate all friendly contributions. -We want to ensure a welcoming environment for all of our projects. Our staff -follow the [18F Code of Conduct][code] and all contributors should do the same. +TTS is committed to building a safe, welcoming, harassment-free culture for everyone. We expect everyone on the TTS team and everyone within TTS spaces, including contributors to our projects, to follow the [TTS Code of Conduct](https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md). -We encourage you to read this project's CONTRIBUTING policy (you are here), its -[LICENSE](LICENSE.md), and its [README](README.md). +We encourage you to read this project’s CONTRIBUTING policy (you are here), its [LICENSE](LICENSE.md), [README](README.md) -If you have any questions or want to read more, check out the -[18F Open Source Policy GitHub repository][os-policy], or just -[shoot us an email](mailto:18f@gsa.gov). +If you have any questions or want to read more, check out the [18F Open Source Policy GitHub repository]( https://github.com/18f/open-source-policy), or [send us an email](mailto:18f@gsa.gov). -[code]: https://github.com/18F/code-of-conduct/blob/master/code-of-conduct.md -[os-policy]: https://github.com/18f/open-source-policy ## Pull request guidelines diff --git a/Gemfile b/Gemfile index 57a2c5e831b..8867f7a926b 100644 --- a/Gemfile +++ b/Gemfile @@ -3,10 +3,11 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } ruby '~> 2.6.5' -gem 'rails', '~> 6.0.0' +gem 'rails', '~> 6.1.0' -gem 'ahoy_matey', '~> 2.2', '>= 2.2.1' +gem 'ahoy_matey', '~> 3.0' gem 'american_date' +gem 'autoprefixer-rails', '~> 10.0' gem 'aws-sdk-kms', '~> 1.4' gem 'aws-sdk-lambda' gem 'aws-sdk-ses', '~> 1.6' @@ -19,12 +20,12 @@ gem 'faraday' gem 'foundation_emails' gem 'hiredis' gem 'http_accept_language' -gem 'identity-doc-auth', github: '18F/identity-doc-auth', tag: 'v0.3.1' -gem 'identity-hostdata', github: '18F/identity-hostdata', tag: 'v0.4.1' +gem 'identity-doc-auth', github: '18F/identity-doc-auth', tag: 'v0.3.3' +gem 'identity-hostdata', github: '18F/identity-hostdata', tag: 'v0.4.2' require File.join(__dir__, 'lib', 'lambda_jobs', 'git_ref.rb') gem 'identity-idp-functions', github: '18F/identity-idp-functions', ref: LambdaJobs::GIT_REF gem 'identity-telephony', github: '18f/identity-telephony', tag: 'v0.1.7' -gem 'identity_validations', github: '18F/identity-validations', branch: 'master' +gem 'identity_validations', github: '18F/identity-validations', branch: 'main' gem 'json-jwt', '>= 1.11.0' gem 'jwt' gem 'local_time' @@ -36,8 +37,6 @@ gem 'pg' gem 'phonelib' gem 'premailer-rails', '>= 1.11.1' gem 'proofer', github: '18F/identity-proofer-gem', tag: 'v2.7.0' -gem 'pry-doc' -gem 'pry-rails' gem 'rack-attack', '>= 6.2.1' gem 'rack-cors', '>= 1.0.5', require: 'rack/cors' gem 'rack-headers_filter' @@ -46,7 +45,7 @@ gem 'raise-if-root' gem 'readthis' gem 'recaptcha', require: 'recaptcha/rails' gem 'redis-session-store', '>= 0.11.3' -gem 'rotp', '~> 3.3.1' +gem 'rotp', '~> 6.1' gem 'rqrcode' gem 'ruby-progressbar' gem 'ruby-saml' @@ -56,16 +55,14 @@ gem 'sassc-rails', '~> 2.1.2' gem 'scrypt' gem 'secure_headers', '~> 6.3' gem 'simple_form', '>= 5.0.2' -gem 'sinatra', '>= 2.0.7', require: false -gem 'slim-rails', '>= 3.2.0' gem 'stringex', require: false gem 'strong_migrations', '>= 0.4.2' gem 'subprocess', require: false -gem 'uglifier', '~> 3.2' +gem 'uglifier', '~> 4.2' gem 'user_agent_parser' gem 'valid_email', '>= 0.1.3' -gem 'webauthn', '~> 2.1.0' -gem 'webpacker', '~> 5.1.1' +gem 'webauthn', '~> 2.1' +gem 'webpacker', '~> 5.1' gem 'xmlenc', '~> 0.7', '>= 0.7.1' gem 'zxcvbn-js' @@ -73,9 +70,7 @@ group :development do gem 'better_errors', '>= 2.5.1' gem 'binding_of_caller' gem 'brakeman', require: false - gem 'bummr', require: false - gem 'derailed', '>= 0.1.0' - gem 'fasterer', require: false + gem 'derailed_benchmarks', '~> 1.8' gem 'guard-rspec', require: false gem 'irb' gem 'octokit' @@ -85,18 +80,20 @@ end group :development, :test do gem 'aws-sdk-cloudwatchlogs', require: false - gem 'bootsnap', '~> 1.4.7', require: false + gem 'bootsnap', '~> 1.5.0', require: false gem 'bullet', '>= 6.0.2' gem 'i18n-tasks', '>= 0.9.31' gem 'knapsack' + gem 'nokogiri', '~> 1.11.0' gem 'parallel_tests' gem 'pry-byebug' + gem 'pry-doc' + gem 'pry-rails' gem 'psych' gem 'puma' gem 'rspec-rails', '~> 4.0' - gem 'rubocop', '~> 0.91.0', require: false + gem 'rubocop', '~> 1.4.0', require: false gem 'rubocop-rails', '>= 2.5.2', require: false - gem 'slim_lint' end group :test do @@ -113,7 +110,7 @@ group :test do gem 'rails-controller-testing', '>= 1.0.4' gem 'shoulda-matchers', '~> 4.0', require: false gem 'timecop' - gem 'webdrivers', '~> 3.0' + gem 'webdrivers', '~> 4.0' gem 'webmock' gem 'zonebie' end diff --git a/Gemfile.lock b/Gemfile.lock index 45398dc465b..85dc45c9311 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,19 +12,19 @@ GIT GIT remote: https://github.com/18F/identity-doc-auth.git - revision: ac9f43820717343788ea6e0dfb9f75f8ec610f8d - tag: v0.3.1 + revision: 4e1e09d7e5eb673dfbc1e301feacc74859d25d29 + tag: v0.3.3 specs: - identity-doc-auth (0.3.1) + identity-doc-auth (0.3.3) activesupport faraday GIT remote: https://github.com/18F/identity-hostdata.git - revision: da013056e3a5ffcb46001a3f4fca21b80640838e - tag: v0.4.1 + revision: 305533690ab16a3fd24f19512061691e25e937ff + tag: v0.4.2 specs: - identity-hostdata (0.4.1) + identity-hostdata (0.4.2) aws-sdk-s3 (~> 1.8) GIT @@ -55,10 +55,10 @@ GIT GIT remote: https://github.com/18F/identity-validations.git - revision: 31d041f479d5479940b9c6c67a3ee37786ace68c - branch: master + revision: 26253af02f472d3023062efd5c7a3920b0db5f9c + branch: main specs: - identity_validations (0.3.1) + identity_validations (0.3.2) GIT remote: https://github.com/18F/saml_idp.git @@ -86,107 +86,109 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.0.3.4) - actionpack (= 6.0.3.4) + actioncable (6.1.0) + actionpack (= 6.1.0) + activesupport (= 6.1.0) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.4) - actionpack (= 6.0.3.4) - activejob (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) + actionmailbox (6.1.0) + actionpack (= 6.1.0) + activejob (= 6.1.0) + activerecord (= 6.1.0) + activestorage (= 6.1.0) + activesupport (= 6.1.0) mail (>= 2.7.1) - actionmailer (6.0.3.4) - actionpack (= 6.0.3.4) - actionview (= 6.0.3.4) - activejob (= 6.0.3.4) + actionmailer (6.1.0) + actionpack (= 6.1.0) + actionview (= 6.1.0) + activejob (= 6.1.0) + activesupport (= 6.1.0) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.4) - actionview (= 6.0.3.4) - activesupport (= 6.0.3.4) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.0) + actionview (= 6.1.0) + activesupport (= 6.1.0) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.4) - actionpack (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) + actiontext (6.1.0) + actionpack (= 6.1.0) + activerecord (= 6.1.0) + activestorage (= 6.1.0) + activesupport (= 6.1.0) nokogiri (>= 1.8.5) - actionview (6.0.3.4) - activesupport (= 6.0.3.4) + actionview (6.1.0) + activesupport (= 6.1.0) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.4) - activesupport (= 6.0.3.4) + activejob (6.1.0) + activesupport (= 6.1.0) globalid (>= 0.3.6) - activemodel (6.0.3.4) - activesupport (= 6.0.3.4) - activerecord (6.0.3.4) - activemodel (= 6.0.3.4) - activesupport (= 6.0.3.4) - activestorage (6.0.3.4) - actionpack (= 6.0.3.4) - activejob (= 6.0.3.4) - activerecord (= 6.0.3.4) + activemodel (6.1.0) + activesupport (= 6.1.0) + activerecord (6.1.0) + activemodel (= 6.1.0) + activesupport (= 6.1.0) + activestorage (6.1.0) + actionpack (= 6.1.0) + activejob (= 6.1.0) + activerecord (= 6.1.0) + activesupport (= 6.1.0) marcel (~> 0.3.1) - activesupport (6.0.3.4) + mimemagic (~> 0.3.2) + activesupport (6.1.0) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) - aes_key_wrap (1.0.1) - ahoy_matey (2.2.1) - addressable - browser (~> 2.0) + aes_key_wrap (1.1.0) + ahoy_matey (3.1.0) + activesupport (>= 5) device_detector geocoder (>= 1.4.5) - railties (>= 4.2) - referer-parser (>= 0.3) - request_store safely_block (>= 0.2.1) - user_agent_parser american_date (1.1.1) + android_key_attestation (0.3.0) ast (2.4.1) + autoprefixer-rails (10.1.0.0) + execjs awrence (1.1.1) aws-eventstream (1.1.0) - aws-partitions (1.388.0) + aws-partitions (1.411.0) aws-sdk-cloudwatchlogs (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.109.1) + aws-sdk-core (3.110.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.39.0) + aws-sdk-kms (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-lambda (1.42.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-lambda (1.57.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpoint (1.41.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-pinpoint (1.47.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpointsmsvoice (1.16.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-pinpointsmsvoice (1.21.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.83.1) + aws-sdk-s3 (1.87.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sdk-ses (1.27.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-ses (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ssm (1.98.0) + aws-sdk-ssm (1.100.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sigv4 (1.2.2) @@ -200,27 +202,23 @@ GEM thread_safe (~> 0.3, >= 0.3.1) base32-crockford (0.1.0) bcrypt (3.1.16) - benchmark-ips (2.8.2) - better_errors (2.8.3) + benchmark-ips (2.8.4) + better_errors (2.9.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - bindata (2.4.7) + bindata (2.4.8) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.8) + bootsnap (1.5.1) msgpack (~> 1.0) brakeman (4.10.0) - browser (2.7.1) builder (3.2.4) - bullet (6.1.0) + bullet (6.1.2) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - bummr (0.5.0) - rainbow - thor byebug (11.1.3) - capybara (3.33.0) + capybara (3.34.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -237,39 +235,36 @@ GEM cbor (0.5.9.6) childprocess (3.0.0) choice (0.2.0) - chunky_png (1.3.11) + chunky_png (1.3.15) codeclimate-test-reporter (1.0.9) simplecov (<= 0.13) coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) - colorize (0.8.1) concurrent-ruby (1.1.7) - connection_pool (2.2.2) - cose (0.10.0) + connection_pool (2.2.3) + cose (1.2.0) cbor (~> 0.5.9) + openssl-signature_algorithm (~> 1.0) crack (0.4.4) crass (1.0.6) css_parser (1.7.1) addressable debug_inspector (0.0.3) - derailed (0.1.0) - derailed_benchmarks - derailed_benchmarks (1.7.0) + derailed_benchmarks (1.8.1) benchmark-ips (~> 2) get_process_mem (~> 0) heapy (~> 0) memory_profiler (~> 0) - mini_histogram (~> 0) + mini_histogram (>= 0.2.1) rack (>= 1) rake (> 10, < 14) ruby-statistics (>= 2.1) thor (>= 0.19, < 2) - unicode_plot (>= 0.0.4, < 1.0.0) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - device_detector (1.0.4) - devise (4.7.2) + device_detector (1.0.5) + devise (4.7.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -286,7 +281,6 @@ GEM htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) - enumerable-statistics (2.0.1) equalizer (0.0.11) errbase (0.2.0) erubi (1.10.0) @@ -299,22 +293,19 @@ GEM factory_bot_rails (6.1.0) factory_bot (~> 6.1.0) railties (>= 5.0.0) - faker (2.14.0) + faker (2.15.1) i18n (>= 1.6, < 2) faraday (1.1.0) multipart-post (>= 1.2, < 3) ruby2_keywords - fasterer (0.8.0) - colorize (~> 0.7) - ruby_parser (>= 3.14.1) - ffi (1.13.1) + ffi (1.14.2) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake formatador (0.2.5) foundation_emails (2.2.1.0) - geocoder (1.6.3) - get_process_mem (0.2.5) + geocoder (1.6.4) + get_process_mem (0.2.7) ffi (~> 1.0) globalid (0.4.2) activesupport (>= 4.2.0) @@ -323,7 +314,7 @@ GEM mail (>= 2.2.1) gmail_xoauth (0.4.2) oauth (>= 0.3.6) - guard (2.16.1) + guard (2.16.2) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) lumberjack (>= 1.0.12, < 2.0) @@ -339,7 +330,8 @@ GEM rspec (>= 2.99.0, < 4.0) hashdiff (1.0.1) hashie (4.1.0) - heapy (0.1.4) + heapy (0.2.0) + thor highline (2.0.3) hiredis (0.6.3) htmlentities (4.3.4) @@ -349,7 +341,7 @@ GEM multi_xml (>= 0.5.2) i18n (1.8.5) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.31) + i18n-tasks (0.9.33) activesupport (>= 4.0.2) ast (>= 2.1.0) erubi @@ -361,20 +353,20 @@ GEM terminal-table (>= 1.5.1) ice_nine (0.11.2) io-console (0.5.6) - irb (1.2.7) + irb (1.2.9) reline (>= 0.1.5) jmespath (1.4.0) - json (2.3.0) - json-jwt (1.12.0) + json (2.4.1) + json-jwt (1.13.0) activesupport (>= 4.2) aes_key_wrap bindata - jwt (2.2.1) + jwt (2.2.2) knapsack (1.20.0) rake launchy (2.5.0) addressable (~> 2.7) - listen (3.2.0) + listen (3.3.3) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) local_time (2.1.0) @@ -383,10 +375,10 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.7.0) + loofah (2.8.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - lumberjack (1.0.13) + lumberjack (1.2.8) macaddr (1.7.2) systemu (~> 2.6.5) mail (2.7.1) @@ -398,25 +390,24 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) + mime-types-data (3.2020.1104) mimemagic (0.3.5) - mini_histogram (0.1.3) + mini_histogram (0.3.1) mini_mime (1.0.2) - mini_portile2 (2.4.0) + mini_portile2 (2.5.0) minitest (5.14.2) msgpack (1.3.3) multi_xml (0.6.0) multipart-post (2.1.1) - mustermann (1.1.1) - ruby2_keywords (~> 0.0.1) nenv (0.3.0) net-sftp (2.1.2) net-ssh (>= 2.6.5) net-ssh (5.2.0) - newrelic_rpm (6.12.0.367) + newrelic_rpm (6.14.0) nio4r (2.5.4) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) + nokogiri (1.11.0) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) @@ -424,17 +415,18 @@ GEM octokit (4.19.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - openssl (2.1.2) + openssl (2.2.0) + openssl-signature_algorithm (1.0.0) orm_adapter (0.5.0) - parallel (1.19.2) + parallel (1.20.1) parallel_tests (2.29.2) parallel - parser (2.7.1.4) + parser (2.7.2.0) ast (~> 2.4.1) - pg (1.1.4) - phonelib (0.6.46) - pkcs11 (0.3.2) - premailer (1.13.1) + pg (1.2.3) + phonelib (0.6.47) + pkcs11 (0.3.3) + premailer (1.14.2) addressable css_parser (>= 1.6.0) htmlentities (>= 4.0.0) @@ -447,15 +439,16 @@ GEM pry-byebug (3.9.0) byebug (~> 11.0) pry (~> 0.13.0) - pry-doc (1.0.0) + pry-doc (1.1.0) pry (~> 0.11) yard (~> 0.9.11) pry-rails (0.3.9) pry (>= 0.10.4) - psych (3.2.0) + psych (3.2.1) public_suffix (4.0.6) - puma (5.0.3) + puma (5.1.1) nio4r (~> 2.0) + racc (1.5.2) rack (2.2.3) rack-attack (6.3.1) rack (>= 1.0, < 3) @@ -464,30 +457,28 @@ GEM rack-headers_filter (0.0.1) rack-mini-profiler (2.2.0) rack (>= 1.2.0) - rack-protection (2.0.8.1) - rack rack-proxy (0.6.5) rack rack-test (1.1.0) rack (>= 1.0, < 3) - rack-timeout (0.5.2) + rack-timeout (0.6.0) rack_session_access (0.2.0) builder (>= 2.0.0) rack (>= 1.0.0) - rails (6.0.3.4) - actioncable (= 6.0.3.4) - actionmailbox (= 6.0.3.4) - actionmailer (= 6.0.3.4) - actionpack (= 6.0.3.4) - actiontext (= 6.0.3.4) - actionview (= 6.0.3.4) - activejob (= 6.0.3.4) - activemodel (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) - bundler (>= 1.3.0) - railties (= 6.0.3.4) + rails (6.1.0) + actioncable (= 6.1.0) + actionmailbox (= 6.1.0) + actionmailer (= 6.1.0) + actionpack (= 6.1.0) + actiontext (= 6.1.0) + actionview (= 6.1.0) + activejob (= 6.1.0) + activemodel (= 6.1.0) + activerecord (= 6.1.0) + activestorage (= 6.1.0) + activesupport (= 6.1.0) + bundler (>= 1.15.0) + railties (= 6.1.0) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -506,30 +497,29 @@ GEM rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) - railties (6.0.3.4) - actionpack (= 6.0.3.4) - activesupport (= 6.0.3.4) + railties (6.1.0) + actionpack (= 6.1.0) + activesupport (= 6.1.0) method_source rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + thor (~> 1.0) rainbow (3.0.0) raise-if-root (0.0.2) - rake (13.0.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rake (13.0.3) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) ffi (~> 1.0) readthis (2.2.0) connection_pool (~> 2.1) redis (>= 3.0, < 5.0) recaptcha (5.2.1) json - redis (4.2.2) + redis (4.2.5) redis-session-store (0.11.3) actionpack (>= 3, < 7) redis (>= 3, < 5) - referer-parser (0.3.0) regexp_parser (1.8.2) - reline (0.1.5) + reline (0.1.10) io-console (~> 0.5) request_store (1.5.0) rack (>= 1.4) @@ -538,23 +528,23 @@ GEM railties (>= 5.0) retries (0.0.5) rexml (3.2.4) - rotp (3.3.1) + rotp (6.2.0) rqrcode (1.1.2) chunky_png (~> 1.0) rqrcode_core (~> 0.1) - rqrcode_core (0.1.1) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.4) + rqrcode_core (0.1.2) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.0) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) + rspec-support (~> 3.10.0) rspec-rails (4.0.1) actionpack (>= 4.2) activesupport (>= 4.2) @@ -563,18 +553,18 @@ GEM rspec-expectations (~> 3.9) rspec-mocks (~> 3.9) rspec-support (~> 3.9) - rspec-support (3.9.4) - rubocop (0.91.0) + rspec-support (3.10.0) + rubocop (1.4.2) parallel (~> 1.10) - parser (>= 2.7.1.1) + parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.7) + regexp_parser (>= 1.8) rexml - rubocop-ast (>= 0.4.0, < 1.0) + rubocop-ast (>= 1.1.1) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.4.1) - parser (>= 2.7.1.4) + rubocop-ast (1.3.0) + parser (>= 2.7.1.5) rubocop-rails (2.5.2) activesupport rack (>= 1.1) @@ -586,13 +576,13 @@ GEM nokogiri (>= 1.5.10) ruby-statistics (2.1.2) ruby2_keywords (0.0.2) - ruby_parser (3.14.1) - sexp_processor (~> 4.9) rubyzip (1.3.0) safe_target_blank (1.0.2) rails safely_block (0.3.0) errbase (>= 0.1.1) + safety_net_attestation (0.4.0) + jwt (~> 2.0) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -604,15 +594,14 @@ GEM sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) - scrypt (3.0.6) + scrypt (3.0.7) ffi-compiler (>= 1.0, < 2.0) - secure_headers (6.3.0) + secure_headers (6.3.1) securecompare (1.0.0) selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - semantic_range (2.3.0) - sexp_processor (4.13.0) + semantic_range (2.3.1) shellany (0.0.1) shoulda-matchers (4.4.1) activesupport (>= 4.2.0) @@ -624,22 +613,6 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) - sinatra (2.0.8.1) - mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.8.1) - tilt (~> 2.0) - slim (4.1.0) - temple (>= 0.7.6, < 0.9) - tilt (>= 2.0.6, < 2.1) - slim-rails (3.2.0) - actionpack (>= 3.1) - railties (>= 3.1) - slim (>= 3.0, < 5.0) - slim_lint (0.18.0) - rubocop (>= 0.50.0) - slim (>= 3.0, < 5.0) - sysexits (~> 1.1) sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -648,25 +621,24 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) stringex (2.8.5) - strong_migrations (0.7.2) + strong_migrations (0.7.4) activerecord (>= 5) - subprocess (1.3.2) - sysexits (1.2.0) + subprocess (1.5.3) systemu (2.6.5) - temple (0.8.2) - terminal-table (1.8.0) + terminal-table (2.0.0) unicode-display_width (~> 1.1, >= 1.1.1) thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) timecop (0.9.2) - tzinfo (1.2.8) - thread_safe (~> 0.1) - uglifier (3.2.0) + tpm-key_attestation (0.10.0) + bindata (~> 2.4) + openssl-signature_algorithm (~> 1.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) unicode-display_width (1.7.0) - unicode_plot (0.0.4) - enumerable-statistics (>= 2.0.1) uniform_notifier (1.13.0) user_agent_parser (2.7.0) uuid (2.3.9) @@ -681,23 +653,25 @@ GEM equalizer (~> 0.0, >= 0.0.9) warden (1.2.9) rack (>= 2.0.9) - webauthn (2.1.0) + webauthn (2.4.0) + android_key_attestation (~> 0.3.0) awrence (~> 1.1) bindata (~> 2.4) cbor (~> 0.5.9) - cose (~> 0.10.0) - jwt (>= 1.5, < 3.0) + cose (~> 1.1) openssl (~> 2.0) + safety_net_attestation (~> 0.4.0) securecompare (~> 1.0) - webdrivers (3.9.4) + tpm-key_attestation (~> 0.10.0) + webdrivers (4.4.1) nokogiri (~> 1.6) - rubyzip (~> 1.0) - selenium-webdriver (~> 3.0) - webmock (3.9.3) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) + webmock (3.11.0) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.1.1) + webpacker (5.2.1) activesupport (>= 5.2) rack-proxy (>= 0.6.1) railties (>= 5.2) @@ -716,8 +690,8 @@ GEM nokogiri (~> 1.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.20) - zeitwerk (2.4.1) + yard (0.9.25) + zeitwerk (2.4.2) zonebie (0.6.1) zxcvbn-js (4.4.3) execjs @@ -727,8 +701,9 @@ PLATFORMS DEPENDENCIES aamva! - ahoy_matey (~> 2.2, >= 2.2.1) + ahoy_matey (~> 3.0) american_date + autoprefixer-rails (~> 10.0) aws-sdk-cloudwatchlogs aws-sdk-kms (~> 1.4) aws-sdk-lambda @@ -737,14 +712,13 @@ DEPENDENCIES base32-crockford better_errors (>= 2.5.1) binding_of_caller - bootsnap (~> 1.4.7) + bootsnap (~> 1.5.0) brakeman bullet (>= 6.0.2) - bummr capybara-screenshot (>= 1.0.23) capybara-selenium (>= 0.0.6) codeclimate-test-reporter - derailed (>= 0.1.0) + derailed_benchmarks (~> 1.8) device_detector devise (~> 4.7.2) dotiw (>= 4.0.1) @@ -753,7 +727,6 @@ DEPENDENCIES factory_bot_rails (>= 5.2.0) faker faraday - fasterer foundation_emails gmail guard-rspec @@ -775,6 +748,7 @@ DEPENDENCIES maxminddb net-sftp newrelic_rpm + nokogiri (~> 1.11.0) octokit parallel_tests pg @@ -793,17 +767,17 @@ DEPENDENCIES rack-test (>= 1.1.0) rack-timeout rack_session_access (>= 0.2.0) - rails (~> 6.0.0) + rails (~> 6.1.0) rails-controller-testing (>= 1.0.4) rails-erd (>= 1.6.0) raise-if-root readthis recaptcha redis-session-store (>= 0.11.3) - rotp (~> 3.3.1) + rotp (~> 6.1) rqrcode rspec-rails (~> 4.0) - rubocop (~> 0.91.0) + rubocop (~> 1.4.0) rubocop-rails (>= 2.5.2) ruby-progressbar ruby-saml @@ -814,20 +788,17 @@ DEPENDENCIES secure_headers (~> 6.3) shoulda-matchers (~> 4.0) simple_form (>= 5.0.2) - sinatra (>= 2.0.7) - slim-rails (>= 3.2.0) - slim_lint stringex strong_migrations (>= 0.4.2) subprocess timecop - uglifier (~> 3.2) + uglifier (~> 4.2) user_agent_parser valid_email (>= 0.1.3) - webauthn (~> 2.1.0) - webdrivers (~> 3.0) + webauthn (~> 2.1) + webdrivers (~> 4.0) webmock - webpacker (~> 5.1.1) + webpacker (~> 5.1) xmlenc (~> 0.7, >= 0.7.1) zonebie zxcvbn-js diff --git a/Guardfile b/Guardfile index 8e882c9ac0a..8586edb9f26 100644 --- a/Guardfile +++ b/Guardfile @@ -40,7 +40,7 @@ guard :rspec, cmd: ENV['GUARD_RSPEC_CMD'] || 'bundle exec rspec' do dsl.watch_spec_files_for(ruby.lib_files) # Rails files - rails = dsl.rails(view_extensions: %w[erb haml slim]) + rails = dsl.rails(view_extensions: %w[erb]) dsl.watch_spec_files_for(rails.app_files) dsl.watch_spec_files_for(rails.views) diff --git a/Makefile b/Makefile index 3b80ed34825..677caf5995e 100644 --- a/Makefile +++ b/Makefile @@ -23,8 +23,6 @@ check: lint test lint: @echo "--- rubocop ---" bundle exec rubocop - @echo "--- slim-lint ---" - bundle exec slim-lint app/views @echo "--- fasterer ---" bundle exec fasterer @echo "--- eslint ---" diff --git a/README.md b/README.md index 9f28b384c08..0fe281ffefb 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,40 @@ -Identity-IdP (Upaya) -==================== +Login.gov Identity Provider (IdP) +================================= [![Build Status](https://circleci.com/gh/18F/identity-idp.svg?style=svg)](https://circleci.com/gh/18F/identity-idp) [![Code Climate](https://api.codeclimate.com/v1/badges/e78d453f7cbcac64a664/maintainability)](https://codeclimate.com/github/18F/identity-idp/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/e78d453f7cbcac64a664/test_coverage)](https://codeclimate.com/github/18F/identity-idp/test_coverage) [![security](https://hakiri.io/github/18F/identity-idp/master.svg)](https://hakiri.io/github/18F/identity-idp/master) -A Identity Management System powering login.gov. +Login.gov is the public's one account for government. Use one account and password for secure, private access to participating government agencies. -### Local development +This repository contains the core code base and documentation for the identity management system powering secure.login.gov. -#### Dependencies +## Contributing to this code base -- Ruby 2.6 -- [Postgresql](http://www.postgresql.org/download/) -- [Redis 2.8+](http://redis.io/) -- [Node.js v14.x.x](https://nodejs.org) -- [Yarn](https://yarnpkg.com/en/) +Thank you for your interest in contributing to the login.gov IdP! For complete instructions on how to contribute code, please read through our [CONTRIBUTING.md](CONTRIBUTING.md) documentation. -#### Running the app with Docker +## Creating your local development environment -See the [Docker documentation](./docs/Docker.md) to get up and running +There are two methods that we recommend for installing for local development. One is container based with Docker, while the other is focused on your local machine's installation (advanced). For those who are not familiar with setting up local environments on your machine, we recommend [installing with Docker](#installing-with-docker). -#### Setting up and running the app without Docker +### Installing on your local machine -1. Make sure you have a working development environment with all the - [dependencies](#dependencies) installed. On OS X, the easiest way - to set up a development environment is by running our [Laptop] - script. The script will install all of this project's dependencies. +This installation method is meant for those who are familiar with setting up local development environments on their machines. - If using rbenv, you may need to alias your specific installed ruby version to the more generic version found in the `.ruby-version` file. To do this, use [`rbenv-aliases`](https://github.com/tpope/rbenv-aliases): +1. To start, make sure you have the following dependencies installed and a working development environment: - ``` - git clone git://github.com/tpope/rbenv-aliases.git "$(rbenv root)/plugins/rbenv-aliases" # install rbenv-aliases per its documentation +- Ruby 2.6 +- [PostgreSQL](http://www.postgresql.org/download/) +- [Redis 5+](http://redis.io/) +- [Node.js v14.x.x](https://nodejs.org) +- [Yarn](https://yarnpkg.com/en/) - rbenv alias 2.6 2.6.5 # create the version alias - ``` +We recommend using [Homebrew](https://brew.sh/), [rbenv](https://github.com/rbenv/rbenv), [nvm](https://github.com/nvm-sh/nvm) or other version management tooling when installing your dependencies. While we don't anticipate changing these frequently, this will ensure that you will be able to easily switch to different versions as needed. -2. Make sure Postgres and Redis are running. +2. Test that you have Postgres and Redis running. - For example, if you've installed the laptop script on OS X, you can start the services like this: + For example, if you've installed with Homebrew, you can start the services like this: ``` $ brew services start redis @@ -53,274 +48,121 @@ See the [Docker documentation](./docs/Docker.md) to get up and running $ psql -c "CREATE DATABASE upaya_test;" ``` -4. Run the following command to set up the environment: + Q: Why "Upaya"? + + A: "skill in means" https://en.wikipedia.org/wiki/Upaya + +4. Run the following command to set up your local environment: ``` $ make setup ``` This command copies sample configuration files, installs required gems - and sets up the database. + and sets up the database. Check out our Makefile commands to learn more about what this command does: https://github.com/18F/identity-idp/blob/master/Makefile -5. Run the app server with: +5. Now that you have you have everything installed, you can run the following command to start your local server: ``` $ make run ``` + You should now be able to go to open up your favorite browser, go to `localhost:3000` and see your local development environment running. -If you want to develop without an internet connection, you can set -`RAILS_OFFLINE=1` in your environment. This disables the `mx` record -check on email addresses. - -If you want to measure the app's performance in development, set the -`rack_mini_profiler` option to `'on'` in `config/application.yml` and -restart the server. See the [rack_mini_profiler] gem for more details. - -[Laptop]: https://github.com/18F/laptop -[rack_mini_profiler]: https://github.com/MiniProfiler/rack-mini-profiler +#### Running tests locally -#### Testing Analytics + Login.gov uses the following tools for our testing: -If you want to visualize and query the event and log data, you can install -the latest versions of Elasticsearch, Logstash, and Kibana. -On OS X, the easiest way is with Homebrew: + - [RSpec](https://relishapp.com/rspec/rspec-core/docs/command-line) + - [Guard](https://github.com/guard/guard-rspec) + - [Mocha documentation](https://mochajs.org/) -``` -brew tap homebrew/services + To run our full test suite locally, use the following command: -brew install elasticsearch logstash kibana - -brew services start elasticsearch -brew services start kibana -``` + ``` + $ make test + ``` -Start logstash by running this command from this repo's root directory: -``` -logstash -f logstash.conf -``` + Use the following command to run a subset of our test suite, excluding slower tests: -When you trigger an event in the app (such as signing in), you should see some -output in the logstash window. + ``` + $ make fast_test + ``` -To explore the data with Kibana, visit http://localhost:5601 + Check out our Makefile commands learn more about how you can customize this command to run specific tests using rspec: https://github.com/18F/identity-idp/blob/master/Makefile#L41 -##### Troubleshooting Kibana errors -Below are some common errors: +##### Running smoke tests -- On the Kibana website: "Your Kibana index is out of date, reset it or use the -X-Pack upgrade assistant." + The smoke tests are a series of RSpec tests designed to run against deployed environments. To run them against the local Rails server: -- In the logstash output: - ``` - Failed to parse mapping [_default_]: [include_in_all] is not allowed for - indices created on or after version 6.0.0 as [_all] is deprecated. As a - replacement, you can use an [copy_to] on mapping fields to create your own - catch all field. + ```bash + ./bin/smoke_test --local ``` -Solution, assuming you don't use these services for other apps and are OK with -deleting existing data: + To run the smoke tests against a deployed server, make sure you set up a `.env` file with the right configuration values, see [monitor_config.rb](spec/support/monitor/monitor_config.rb) for the full list of environment variables used. The script below will `source` that file and add the variables to the environment. -1. Stop all services: - - Press `ctrl-c` to stop logstash if it's running - ```console - brew services stop elasticsearch - brew services stop kibana + ```bash + MONITOR_ENV=INT ./bin/smoke_test --remote ``` -2. Uninstall everything: - ```console - brew uninstall --force elasticsearch - brew uninstall --force logstash - brew uninstall --force kibana - ``` -3. Reinstall everything: - ```console - brew install elasticsearch logstash kibana - ``` - -4. Start the services: - ```console - brew services start elasticsearch - brew services start kibana - ``` +#### Speeding up local development and testing -5. Delete the old Kibana index: + To automatically run the test that corresponds to the file you are editing, + run `bundle exec guard` with the env var `GUARD_RSPEC_CMD` set to your preferred + command for running `rspec`. For example, if you use [Zeus](https://github.com/burke/zeus), + you would set the env var to `zeus rspec`: ```console - curl -XDELETE http://localhost:9200/.kibana + GUARD_RSPEC_CMD="zeus rspec" bundle exec guard ``` -6. Delete the old logstash template: - - Visit http://localhost:5601/app/kibana#/dev_tools/console?_g=() - - Paste `DELETE /_template/logstash` in the box on the left and click - the green "play" button to run the command + If you don't specify the `GUARD_RSPEC_CMD` env var, it will default to + `bundle exec rspec`. -7. Start logstash in a new Terminal tab: + We also recommend setting up a shell alias for running this command, such as: ```console - logstash -f logstash.conf + alias idpguard='GUARD_RSPEC_CMD="zeus rspec" bundle exec guard' ``` -8. Launch the IdP app and sign in to generate some events. You should see output -in the logstash tab without any errors. - -9. Visit http://localhost:5601/ and click "Discover" on the left sidebar. If you -get a warning that no default index pattern exists, copy the last pattern that -appears in the list, which will have the format `logstash-year.month.day`. Paste -it into the "Index pattern" field, then click the "Next step" button. - -10. On `Step 2 of 2: Configure settings`, select `@timestamp` from the -`Time Filter field name` dropdown, then click "Create index pattern". - -11. Create some more events on the IdP app. - -12. Refresh the Kibana website. You should now see new events show up in the -Discover section. - -### Viewing the app locally - -Once it is up and running, the app will be accessible at -`http://localhost:3000/` by default. - -To view email messages, Mailcatcher must be running. You can check if it's -running by visiting http://localhost:1080/. To run Mailcatcher: - -``` -$ mailcatcher -``` - -If you would like to run the application on a different port: +#### Viewing email messages -* Change the port number for `mailer_domain_name` and `domain_name` in `config/application.yml` -* Run the app on your desired port like `make run PORT=1234` + Login.gov uses a tool called [Mailcatcher](https://github.com/sj26/mailcatcher) to view email messages locally. When Mailcatcher is running, visit http://localhost:1080/ to see them. -If you would like to see the Spanish translations on a particular page, add -`?locale=es` to the end of the URL, such as `http://localhost:3000/?locale=es`. -Currently, you'll need to add `?locale=es` to each URL manually. We are working -on a more robust and user-friendly way to switch between locales. + We spin up a Mailcatcher process by default through `make run`, but if you want to run Mailcatcher as a standalone process, just run: -To see outbound SMS messages and phone calls, visit `http://localhost:3000/test/telephony`. - -### Running Tests - -To run all the tests: - -``` -$ make test -``` - -To run a subset of tests excluding slow tests (such as accessibility specs): -``` -$ make fast_test -``` - -#### Smoke Tests - -The smoke tests are a series of RSpec tests designed to run against deployed environments. To run them against the local Rails server: - -```bash -./bin/smoke_test --local -``` - -To run the smoke tests against a deployed server, make sure you set up a `.env` file with the right configuration values, see [monitor_config.rb](spec/support/monitor/monitor_config.rb) for the full list of environment variables used. The script below will `source` that file and add the variables to the environment. - -```bash -MONITOR_ENV=INT ./bin/smoke_test --remote -``` + ``` + $ mailcatcher + ``` -#### Speeding up local development and testing -To automatically run the test that corresponds to the file you are editing, -run `bundle exec guard` with the env var `GUARD_RSPEC_CMD` set to your preferred -command for running `rspec`. For example, if you use [Zeus](https://github.com/burke/zeus), -you would set the env var to `zeus rspec`: -```console -GUARD_RSPEC_CMD="zeus rspec" bundle exec guard -``` + If you would like to run the application on a different port: -If you don't specify the `GUARD_RSPEC_CMD` env var, it will default to -`bundle exec rspec`. + * Change the port number for `mailer_domain_name` and `domain_name` in `config/application.yml` + * Run the app on your desired port like `make run PORT=1234` -We recommend setting up a shell alias for running this command, such as: -```console -alias idpguard='GUARD_RSPEC_CMD="zeus rspec" bundle exec guard' -``` +#### Translations -#### Troubleshooting -If you are on a mac, if you receive the following prompt the first time you run the test suite, enter `sekret` as the passphrase: + Login.gov translates the IdP into English, French and Spanish. To help us handle extra newlines and make sure we wrap lines consistently, we have a script called `./scripts/normalize-yaml` that helps format YAML consistently. After importing translations (or making changes to the *.yml files with strings, run this for the IdP app: -![alt text][mac-test-passphrase-prompt] + ``` + $ make normalize_yaml + ``` -#### Documentation for the testing tools we use -[RSpec](https://relishapp.com/rspec/rspec-core/docs/command-line) + If you would like to preview the translations on a particular page, use the Language dropdown in the footer of the website. To manually override a locale, add the locale as the first segment of the URL: + - http://localhost:3000 becomes http://localhost:3000/es + - http://localhost:3000/sign_up/enter_email becomes http://localhost:3000/es/sign_up/enter_email -[Guard](https://github.com/guard/guard-rspec) +#### Viewing outbound SMS messages and phone calls -JavaScript unit tests run using the mocha test runner. Check out the -[mocha documentation](https://mochajs.org/) for more details. + To see outbound SMS messages and phone calls, visit `http://localhost:3000/test/telephony`. -### Setting up Geolocation +#### Setting up Geolocation -The app uses MaxMind Geolite2 for geolocation. -To test geolocation locally you will need to add a copy of the Geolite2-City database to the IdP. +Login.gov uses MaxMind Geolite2 for geolocation. To test geolocation locally, you will need to add a copy of the Geolite2-City database to the IdP. The Geolite2-City database can be downloaded from MaxMind's site at [https://dev.maxmind.com/geoip/geoip2/geolite2/](https://dev.maxmind.com/geoip/geoip2/geolite2/). Download the GeoIP2 Binary and save it at `geo_data/GeoLite2-City.mmdb`. The app will start using that Geolite2 file for geolocation after restart. -### User flows - -We have an automated tool for generating user flows using real views generated from the application. These specs are excluded from our typical spec run because of the overhead of generating screenshots for each view. - -The local instance of the application must be running in order to serve up the assets (eg. `make run`). Then, you can specify where the assets are hosted from and generate the views with: - -``` -$ RAILS_ASSET_HOST=localhost:3000 rake spec:user_flows -``` - -Then, visit http://localhost:3000/user_flows in your browser! - -##### Exporting - -The user flows tool also has an export feature which allows you to export everything for the web. You may host these assets with someting like [`simplehttpserver`](https://www.npmjs.com/package/simplehttpserver) or publish to [Federalist](https://federalist.18f.gov/). To publish user flows for Federalist, first make sure the application is running locally (eg. localhost:3000) and run: - -``` -$ RAILS_ASSET_HOST=localhost:3000 FEDERALIST_PATH=/site/user/repository rake spec:user_flows:web -``` - -This will output your site to `public/site/user/repository` for quick publishing to [Federalist](https://federalist-docs.18f.gov/pages/using-federalist/). To test compatibility, run `simplehttpserver` from the app's `public` folder and visit `http://localhost:8000//user_flows` in your browser. - -[laptop script]: https://github.com/18F/laptop - -### Proofing vendors - -Some proofing vendor code is located in private Github repositories because of NDAs. You can still use it -in your local development environment if you have access to the private repository. - -Example: - -#### Check out the private repository for `somevendorname` - -``` -$ cd vendor -$ git clone git@github.com:18F/identity-somevendorname-api-client-gem.git somevendorname -``` - -#### Add the vendor configuration - -Add appropriate vendor environment variables to `config/application.yml` -- see a member of the -login.gov team for credentials and other values. - -### Why 'Upaya'? - -"skill in means" https://en.wikipedia.org/wiki/Upaya - -### Managing translation files - -To help us handle extra newlines and make sure we wrap lines consistently, we have a script called `./scripts/normalize-yaml` that helps format YAML consistently. After importing translations (or making changes to the *.yml files with strings, run this for the IDP app: - -``` -$ make normalize_yaml -``` +### Installing with Docker -[mac-test-passphrase-prompt]: mac-test-passphrase-prompt.png "Mac Test Passphrase Prompt" +See the [Docker documentation](./docs/Docker.md) to install the IdP as a container. diff --git a/app/assets/images/alert/ico-thumb.svg b/app/assets/images/alert/ico-thumb.svg deleted file mode 100644 index 67a8fde68af..00000000000 --- a/app/assets/images/alert/ico-thumb.svg +++ /dev/null @@ -1 +0,0 @@ -ico-thumb \ No newline at end of file diff --git a/app/assets/images/alert/notice.svg b/app/assets/images/alert/notice.svg deleted file mode 100644 index 79a9e96d604..00000000000 --- a/app/assets/images/alert/notice.svg +++ /dev/null @@ -1 +0,0 @@ -warning diff --git a/app/assets/images/state-id-back.svg b/app/assets/images/state-id-back.svg deleted file mode 100644 index dbc921d16da..00000000000 --- a/app/assets/images/state-id-back.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/assets/images/state-id-front.svg b/app/assets/images/state-id-front.svg deleted file mode 100644 index e47cc5f4a90..00000000000 --- a/app/assets/images/state-id-front.svg +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/assets/images/state-id-sample-back.jpg b/app/assets/images/state-id-sample-back.jpg deleted file mode 100644 index 7c1be5146bf..00000000000 Binary files a/app/assets/images/state-id-sample-back.jpg and /dev/null differ diff --git a/app/assets/images/state-id-sample-front.jpg b/app/assets/images/state-id-sample-front.jpg deleted file mode 100644 index 62d0cb0e250..00000000000 Binary files a/app/assets/images/state-id-sample-front.jpg and /dev/null differ diff --git a/app/assets/images/tooltip.svg b/app/assets/images/tooltip.svg deleted file mode 100644 index 775ce7c851e..00000000000 --- a/app/assets/images/tooltip.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - Tooltip LG - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/app/assets/stylesheets/_vendor.scss b/app/assets/stylesheets/_vendor.scss index db0dc700ca9..699816a810e 100644 --- a/app/assets/stylesheets/_vendor.scss +++ b/app/assets/stylesheets/_vendor.scss @@ -1,6 +1,3 @@ -@import 'normalize.css/normalize'; -@import 'hint.css/hint'; - @import 'basscss-sass/defaults'; @import 'basscss-sass/base-reset'; @import 'basscss-sass/base-forms'; @@ -15,7 +12,6 @@ @import 'basscss-sass/type-scale'; @import 'basscss-sass/utility-typography'; @import 'basscss-sass/utility-layout'; -@import 'basscss-sass/align'; @import 'basscss-sass/white-space'; @import 'basscss-sass/positions'; @import 'basscss-sass/responsive-states'; diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index b3d99763212..8576adb3165 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -4,11 +4,3 @@ @import 'identity-style-guide/dist/assets/scss/styles'; @import 'components/all'; @import 'print'; - -// This import is temporary and scoped to the new Document Capture screen. The File Input component -// is part of a USWDS version newer than what's currently shipped with Login Design System. Follow- -// up work should upgrade to use a newer USWDS, at which time this should be removed altogether. -#document-capture-form { // scss-lint:disable IdSelector - @import 'uswds/dist/scss/elements/form-controls/file-input'; - @import 'components/file-input'; -} diff --git a/app/assets/stylesheets/components/_alert.scss b/app/assets/stylesheets/components/_alert.scss deleted file mode 100644 index 2adf337a7f8..00000000000 --- a/app/assets/stylesheets/components/_alert.scss +++ /dev/null @@ -1,78 +0,0 @@ -$ico-size: 1rem; -$ico-offset: 1rem; - -.alert { - background-color: $blue-lighter; - border-radius: $space-1; - color: #5b616a; - font-size: 1rem; - line-height: 1.5rem; - margin-bottom: $space-4; - padding: 12px $space-2; - position: relative; - - &::before { - background-image: none; - background-repeat: no-repeat; - content: ''; - height: $ico-size; - left: $ico-offset; - position: absolute; - top: $ico-offset; - width: $ico-size; - } -} - -.alert-inline { - background-color: #fffdd7; - color: #5b616a; - font-size: 1rem; - line-height: 1.5rem; - padding: 12px $space-2 12px $space-5; - position: relative; - - &::before { - background-image: url(image-path('alert/warning.svg')); - background-repeat: no-repeat; - content: ''; - height: $ico-size; - left: 2rem; - position: absolute; - top: $ico-offset; - width: $ico-size; - } -} - -.alert-success { - background-color: #ebfcef; - padding-left: $space-4; - - &::before { background-image: url(image-path('alert/success.svg')); } -} - -.alert-thumb { - background-color: #ebfcef; - padding-left: $space-4; - - &::before { background-image: url(image-path('alert/ico-thumb.svg')); } -} - -.alert-error, -.alert-alert { - background-color: #fff0f3; - padding-left: $space-4; - - &::before { background-image: url(image-path('alert/error.svg')); } -} - -.alert-warning { - background-color: #fffdd7; - padding-left: $space-4; - - &::before { background-image: url(image-path('alert/warning.svg')); } -} - -.alert-notice { - padding-left: $space-4; - &::before { background-image: url(image-path('alert/notice.svg')); } -} diff --git a/app/assets/stylesheets/components/_banner.scss b/app/assets/stylesheets/components/_banner.scss index da9b4e9f0ca..a6a6fbfb0f0 100644 --- a/app/assets/stylesheets/components/_banner.scss +++ b/app/assets/stylesheets/components/_banner.scss @@ -1,34 +1,14 @@ -.site .site-wrap .usa-banner { - background-color: #fff; - box-shadow: inset 0 -1px 0 0 #ccc; -} - -.usa-banner__inner { - padding-left: 0; - padding-right: 0; +.usa-banner, +.usa-banner__button[aria-expanded='true']::before { + background-color: color('white'); } -.usa-banner__header { - display: flex; - flex-direction: column; - justify-content: center; - - + .usa-banner__content.usa-accordion__content { - position: relative; - } -} - -@media #{$mobile} { - .usa-banner__header--expanded .usa-banner__inner { - margin-left: auto; - } +.usa-banner { + box-shadow: inset 0 -1px 0 0 #ccc; } -.usa-banner__button[aria-expanded="true"]::after { - @include at-media-max('tablet') { - background-color: transparent; - right: 0; - top: 100%; - z-index: 10; +.usa-banner__inner { + @include at-media('tablet') { + justify-content: center; } } diff --git a/app/assets/stylesheets/components/_external-link.scss b/app/assets/stylesheets/components/_external-link.scss index b0be329f2fd..ef15386b09d 100644 --- a/app/assets/stylesheets/components/_external-link.scss +++ b/app/assets/stylesheets/components/_external-link.scss @@ -8,4 +8,9 @@ &.usa-link--alt { @include external-link(external-link-alt, external-link-alt); } + + &::after { + // The icon is displayed inline, which has no implicit height when included in a flex context. + align-self: stretch; + } } diff --git a/app/assets/stylesheets/components/_file-input.scss b/app/assets/stylesheets/components/_file-input.scss index fc130e6ee10..4f8403aca0d 100644 --- a/app/assets/stylesheets/components/_file-input.scss +++ b/app/assets/stylesheets/components/_file-input.scss @@ -67,7 +67,7 @@ padding: 0; } - .usa-file-input__preview__image { + .usa-file-input__preview-image { height: auto; margin-left: auto; margin-right: 0; diff --git a/app/assets/stylesheets/components/_spinner-button.scss b/app/assets/stylesheets/components/_spinner-button.scss new file mode 100644 index 00000000000..cad1be060b1 --- /dev/null +++ b/app/assets/stylesheets/components/_spinner-button.scss @@ -0,0 +1,90 @@ +@keyframes spinner-button-dot-one { + 0% { transform: scale(0); } + 25% { transform: scale(1); } + 50% { transform: scale(0); } +} + +@keyframes spinner-button-dot-two { + 0% { transform: scale(0); } + 20% { transform: scale(0); } + 45% { transform: scale(1); } + 70% { transform: scale(0); } +} + +@keyframes spinner-button-dot-three { + 0% { transform: scale(0); } + 40% { transform: scale(0); } + 65% { transform: scale(1); } + 90% { transform: scale(0); } +} + +.spinner-button { + display: inline-block; +} + +.spinner-button--spinner-active { + .spinner-button__content { + position: relative; + + a, + button:not([type]), + [type="submit"], + [type="button"] { + background-color: color('primary-darker'); + color: transparent; + opacity: 1; + } + } + + .spinner-button__spinner { + display: flex; + height: 12px; + left: 50%; + margin-left: -23px; + margin-top: -6px; + pointer-events: none; + position: absolute; + top: 50%; + width: 46px; + } + + .spinner-button__spinner-dot { + height: 12px; + margin-left: 5px; + width: 12px; + + &::after { + animation-duration: 1.25s; + animation-iteration-count: infinite; + animation-timing-function: linear; + background-color: color('white'); + border-radius: 50%; + content: ''; + display: block; + height: 100%; + transform: scale(0); + width: 100%; + } + + &:first-child { + margin-left: 0; + } + + &:nth-child(1)::after { + animation-name: spinner-button-dot-one; + } + + &:nth-child(2)::after { + animation-name: spinner-button-dot-two; + } + + &:nth-child(3)::after { + animation-name: spinner-button-dot-three; + } + } +} + +.spinner-button__action-message { + @include u-text('bold', 'primary-darker'); + @include u-margin-top(2); +} diff --git a/app/assets/stylesheets/components/_spinner.scss b/app/assets/stylesheets/components/_spinner.scss deleted file mode 100644 index d86fd6a0b2c..00000000000 --- a/app/assets/stylesheets/components/_spinner.scss +++ /dev/null @@ -1,5 +0,0 @@ -.spinner { - margin-left: auto; - margin-right: auto; - width: 144px; -} diff --git a/app/assets/stylesheets/components/_tooltip.scss b/app/assets/stylesheets/components/_tooltip.scss deleted file mode 100644 index 774cb4f0a7d..00000000000 --- a/app/assets/stylesheets/components/_tooltip.scss +++ /dev/null @@ -1,50 +0,0 @@ -[class*="hint--"] { - cursor: help; - font-style: normal; - font-weight: normal; - letter-spacing: normal; - text-transform: none; - - &::after { - font-size: $h6; - line-height: 1.3; - min-width: 125px; - white-space: normal; - width: 100%; - } - - &:focus::before, - &:focus::after { - opacity: 1; - transition-delay: 100ms; - visibility: visible; - } -} - -.hint--top { - &:focus::before { - transform: translateY(-8px); - } - - &:focus::after { - transform: translateX(-50%) translateY(-8px); - } -} - -.hint--no-animate { - &::before, - &::after { - visibility: hidden; - } - - &:focus::before, - &:focus::after, - &:hover::before, - &:hover::after { - visibility: visible; - } -} - -.img-tooltip { - vertical-align: middle; -} diff --git a/app/assets/stylesheets/components/all.scss b/app/assets/stylesheets/components/all.scss index 49e2183c339..5ea31614800 100644 --- a/app/assets/stylesheets/components/all.scss +++ b/app/assets/stylesheets/components/all.scss @@ -1,6 +1,5 @@ @import 'general'; @import 'abbr'; -@import 'alert'; @import 'background'; @import 'banner'; @import 'border'; @@ -9,6 +8,7 @@ @import 'color'; @import 'container'; @import 'external-link'; +@import 'file-input'; @import 'footer'; @import 'form'; @import 'icon'; @@ -22,10 +22,9 @@ @import 'profile-section'; @import 'personal-key'; @import 'position'; -@import 'tooltip'; @import 'util'; @import 'verification-badge'; -@import 'spinner'; +@import 'spinner-button'; @import 'full-screen'; @import 'space-addon'; @import 'space-misc'; diff --git a/app/controllers/concerns/idv_session.rb b/app/controllers/concerns/idv_session.rb index 071f48e2789..4d2d4fa19b7 100644 --- a/app/controllers/concerns/idv_session.rb +++ b/app/controllers/concerns/idv_session.rb @@ -40,4 +40,12 @@ def idv_session def idv_attempter_throttled? Throttler::IsThrottled.call(current_user.id, :idv_resolution) end + + def sp_context_needed? + return if sp_from_sp_session.present? + return unless LoginGov::Hostdata.in_datacenter? + return if LoginGov::Hostdata.env != AppConfig.env.sp_context_needed_environment + + redirect_to account_url + end end diff --git a/app/controllers/concerns/saml_idp_auth_concern.rb b/app/controllers/concerns/saml_idp_auth_concern.rb index 4ee9a093d09..45630305257 100644 --- a/app/controllers/concerns/saml_idp_auth_concern.rb +++ b/app/controllers/concerns/saml_idp_auth_concern.rb @@ -17,7 +17,7 @@ def validate_service_provider_and_authn_context @result = @saml_request_validator.call( service_provider: current_service_provider, - authn_context: requested_authn_context, + authn_context: requested_authn_contexts, nameid_format: name_id_format, ) @@ -49,17 +49,49 @@ def store_saml_request ).call end + def requested_authn_contexts + @requested_authn_contexts ||= saml_request.requested_authn_contexts.presence || + [default_authn_context] + end + def requested_authn_context - @requested_authn_context ||= saml_request.requested_authn_contexts.presence || - [default_authn_context] + if AppConfig.env.aal_authn_context_enabled == 'true' + requested_aal_authn_context + else + requested_ial_authn_context + end end def default_authn_context - Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + if AppConfig.env.aal_authn_context_enabled == 'true' + default_aal_context + else + default_ial_context + end + end + + def default_aal_context + if current_service_provider.aal + Saml::Idp::Constants::AUTHN_CONTEXT_AAL_TO_CLASSREF[current_service_provider.aal] + else + Saml::Idp::Constants::DEFAULT_AAL_AUTHN_CONTEXT_CLASSREF + end + end + + def default_ial_context + if current_service_provider.ial + Saml::Idp::Constants::AUTHN_CONTEXT_IAL_TO_CLASSREF[current_service_provider.ial] + else + Saml::Idp::Constants::IAL1_AUTHN_CONTEXT_CLASSREF + end + end + + def requested_aal_authn_context + saml_request.requested_aal_authn_context || default_aal_context end def requested_ial_authn_context - saml_request.requested_ial_authn_context || default_authn_context + saml_request.requested_ial_authn_context || default_ial_context end def link_identity_from_session_data @@ -114,14 +146,23 @@ def saml_response encode_response( current_user, name_id_format: name_id_format, - authn_context_classref: requested_ial_authn_context, + authn_context_classref: requested_authn_context, reference_id: active_identity.session_uuid, - encryption: current_service_provider.encryption_opts, + encryption: encryption_opts, signature: saml_response_signature_options, signed_response_message: current_service_provider.signed_response_message_requested, ) end + def encryption_opts + query_params = UriService.params(request.original_url) + if query_params[:skip_encryption].present? && current_service_provider.skip_encryption_allowed + nil + else + current_service_provider.encryption_opts + end + end + def saml_response_signature_options endpoint = SamlEndpoint.new(request) { diff --git a/app/controllers/concerns/two_factor_authenticatable.rb b/app/controllers/concerns/two_factor_authenticatable.rb index dae75438370..72a8497d654 100644 --- a/app/controllers/concerns/two_factor_authenticatable.rb +++ b/app/controllers/concerns/two_factor_authenticatable.rb @@ -19,8 +19,4 @@ module TwoFactorAuthenticatable before_action :apply_secure_headers_override, only: %i[show create] # rubocop:enable Rails/LexicallyScopedActionFilter end - - def self.allowed_otp_drift_seconds - ALLOWED_OTP_DRIFT_SECONDS - end end diff --git a/app/controllers/event_disavowal_controller.rb b/app/controllers/event_disavowal_controller.rb index 9cb4432ba43..4b1d494d27d 100644 --- a/app/controllers/event_disavowal_controller.rb +++ b/app/controllers/event_disavowal_controller.rb @@ -12,6 +12,7 @@ def new extra: EventDisavowal::BuildDisavowedEventAnalyticsAttributes.call(disavowed_event), ).to_h, ) + @forbidden_passwords = forbidden_passwords end def create @@ -20,12 +21,19 @@ def create if result.success? handle_successful_password_reset else + @forbidden_passwords = forbidden_passwords render :new end end private + def forbidden_passwords + disavowed_event.user.email_addresses.flat_map do |email_address| + ForbiddenPasswords.new(email_address.email).call + end + end + def password_reset_from_disavowal_form @password_reset_from_disavowal_form ||= EventDisavowal::PasswordResetFromDisavowalForm.new( disavowed_event, diff --git a/app/controllers/idv/capture_doc_controller.rb b/app/controllers/idv/capture_doc_controller.rb index d9a4874746f..cff36b91832 100644 --- a/app/controllers/idv/capture_doc_controller.rb +++ b/app/controllers/idv/capture_doc_controller.rb @@ -12,23 +12,6 @@ class CaptureDocController < ApplicationController analytics_id: Analytics::CAPTURE_DOC, }.freeze - def index - ## - # This allows us to switch between the old doc auth step by step flow and - # the new document capture flow. When the old step by step flow is retired - # we should remove this logic and redirect the user directly to the show - # action. - # - if FeatureManagement.document_capture_step_enabled? - flow.mark_step_complete(:mobile_front_image) - flow.mark_step_complete(:capture_mobile_back_image) - flow.mark_step_complete(:selfie) - else - flow.mark_step_complete(:document_capture) - end - redirect_to_step(next_step) - end - private def ensure_user_id_in_session @@ -36,26 +19,14 @@ def ensure_user_id_in_session token.blank? && document_capture_session_uuid.blank? - result = if FeatureManagement.document_capture_step_enabled? - CaptureDoc::ValidateDocumentCaptureSession.new(document_capture_session_uuid).call - else - CaptureDoc::ValidateRequestToken.new(token).call - end + result = CaptureDoc::ValidateDocumentCaptureSession.new(document_capture_session_uuid).call analytics.track_event(FSM_SETTINGS[:analytics_id], result.to_h) process_result(result) end def add_unsafe_eval_to_capture_steps - return unless %w[ - front_image - back_image - mobile_front_image - mobile_back_image - capture_mobile_back_image - selfie - document_capture - ].include?(current_step) + return unless current_step == 'document_capture' # required to run wasm until wasm-eval is available SecureHeaders.append_content_security_policy_directives( diff --git a/app/controllers/idv/capture_doc_status_controller.rb b/app/controllers/idv/capture_doc_status_controller.rb index db2ac6f907c..e885c466c6d 100644 --- a/app/controllers/idv/capture_doc_status_controller.rb +++ b/app/controllers/idv/capture_doc_status_controller.rb @@ -5,24 +5,11 @@ class CaptureDocStatusController < ApplicationController respond_to :json def show - result = if FeatureManagement.document_capture_step_enabled? - document_capture_session_poll_render_result - else - doc_capture_poll_render_result - end - - render result + render document_capture_session_poll_render_result end private - def doc_capture_poll_render_result - doc_capture = DocCapture.find_by(user_id: current_user.id) - return { plain: 'Unauthorized', status: :unauthorized } if doc_capture.blank? - return { plain: 'Pending', status: :accepted } if doc_capture.acuant_token.blank? - { plain: 'Complete', status: :ok } - end - def document_capture_session_poll_render_result session_uuid = flow_session[:document_capture_session_uuid] document_capture_session = DocumentCaptureSession.find_by(uuid: session_uuid) diff --git a/app/controllers/idv/doc_auth_controller.rb b/app/controllers/idv/doc_auth_controller.rb index 6bf8b5d9f8c..b17b04486b4 100644 --- a/app/controllers/idv/doc_auth_controller.rb +++ b/app/controllers/idv/doc_auth_controller.rb @@ -43,16 +43,7 @@ def flow_session end def add_unsafe_eval_to_capture_steps - capture_steps = %w[ - front_image - back_image - mobile_front_image - mobile_back_image - capture_mobile_back_image - selfie - document_capture - ] - return unless capture_steps.include?(params[:step]) + return unless params[:step] == 'document_capture' # required to run wasm until wasm-eval is available SecureHeaders.append_content_security_policy_directives( diff --git a/app/controllers/idv/image_uploads_controller.rb b/app/controllers/idv/image_uploads_controller.rb index e4fd1fd8a73..1e433a87e13 100644 --- a/app/controllers/idv/image_uploads_controller.rb +++ b/app/controllers/idv/image_uploads_controller.rb @@ -2,8 +2,6 @@ module Idv class ImageUploadsController < ApplicationController include ApplicationHelper # for liveness_checking_enabled? - before_action :render_404_if_disabled - respond_to :json def create @@ -39,10 +37,6 @@ def create private - def render_404_if_disabled - render_not_found unless FeatureManagement.document_capture_step_enabled? - end - def image_form @image_form ||= Idv::ApiImageUploadForm.new( params, diff --git a/app/controllers/idv/phone_controller.rb b/app/controllers/idv/phone_controller.rb index a2e176a5218..8cacb66e13e 100644 --- a/app/controllers/idv/phone_controller.rb +++ b/app/controllers/idv/phone_controller.rb @@ -11,16 +11,15 @@ class PhoneController < ApplicationController def new async_state = step.async_state - case async_state.status - when :none + if async_state.none? analytics.track_event(Analytics::IDV_PHONE_RECORD_VISIT) render :new - when :in_progress + elsif async_state.in_progress? render :wait - when :timed_out + elsif async_state.timed_out? flash[:info] = I18n.t('idv.failure.timeout') render :new - when :done + elsif async_state.done? async_state_done(async_state) end end @@ -51,8 +50,7 @@ def submit_proofing_attempt step.submit(step_params.to_h) end - def handle_proofing_failure(pii) - idv_session.previous_phone_step_params = { 'phone' => pii[:phone] } + def handle_proofing_failure redirect_to failure_url(step.failure_reason) end @@ -96,7 +94,7 @@ def async_state_done(async_state) form_result = step.async_state_done(async_state) analytics.track_event(Analytics::IDV_PHONE_CONFIRMATION_VENDOR, form_result.to_h) redirect_to_next_step and return if async_state.result[:success] - handle_proofing_failure(async_state.pii) + handle_proofing_failure end end end diff --git a/app/controllers/idv/usps_controller.rb b/app/controllers/idv/usps_controller.rb index 4dbdd5f9732..9c6e0c8fff7 100644 --- a/app/controllers/idv/usps_controller.rb +++ b/app/controllers/idv/usps_controller.rb @@ -10,17 +10,17 @@ class UspsController < ApplicationController def index @presenter = UspsPresenter.new(current_user) + current_async_state = async_state - case async_state.status - when :none + if current_async_state.none? analytics.track_event(Analytics::IDV_USPS_ADDRESS_VISITED) render :index - when :in_progress + elsif current_async_state.in_progress? render :wait - when :timed_out + elsif current_async_state.timed_out? render :index - when :done - async_state_done(async_state) + elsif current_async_state.done? + async_state_done(current_async_state) end end @@ -58,24 +58,14 @@ def failure redirect_to idv_usps_url unless performed? end - def pii - hash = {} - update_hash_with_address(hash) - update_hash_with_non_address_pii(hash) - hash + def pii(address_pii) + address_pii.dup.merge(non_address_pii) end - def update_hash_with_address(hash) - profile_params.each { |key, value| hash[key] = value } - end - - def update_hash_with_non_address_pii(hash) - pii_h = pii_to_h - %w[first_name middle_name last_name dob phone ssn].each do |key| - hash[key] = pii_h[key] - end - - hash[:uuid_prefix] = ServiceProvider.from_issuer(sp_session[:issuer]).app_id + def non_address_pii + pii_to_h. + slice('first_name', 'middle_name', 'last_name', 'dob', 'phone', 'ssn'). + merge(uuid_prefix: ServiceProvider.from_issuer(sp_session[:issuer]).app_id) end def pii_to_h @@ -183,6 +173,8 @@ def send_reminder def enqueue_job return if idv_session.idv_usps_document_capture_session_uuid + idv_session.previous_usps_step_params = profile_params.to_h + document_capture_session = DocumentCaptureSession.create( user_id: current_user.id, issuer: sp_session[:issuer], @@ -190,9 +182,10 @@ def enqueue_job requested_at: Time.zone.now, ) - document_capture_session.store_proofing_pii_from_doc(pii) + document_capture_session.create_proofing_session idv_session.idv_usps_document_capture_session_uuid = document_capture_session.uuid - Idv::Agent.new(pii).proof_resolution( + applicant = pii(profile_params.to_h) + Idv::Agent.new(applicant).proof_resolution( document_capture_session, should_proof_state_id: false, trace_id: amzn_trace_id, @@ -202,17 +195,13 @@ def enqueue_job def async_state dcs_uuid = idv_session.idv_usps_document_capture_session_uuid dcs = DocumentCaptureSession.find_by(uuid: dcs_uuid) - return ProofingDocumentCaptureSessionResult.none if dcs_uuid.nil? + return ProofingSessionAsyncResult.none if dcs_uuid.nil? return timed_out if dcs.nil? proofing_job_result = dcs.load_proofing_result return timed_out if proofing_job_result.nil? - if proofing_job_result.result - proofing_job_result.done - elsif proofing_job_result.pii - ProofingDocumentCaptureSessionResult.in_progress - end + proofing_job_result end def async_state_done(async_state) @@ -222,11 +211,11 @@ def async_state_done(async_state) throttle_failure unless success result = form_response(idv_result, success) - pii = async_state.pii delete_async async_state_done_analytics(result) - result.success? ? resolution_success(pii) : failure + applicant = pii(idv_session.previous_usps_step_params) + result.success? ? resolution_success(applicant) : failure end def async_state_done_analytics(result) @@ -242,7 +231,7 @@ def delete_async def timed_out flash[:info] = I18n.t('idv.failure.timeout') delete_async - ProofingDocumentCaptureSessionResult.timed_out + ProofingSessionAsyncResult.timed_out end end end diff --git a/app/controllers/idv_controller.rb b/app/controllers/idv_controller.rb index 421e3f325f6..2c5d62f5386 100644 --- a/app/controllers/idv_controller.rb +++ b/app/controllers/idv_controller.rb @@ -5,6 +5,7 @@ class IdvController < ApplicationController before_action :confirm_two_factor_authenticated before_action :confirm_idv_needed, only: [:fail] before_action :profile_needs_reactivation?, only: [:index] + before_action :sp_context_needed?, only: [:index] def index if decorated_session.requested_more_recent_verification? diff --git a/app/controllers/lambda_callback/address_proof_result_controller.rb b/app/controllers/lambda_callback/address_proof_result_controller.rb index 43aa47cf431..24ad13543b6 100644 --- a/app/controllers/lambda_callback/address_proof_result_controller.rb +++ b/app/controllers/lambda_callback/address_proof_result_controller.rb @@ -1,11 +1,21 @@ module LambdaCallback class AddressProofResultController < AuthTokenController def create - dcs = DocumentCaptureSession.new - dcs.result_id = result_id_parameter - dcs.store_proofing_result(address_result_parameter.to_h) + dcs = DocumentCaptureSession.find_by(result_id: result_id_parameter) - track_exception_in_result(address_result_parameter) + if dcs + analytics.track_event( + Analytics::LAMBDA_RESULT_ADDRESS_PROOF_RESULT, + result: address_result_parameter, + ) + + dcs.store_proofing_result(address_result_parameter.to_h) + + track_exception_in_result(address_result_parameter) + else + NewRelic::Agent.notice_error('AddressProofResult result_id not found') + head :not_found + end end private diff --git a/app/controllers/lambda_callback/document_proof_result_controller.rb b/app/controllers/lambda_callback/document_proof_result_controller.rb index 96884b65586..a7315db5bf4 100644 --- a/app/controllers/lambda_callback/document_proof_result_controller.rb +++ b/app/controllers/lambda_callback/document_proof_result_controller.rb @@ -1,16 +1,24 @@ module LambdaCallback class DocumentProofResultController < AuthTokenController def create - EncryptedRedisStructStorage.store( - ProofingDocumentCaptureSessionResult.new( - id: result_id_parameter, - pii: document_result_parameter[:pii_from_doc], + dcs = DocumentCaptureSession.find_by(result_id: result_id_parameter) + + if dcs + analytics.track_event( + Analytics::LAMBDA_RESULT_DOCUMENT_PROOF_RESULT, result: document_result_parameter.except(:pii_from_doc), - ), - expires_in: AppConfig.env.async_wait_timeout_seconds.to_i, - ) + ) + + dcs.store_doc_auth_result( + result: document_result_parameter.except(:pii_from_doc), + pii: document_result_parameter[:pii_from_doc], + ) - track_exception_in_result(document_result_parameter) + track_exception_in_result(document_result_parameter) + else + NewRelic::Agent.notice_error('DocumentProofResult result_id not found') + head :not_found + end end private diff --git a/app/controllers/lambda_callback/resolution_proof_result_controller.rb b/app/controllers/lambda_callback/resolution_proof_result_controller.rb index 4a4efcec2f0..0fe58e920be 100644 --- a/app/controllers/lambda_callback/resolution_proof_result_controller.rb +++ b/app/controllers/lambda_callback/resolution_proof_result_controller.rb @@ -1,11 +1,21 @@ module LambdaCallback class ResolutionProofResultController < AuthTokenController def create - dcs = DocumentCaptureSession.new - dcs.result_id = result_id_parameter - dcs.store_proofing_result(resolution_result_parameter) + dcs = DocumentCaptureSession.find_by(result_id: result_id_parameter) - track_exception_in_result(resolution_result_parameter) + if dcs + analytics.track_event( + Analytics::LAMBDA_RESULT_RESOLUTION_PROOF_RESULT, + result: resolution_result_parameter, + ) + + dcs.store_proofing_result(resolution_result_parameter) + + track_exception_in_result(resolution_result_parameter) + else + NewRelic::Agent.notice_error('ResolutionProofResult result_id not found') + head :not_found + end end private diff --git a/app/controllers/recurring_job/usps_upload_controller.rb b/app/controllers/recurring_job/usps_upload_controller.rb index 95c3c8c037a..5e4cb169046 100644 --- a/app/controllers/recurring_job/usps_upload_controller.rb +++ b/app/controllers/recurring_job/usps_upload_controller.rb @@ -2,7 +2,7 @@ module RecurringJob class UspsUploadController < AuthTokenController def create today = Time.zone.today - UspsConfirmationUploader.new.run unless HolidayService.observed_holiday?(today) + UspsConfirmationUploader.new.run unless CalendarService.weekend_or_holiday?(today) render plain: 'ok' end diff --git a/app/controllers/test/saml_test_controller.rb b/app/controllers/test/saml_test_controller.rb index 6eaf7dc4374..3dc0d437b41 100644 --- a/app/controllers/test/saml_test_controller.rb +++ b/app/controllers/test/saml_test_controller.rb @@ -38,12 +38,12 @@ def decode_slo_request private def test_saml_settings - sp1_saml_settings + sp1_ial1_saml_settings end def render_template_for(validity, response) render( - template: 'test/saml_test/decode_response.html.erb', + template: 'test/saml_test/decode_response', locals: { is_valid: validity, response: response }, ) end diff --git a/app/controllers/users/reset_passwords_controller.rb b/app/controllers/users/reset_passwords_controller.rb index c92e8421f19..ea0b920e147 100644 --- a/app/controllers/users/reset_passwords_controller.rb +++ b/app/controllers/users/reset_passwords_controller.rb @@ -121,6 +121,7 @@ def handle_unsuccessful_password_reset(result) return end + @forbidden_passwords = forbidden_passwords(resource.email_addresses) render :edit end diff --git a/app/controllers/users/verify_password_controller.rb b/app/controllers/users/verify_password_controller.rb index 4f47a6ac0da..f292c08e1d2 100644 --- a/app/controllers/users/verify_password_controller.rb +++ b/app/controllers/users/verify_password_controller.rb @@ -7,19 +7,17 @@ class VerifyPasswordController < ApplicationController before_action :confirm_personal_key def new - @verify_password_form = VerifyPasswordForm.new( - user: current_user, - password: '', - decrypted_pii: decrypted_pii, - ) + @decrypted_pii = decrypted_pii end def update + @decrypted_pii = decrypted_pii result = verify_password_form.submit if result.success? handle_success(result) else + flash[:error] = t('errors.messages.password_incorrect') render :new end end diff --git a/app/decorators/phone_configuration_decorator.rb b/app/decorators/phone_configuration_decorator.rb deleted file mode 100644 index fa08c8389d7..00000000000 --- a/app/decorators/phone_configuration_decorator.rb +++ /dev/null @@ -1,6 +0,0 @@ -PhoneConfigurationDecorator = Struct.new(:phone_configuration) do - def default_number_message - I18n.t('account.index.default') if - phone_configuration == phone_configuration.user.default_phone_configuration - end -end diff --git a/app/decorators/service_provider_session_decorator.rb b/app/decorators/service_provider_session_decorator.rb index 5f8d5413b48..243fac9dabc 100644 --- a/app/decorators/service_provider_session_decorator.rb +++ b/app/decorators/service_provider_session_decorator.rb @@ -17,16 +17,10 @@ def remember_device_default sp_aal < 2 end - def sp_msg(section, args = {}) - args = args.merge(sp_name: sp_name) - args = args.merge(sp_create_link: sp_create_link) - generate_custom_alert(section, args) - end - - def generate_custom_alert(section, args) + def custom_alert(section) language = I18n.locale.to_s - help_text = sp.help_text.dig(section, language) - help_text % args if help_text + alert = sp.help_text.dig(section, language) + format(alert, sp_name: sp_name, sp_create_link: sp_create_link) if alert.present? end def sp_logo @@ -122,7 +116,7 @@ def failure_to_proof_url sp.failure_to_proof_url.presence || sp_return_url end - def sp_alert?(path) + def sp_alert(path) sign_in_path = I18n.locale == :en ? new_user_session_path : new_user_session_path(locale: I18n.locale) sign_up_path = @@ -132,7 +126,7 @@ def sp_alert?(path) path_to_section_map = { sign_in_path => 'sign_in', sign_up_path => 'sign_up', forgot_password_path => 'forgot_password' } - custom_alert?(path_to_section_map[path]) + custom_alert(path_to_section_map[path]) end def mfa_expiration_interval @@ -162,11 +156,6 @@ def sp_ial sp.ial || 1 end - def custom_alert?(section) - language = I18n.locale.to_s - sp.help_text[section]&.dig(language).present? - end - def request_url sp_session[:request_url] || service_provider_request.url end diff --git a/app/decorators/session_decorator.rb b/app/decorators/session_decorator.rb index 935dfdbf8c2..cd488c52f9a 100644 --- a/app/decorators/session_decorator.rb +++ b/app/decorators/session_decorator.rb @@ -41,8 +41,6 @@ def remember_device_default true end - def sp_msg; end - def sp_name; end def sp_logo; end @@ -55,7 +53,7 @@ def sp_return_url; end def requested_attributes; end - def sp_alert?(_path); end + def sp_alert(_path); end def requested_more_recent_verification? false diff --git a/app/forms/idv/api_document_verification_status_form.rb b/app/forms/idv/api_document_verification_status_form.rb index d0573ef71be..ee61ff3c584 100644 --- a/app/forms/idv/api_document_verification_status_form.rb +++ b/app/forms/idv/api_document_verification_status_form.rb @@ -27,12 +27,12 @@ def remaining_attempts end def timeout_error - return unless @async_state.status == :timed_out + return unless @async_state.timed_out? errors.add(:timeout, t('errors.doc_auth.document_verification_timeout')) end def failed_result - return if @async_state.status != :done || @async_state.result[:success] + return if !@async_state.done? || @async_state.result[:success] @async_state.result[:errors].each { |key, error| errors.add(key, error) } end end diff --git a/app/forms/idv/api_image_upload_form.rb b/app/forms/idv/api_image_upload_form.rb index ee3af08fcac..d55e0432f73 100644 --- a/app/forms/idv/api_image_upload_form.rb +++ b/app/forms/idv/api_image_upload_form.rb @@ -91,7 +91,6 @@ def validate_images def as_readable(image_key) return @readable[image_key] if @readable.key?(image_key) - value = params[image_key] @readable[image_key] = begin if value.respond_to?(:read) diff --git a/app/forms/idv/document_capture_form.rb b/app/forms/idv/document_capture_form.rb index 1c82dcf71a8..b04fad978bf 100644 --- a/app/forms/idv/document_capture_form.rb +++ b/app/forms/idv/document_capture_form.rb @@ -2,23 +2,21 @@ module Idv class DocumentCaptureForm include ActiveModel::Model - ATTRIBUTES = %i[front_image front_image_data_url - back_image back_image_data_url - selfie_image selfie_image_data_url].freeze - - attr_accessor :front_image, :front_image_data_url, - :back_image, :back_image_data_url, - :selfie_image, :selfie_image_data_url + ATTRIBUTES = %i[front_image + back_image + selfie_image].freeze + attr_accessor :front_image, + :back_image, + :selfie_image attr_reader :liveness_checking_enabled - validate :front_image_or_image_data_url_presence - validate :back_image_or_image_data_url_presence - validate :selfie_image_or_image_data_url_presence + validates :front_image, presence: true + validates :back_image, presence: true + validates :selfie_image, presence: true, if: :liveness_checking_enabled def initialize(**args) @liveness_checking_enabled = args.delete(:liveness_checking_enabled) - super end def self.model_name @@ -38,22 +36,6 @@ def extra_analytics_attributes { is_fallback_link: is_fallback_link } end - def front_image_or_image_data_url_presence - return if front_image.present? || front_image_data_url.present? - errors.add(:front_image, :blank) - end - - def back_image_or_image_data_url_presence - return if back_image.present? || back_image_data_url.present? - errors.add(:back_image, :blank) - end - - def selfie_image_or_image_data_url_presence - return unless liveness_checking_enabled - return if selfie_image.present? || selfie_image_data_url.present? - errors.add(:selfie_image, :blank) - end - def consume_params(params) params.each do |key, value| raise_invalid_image_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym) diff --git a/app/forms/idv/image_upload_form.rb b/app/forms/idv/image_upload_form.rb deleted file mode 100644 index 6c768d75d9f..00000000000 --- a/app/forms/idv/image_upload_form.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Idv - class ImageUploadForm - include ActiveModel::Model - - ATTRIBUTES = %i[image image_data_url].freeze - - attr_accessor :image, :image_data_url - - validate :image_or_image_data_url_presence - - def self.model_name - ActiveModel::Name.new(self, nil, 'Image') - end - - def submit(params) - consume_params(params) - - FormResponse.new(success: valid?, errors: errors.messages, extra: extra_analytics_attributes) - end - - private - - def extra_analytics_attributes - { is_fallback_link: image.present? } - end - - def image_or_image_data_url_presence - return if image.present? || image_data_url.present? - errors.add(:image, :blank) - end - - def consume_params(params) - params.each do |key, value| - raise_invalid_image_parameter_error(key) unless ATTRIBUTES.include?(key.to_sym) - send("#{key}=", value) - end - end - - def raise_invalid_image_parameter_error(key) - raise ArgumentError, "#{key} is an invalid image attribute" - end - end -end diff --git a/app/forms/openid_connect_authorize_form.rb b/app/forms/openid_connect_authorize_form.rb index d1b8fb90563..dc4eba1726c 100644 --- a/app/forms/openid_connect_authorize_form.rb +++ b/app/forms/openid_connect_authorize_form.rb @@ -65,6 +65,7 @@ def verified_at_requested? end def service_provider + return NullServiceProvider.new(issuer: nil) if client_id && client_id.include?("\x00") @_service_provider ||= ServiceProvider.from_issuer(client_id) end diff --git a/app/forms/openid_connect_token_form.rb b/app/forms/openid_connect_token_form.rb index 59f9f15e987..9ce20e94f27 100644 --- a/app/forms/openid_connect_token_form.rb +++ b/app/forms/openid_connect_token_form.rb @@ -3,6 +3,8 @@ class OpenidConnectTokenForm include ActionView::Helpers::TranslationHelper include Rails.application.routes.url_helpers + ISSUED_AT_LEEWAY_SECONDS = 10.seconds.to_i + ATTRS = %i[ client_assertion client_assertion_type @@ -104,10 +106,11 @@ def validate_client_assertion return if identity.blank? payload, _headers = JWT.decode(client_assertion, service_provider.ssl_cert.public_key, true, - algorithm: 'RS256', verify_iat: true, - iss: client_id, verify_iss: true, - sub: client_id, verify_sub: true) + algorithm: 'RS256', iss: client_id, + verify_iss: true, sub: client_id, + verify_sub: true) validate_aud_claim(payload) + validate_iat(payload) rescue JWT::DecodeError => err errors.add(:client_assertion, err.message) end @@ -122,6 +125,14 @@ def validate_aud_claim(payload) t('openid_connect.token.errors.invalid_aud', url: api_openid_connect_token_url)) end + def validate_iat(payload) + return true unless payload.key?('iat') + iat = payload['iat'] + return true if iat.is_a?(Numeric) && (iat.to_i - ISSUED_AT_LEEWAY_SECONDS) < Time.zone.now.to_i + + errors.add(:client_assertion, t('openid_connect.token.errors.invalid_iat')) + end + def service_provider @service_provider ||= ServiceProvider.from_issuer(client_id) end diff --git a/app/forms/verify_password_form.rb b/app/forms/verify_password_form.rb index c6ee11c3e10..81f81b7750f 100644 --- a/app/forms/verify_password_form.rb +++ b/app/forms/verify_password_form.rb @@ -42,8 +42,4 @@ def reencrypt_pii def profile @_profile ||= user.decorate.password_reset_profile end - - def user_access_key - @_uak ||= user.unlock_user_access_key(password) - end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c778f111655..a1b44ed5d62 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -15,16 +15,6 @@ def step_class(step, active) end end - def tooltip(text) - content_tag( - :span, \ - image_tag(asset_url('tooltip.svg'), width: 16, class: 'px1 img-tooltip'), \ - class: 'hint--top hint--no-animate', \ - 'aria-label': text, \ - 'tabindex': '0', - ) - end - def sp_session session.fetch(:sp, {}) end diff --git a/app/helpers/script_helper.rb b/app/helpers/script_helper.rb index 5455bc10079..a82e9995f57 100644 --- a/app/helpers/script_helper.rb +++ b/app/helpers/script_helper.rb @@ -7,6 +7,7 @@ module ScriptHelper def javascript_pack_tag_once(name) @scripts ||= Set.new @scripts.add(name) + nil end def render_javascript_pack_once_tags diff --git a/app/javascript/app/acuant/document_capture.js b/app/javascript/app/acuant/document_capture.js deleted file mode 100644 index b1adb10cfae..00000000000 --- a/app/javascript/app/acuant/document_capture.js +++ /dev/null @@ -1,57 +0,0 @@ -const { - fetchSdkInitializationCredentials, - fetchSdkInitializationEndpoint, - acuantSdkInitializationStarted, - acuantSdkInitializeSuccess, - acuantSdkInitializeFailed, - addClickEventListenerToAcuantCaptureButton, - acuantImageCaptureStarted, - acuantImageCaptureSuccess, - acuantImageCaptureFailed, -} = require('./document_capture_dom'); - -export const imageCaptureButtonClicked = (event) => { - event.preventDefault(); - acuantImageCaptureStarted(); - - const start = window.AcuantCamera.isCameraSupported - ? window.AcuantCameraUI.start.bind(window.AcuantCameraUI) - : window.AcuantCamera.startManualCapture.bind(window.AcuantCamera); - - start( - { - onCaptured() {}, - onCropped(response) { - if (response) { - acuantImageCaptureSuccess(response); - } else { - acuantImageCaptureFailed(); - } - }, - }, - acuantImageCaptureFailed, - ); -}; - -export const initializeAcuantSdk = (credentials = null, endpoint = null) => { - credentials = credentials || fetchSdkInitializationCredentials(); - endpoint = endpoint || fetchSdkInitializationEndpoint(); - window.AcuantJavascriptWebSdk.initialize(credentials, endpoint, { - onSuccess: () => { - addClickEventListenerToAcuantCaptureButton(imageCaptureButtonClicked); - acuantSdkInitializeSuccess(); - }, - onFail: acuantSdkInitializeFailed, - }); -}; - -export const loadAndInitializeAcuantSdk = () => { - acuantSdkInitializationStarted(); - window.onAcuantSdkLoaded = initializeAcuantSdk; - - const sdk = document.createElement('script'); - sdk.src = '/acuant/11.4.1/AcuantJavascriptWebSdk.min.js'; - sdk.async = true; - - document.body.appendChild(sdk); -}; diff --git a/app/javascript/app/acuant/document_capture_dom.js b/app/javascript/app/acuant/document_capture_dom.js deleted file mode 100644 index b4d2117686f..00000000000 --- a/app/javascript/app/acuant/document_capture_dom.js +++ /dev/null @@ -1,124 +0,0 @@ -// eslint-disable-next-line import/no-cycle -import { documentCaptureFallbackModeEnabled } from './document_capture_fallback'; - -// Fallback form elements -export const fallbackImageForm = () => document.querySelector('#acuant-fallback-image-form'); -export const imageFileInput = () => document.querySelector('#doc_auth_image'); -export const imageDataUrlInput = () => document.querySelector('#doc_auth_image_data_url'); -// Acuant UI containers -export const acuantSdkUploadForm = () => document.querySelector('#acuant-sdk-upload-form'); -export const acuantSdkSpinner = () => document.querySelector('#acuant-sdk-spinner'); -export const acuantSdkCaptureView = () => document.querySelector('#acuant-sdk-capture-view'); -export const acuantSdkCaptureViewCloseButton = () => - document.querySelector('#acuant-sdk-capture-view-close'); -export const acuantSdkContinueForm = () => document.querySelector('#acuant-sdk-continue-form'); -// Acuant UI elements -export const acuantSdkCaptureButton = () => document.querySelector('#acuant-sdk-capture'); -export const acuantSdkPreviewImage = () => document.querySelector('#acuant-sdk-preview'); -// Fallback UI elements -export const acuantSdkFallbackText = () => document.querySelector('#acuant-fallback-text'); -export const acuantSdkFallbackLink = () => document.querySelector('#acuant-fallback-link'); - -export const fetchSdkInitializationCredentials = () => - document.querySelector('meta[name="acuant-sdk-initialization-creds"]').content; - -export const fetchSdkInitializationEndpoint = () => - document.querySelector('meta[name="acuant-sdk-initialization-endpoint"]').content; - -const hideAcuantSdkContainers = () => { - acuantSdkUploadForm().classList.add('hidden'); - acuantSdkSpinner().classList.add('hidden'); - acuantSdkCaptureView().classList.add('hidden'); - acuantSdkContinueForm().classList.add('hidden'); -}; - -export const acuantImageCaptureEnded = () => { - acuantSdkCaptureView().classList.add('hidden'); - acuantSdkUploadForm().classList.remove('hidden'); - window.AcuantCameraUI.end(); -}; - -export const addClickEventListenerToAcuantCaptureViewCloseButton = (clickCallback) => { - acuantSdkCaptureViewCloseButton().onclick = clickCallback; -}; - -const showFallbackForm = () => { - fallbackImageForm().classList.remove('hidden'); -}; - -export const showAcuantSdkContainer = (container) => { - if (documentCaptureFallbackModeEnabled()) { - return; - } - - hideAcuantSdkContainers(); - - switch (container) { - case 'upload-form': - acuantSdkUploadForm().classList.remove('hidden'); - break; - case 'spinner': - acuantSdkSpinner().classList.remove('hidden'); - break; - case 'capture-view': - addClickEventListenerToAcuantCaptureViewCloseButton(acuantImageCaptureEnded); - acuantSdkCaptureView().classList.remove('hidden'); - break; - case 'continue-form': - acuantSdkContinueForm().classList.remove('hidden'); - break; - default: - break; - } -}; - -export const acuantSdkInitializationStarted = () => { - fallbackImageForm().classList.add('hidden'); - showAcuantSdkContainer('spinner'); -}; - -export const acuantSdkInitializeSuccess = () => { - showAcuantSdkContainer('upload-form'); -}; - -export const acuantSdkInitializeFailed = () => { - hideAcuantSdkContainers(); - showFallbackForm(); -}; - -export const addClickEventListenerToAcuantCaptureButton = (clickCallback) => { - acuantSdkCaptureButton().onclick = clickCallback; -}; - -export const acuantImageCaptureStarted = () => { - showAcuantSdkContainer('capture-view'); -}; - -export const acuantImageCaptureSuccess = (response) => { - acuantSdkPreviewImage().src = response.image.data; - imageDataUrlInput().value = response.image.data; - imageFileInput().required = false; - window.AcuantCameraUI.end(); - showAcuantSdkContainer('continue-form'); -}; - -export const acuantImageCaptureFailed = (error) => { - // eslint-disable-next-line - console.log('Acuant SDK image capture error:', error); - hideAcuantSdkContainers(); - showFallbackForm(); -}; - -export const showAcuantSdkFallbackText = () => { - acuantSdkFallbackText().classList.remove('hidden'); -}; - -export const addClickEventListenerToAcuantFallbackLink = (clickCallback) => { - acuantSdkFallbackLink().onclick = clickCallback; -}; - -export const acauntDocumentCaptureFallbackEnabled = () => { - hideAcuantSdkContainers(); - showFallbackForm(); - acuantSdkFallbackText().classList.add('hidden'); -}; diff --git a/app/javascript/app/acuant/document_capture_fallback.js b/app/javascript/app/acuant/document_capture_fallback.js deleted file mode 100644 index e3d173d4c0d..00000000000 --- a/app/javascript/app/acuant/document_capture_fallback.js +++ /dev/null @@ -1,26 +0,0 @@ -// eslint-disable-next-line import/no-cycle -import { - showAcuantSdkFallbackText, - addClickEventListenerToAcuantFallbackLink, - acauntDocumentCaptureFallbackEnabled, -} from './document_capture_dom'; - -export const enableDocumentCaptureFallbackMode = () => { - window.isDocumentCaptureFallbackModeEnabled = true; - acauntDocumentCaptureFallbackEnabled(); -}; - -export const documentCaptureFallbackModeEnabled = () => - window.isDocumentCaptureFallbackModeEnabled === true; - -export const documentCaptureFallbackLinkClicked = (event) => { - event.preventDefault(); - enableDocumentCaptureFallbackMode(); -}; - -export const setDocumentCaptureFallbackTimeout = () => { - addClickEventListenerToAcuantFallbackLink(documentCaptureFallbackLinkClicked); - window.setTimeout(() => { - showAcuantSdkFallbackText(); - }, 5000); -}; diff --git a/app/javascript/app/acuant/selfie_capture.js b/app/javascript/app/acuant/selfie_capture.js deleted file mode 100644 index a0ea9147ac9..00000000000 --- a/app/javascript/app/acuant/selfie_capture.js +++ /dev/null @@ -1,50 +0,0 @@ -import { - acuantSdkPreviewImage, - imageDataUrlInput, - imageFileInput, - showAcuantSdkContainer, -} from './document_capture_dom'; - -const { - fetchSdkInitializationCredentials, - fetchSdkInitializationEndpoint, - acuantSdkInitializationStarted, - acuantSdkInitializeSuccess, - acuantSdkInitializeFailed, - addClickEventListenerToAcuantCaptureButton, -} = require('./document_capture_dom'); - -export const onCaptured = (image) => { - acuantSdkPreviewImage().src = `data:image/jpeg;base64,${image}`; - imageDataUrlInput().value = `data:image/jpeg;base64,${image}`; - imageFileInput().required = false; - showAcuantSdkContainer('continue-form'); -}; - -export const imageCaptureButtonClicked = (event) => { - event.preventDefault(); - window.AcuantPassiveLiveness.startSelfieCapture(onCaptured.bind(this)); -}; - -export const initializeAcuantSdk = (credentials = null, endpoint = null) => { - credentials = credentials || fetchSdkInitializationCredentials(); - endpoint = endpoint || fetchSdkInitializationEndpoint(); - window.AcuantJavascriptWebSdk.initialize(credentials, endpoint, { - onSuccess: () => { - addClickEventListenerToAcuantCaptureButton(imageCaptureButtonClicked); - acuantSdkInitializeSuccess(); - }, - onFail: acuantSdkInitializeFailed, - }); -}; - -export const loadAndInitializeAcuantSdk = () => { - acuantSdkInitializationStarted(); - window.onAcuantSdkLoaded = initializeAcuantSdk; - - const sdk = document.createElement('script'); - sdk.src = '/acuant/11.4.1/AcuantJavascriptWebSdk.min.js'; - sdk.async = true; - - document.body.appendChild(sdk); -}; diff --git a/app/javascript/app/components/index.js b/app/javascript/app/components/index.js index c4a4bc5facb..3de28c2a979 100644 --- a/app/javascript/app/components/index.js +++ b/app/javascript/app/components/index.js @@ -1,4 +1,9 @@ +import { accordion, accordionCloseButton, banner, navigation } from 'identity-style-guide'; +import domready from 'domready'; import Modal from './modal'; window.LoginGov = window.LoginGov || {}; window.LoginGov.Modal = Modal; + +const components = [accordion, accordionCloseButton, banner, navigation]; +domready(() => components.forEach((component) => component.on())); diff --git a/app/javascript/app/ssn-field.js b/app/javascript/app/ssn-field.js index 0970fc49958..aedeafc1fb1 100644 --- a/app/javascript/app/ssn-field.js +++ b/app/javascript/app/ssn-field.js @@ -3,7 +3,7 @@ import Cleave from 'cleave.js'; const { I18n } = window.LoginGov; /* eslint-disable no-new */ -function formatSSNField() { +function formatSSNFieldAndLimitLength() { const inputs = document.querySelectorAll('input.ssn-toggle[type="password"]'); if (inputs) { @@ -48,8 +48,18 @@ function formatSSNField() { sync(); toggle.addEventListener('change', sync); + + function limitLength() { + const maxLength = 9 + (this.value.match(/-/g) || []).length; + if (this.value.length > maxLength) { + this.value = this.value.slice(0, maxLength); + this.checkValidity(); + } + } + + input.addEventListener('input', limitLength.bind(input)); }); } } -document.addEventListener('DOMContentLoaded', formatSSNField); +document.addEventListener('DOMContentLoaded', formatSSNFieldAndLimitLength); diff --git a/app/javascript/app/utils/index.js b/app/javascript/app/utils/index.js index dcc2e352fdf..76003a9cf1d 100644 --- a/app/javascript/app/utils/index.js +++ b/app/javascript/app/utils/index.js @@ -1,4 +1,3 @@ -import 'identity-style-guide/dist/assets/js/main'; import autoLogout from './auto-logout'; import countdownTimer from './countdown-timer'; import msFormatter from './ms-formatter'; diff --git a/app/javascript/packages/document-capture/components/acuant-capture.scss b/app/javascript/packages/document-capture/components/acuant-capture.scss index 6e3aa996b73..23357d8adcf 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.scss +++ b/app/javascript/packages/document-capture/components/acuant-capture.scss @@ -1,5 +1,4 @@ -// Note: ID specifier necessary only until USWDS upgraded. -#document-capture-form .document-capture-acuant-capture { // scss-lint:disable IdSelector +.document-capture-acuant-capture { max-width: 375px; %pad-common-id-card { diff --git a/app/javascript/packages/document-capture/components/button.jsx b/app/javascript/packages/document-capture/components/button.jsx index 36eed61a8d4..89e446bdb49 100644 --- a/app/javascript/packages/document-capture/components/button.jsx +++ b/app/javascript/packages/document-capture/components/button.jsx @@ -13,6 +13,8 @@ * @prop {boolean=} isDisabled Whether button is disabled. * @prop {boolean=} isUnstyled Whether button should be unstyled, visually as a * link. + * @prop {boolean=} isVisuallyDisabled Whether button should appear disabled (but + * remain clickable). * @prop {string=} className Optional additional class names. */ @@ -27,6 +29,7 @@ function Button({ isSecondary, isDisabled, isUnstyled, + isVisuallyDisabled, className, }) { const classes = [ @@ -34,6 +37,7 @@ function Button({ isPrimary && 'btn-primary btn-wide', isSecondary && 'btn-secondary', isUnstyled && 'btn-link', + isVisuallyDisabled && 'btn-disabled', className, ] .filter(Boolean) diff --git a/app/javascript/packages/document-capture/components/document-capture.jsx b/app/javascript/packages/document-capture/components/document-capture.jsx index b08187d69ce..edd04b22ee0 100644 --- a/app/javascript/packages/document-capture/components/document-capture.jsx +++ b/app/javascript/packages/document-capture/components/document-capture.jsx @@ -2,8 +2,8 @@ import { useState, useMemo, useContext } from 'react'; import { Alert } from '@18f/identity-components'; import FormSteps from './form-steps'; import { UploadFormEntriesError } from '../services/upload'; -import DocumentsStep from './documents-step'; -import SelfieStep from './selfie-step'; +import DocumentsStep, { documentsStepValidator } from './documents-step'; +import SelfieStep, { selfieStepValidator } from './selfie-step'; import ReviewIssuesStep from './review-issues-step'; import MobileIntroStep from './mobile-intro-step'; import DeviceContext from '../context/device'; @@ -99,12 +99,14 @@ function DocumentCapture({ isAsyncForm = false }) { name: 'documents', title: t('doc_auth.headings.document_capture'), form: DocumentsStep, + validator: documentsStepValidator, footer: DesktopDocumentDisclosure, }, serviceProvider.isLivenessRequired && { name: 'selfie', title: t('doc_auth.headings.selfie'), form: SelfieStep, + validator: selfieStepValidator, }, ].filter(Boolean)); diff --git a/app/javascript/packages/document-capture/components/documents-step.jsx b/app/javascript/packages/document-capture/components/documents-step.jsx index b58cb5728a7..14c73d7b8ac 100644 --- a/app/javascript/packages/document-capture/components/documents-step.jsx +++ b/app/javascript/packages/document-capture/components/documents-step.jsx @@ -19,6 +19,13 @@ import withBackgroundEncryptedUpload from '../higher-order/with-background-encry */ const DOCUMENT_SIDES = ['front', 'back']; +/** + * @return {Boolean} whether or not the value is valid for the document step + */ +function documentsStepValidator(value = {}) { + return DOCUMENT_SIDES.every((side) => !!value[side]); +} + /** * @param {import('./form-steps').FormStepComponentProps} props Props object. */ @@ -64,3 +71,5 @@ function DocumentsStep({ } export default withBackgroundEncryptedUpload(DocumentsStep); + +export { documentsStepValidator }; diff --git a/app/javascript/packages/document-capture/components/file-input.jsx b/app/javascript/packages/document-capture/components/file-input.jsx index 60b624d1664..08c96cb5340 100644 --- a/app/javascript/packages/document-capture/components/file-input.jsx +++ b/app/javascript/packages/document-capture/components/file-input.jsx @@ -210,9 +210,9 @@ const FileInput = forwardRef((props, ref) => { {value && isImage(value) && ( )} diff --git a/app/javascript/packages/document-capture/components/form-steps.jsx b/app/javascript/packages/document-capture/components/form-steps.jsx index 4c1d9b16d8b..1bdaeaee2f7 100644 --- a/app/javascript/packages/document-capture/components/form-steps.jsx +++ b/app/javascript/packages/document-capture/components/form-steps.jsx @@ -46,6 +46,7 @@ import useForceRender from '../hooks/use-force-render'; * @prop {string} title Step title, shown as heading. * @prop {import('react').FC>>} form Step form component. * @prop {import('react').FC=} footer Optional step footer component. + * @prop {(object)=>boolean=} validator Optional function to validate values for the step */ /** @@ -197,6 +198,8 @@ function FormSteps({ const { form: Form, footer: Footer, name, title } = step; const isLastStep = stepIndex + 1 === steps.length; + const canContinue = (step.validator?.(values) ?? true) && !activeErrors.length; + return (
{Object.keys(values).length > 0 && } @@ -240,7 +243,7 @@ function FormSteps({ return fields.current[field].refCallback; }} /> - {Footer &&