diff --git a/Gemfile b/Gemfile index 9359a42..cdb2249 100644 --- a/Gemfile +++ b/Gemfile @@ -7,7 +7,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.5.1' +ruby '2.5.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.2.1' @@ -44,7 +44,7 @@ gem 'json-jwt' # Authorization # ---------------------------------------------------------------------------------------------------------------------- -gem 'jsonapi-authorization', '~> 1.0.0.beta2' +gem 'jsonapi-authorization', '~> 1.0.0' # ---------------------------------------------------------------------------------------------------------------------- gem 'jsonapi-resources', '~> 0.9' @@ -63,11 +63,17 @@ gem 'scenic', '~> 1.4' # ---------------------------------------------------------------------------------------------------------------------- gem 'paperclip', '~> 6.1' -# add the aws-sdk for storing images in S3 -gem 'aws-sdk', '~> 3.0' +# Amazon Web Services (AWS) +# ---------------------------------------------------------------------------------------------------------------------- + +# aws-sdk for Rails services (not used just yet...but can be for SES, etc.) gem 'aws-sdk-rails', '~> 2.0' +# aws-sdk for Cognito interactions +# @see https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Client.html +gem 'aws-sdk-cognitoidentityprovider', '~> 1' + # Use postgres database gem 'pg', '~> 1.1' @@ -101,8 +107,7 @@ gem 'slowpoke' gem 'bootstrap-email' # `sassc-rails` is required for Bootstrap 4 for email (https://github.com/sass/sassc-rails) -# gem 'sassc-rails' # TODO: see https://github.com/stuyam/bootstrap-email/issues/23 -gem 'sass' +gem 'sassc-rails' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 2c1e16a..6ee865b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,730 +52,47 @@ GEM auto_strip_attributes (2.5.0) activerecord (>= 4.0) aws-eventstream (1.0.1) - aws-partitions (1.134.0) - aws-sdk (3.0.1) - aws-sdk-resources (~> 3) - aws-sdk-acm (1.14.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-acmpca (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-alexaforbusiness (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-amplify (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-apigateway (1.23.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-apigatewaymanagementapi (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-apigatewayv2 (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-applicationautoscaling (1.16.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-applicationdiscoveryservice (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-appmesh (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-appstream (1.20.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-appsync (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-athena (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-autoscaling (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-autoscalingplans (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-backup (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-batch (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-budgets (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-chime (1.2.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloud9 (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-clouddirectory (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudformation (1.14.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudfront (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudhsm (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudhsmv2 (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudsearch (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudsearchdomain (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudtrail (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudwatch (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudwatchevents (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cloudwatchlogs (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-codebuild (1.25.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-codecommit (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-codedeploy (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-codepipeline (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-codestar (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-cognitoidentity (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) + aws-partitions (1.140.0) aws-sdk-cognitoidentityprovider (1.12.0) aws-sdk-core (~> 3, >= 3.39.0) aws-sigv4 (~> 1.0) - aws-sdk-cognitosync (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-comprehend (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-comprehendmedical (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-configservice (1.21.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-connect (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-core (3.46.0) + aws-sdk-core (3.46.2) aws-eventstream (~> 1.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) - aws-sdk-costandusagereportservice (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-costexplorer (1.16.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-databasemigrationservice (1.16.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-datapipeline (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-datasync (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-dax (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-devicefarm (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-directconnect (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-directoryservice (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-dlm (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-docdb (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-dynamodb (1.20.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-dynamodbstreams (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-ec2 (1.67.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-ecr (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-ecs (1.28.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-efs (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-eks (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elasticache (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elasticbeanstalk (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elasticloadbalancing (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elasticloadbalancingv2 (1.19.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elasticsearchservice (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-elastictranscoder (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-emr (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-firehose (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-fms (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-fsx (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-gamelift (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-glacier (1.14.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-globalaccelerator (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-glue (1.23.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-greengrass (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-guardduty (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-health (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iam (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-importexport (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv2 (~> 1.0) - aws-sdk-inspector (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iot (1.22.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iot1clickdevicesservice (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iot1clickprojects (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iotanalytics (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iotdataplane (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-iotjobsdataplane (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kafka (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesis (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesisanalytics (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesisanalyticsv2 (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesisvideo (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesisvideoarchivedmedia (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kinesisvideomedia (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-kms (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-lambda (1.17.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-lambdapreview (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-lex (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-lexmodelbuildingservice (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-licensemanager (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-lightsail (1.14.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-machinelearning (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-macie (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-marketplacecommerceanalytics (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-marketplaceentitlementservice (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-marketplacemetering (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediaconnect (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediaconvert (1.19.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-medialive (1.18.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediapackage (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediastore (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediastoredata (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mediatailor (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-migrationhub (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mobile (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mq (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-mturk (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-neptune (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-opsworks (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-opsworkscm (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-organizations (1.17.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-pi (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-pinpoint (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-pinpointemail (1.2.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-pinpointsmsvoice (1.2.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-polly (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-pricing (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-quicksight (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-rails (2.0.1) + aws-sdk-rails (2.1.0) aws-sdk-ses (~> 1) railties (>= 3) - aws-sdk-ram (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-rds (1.42.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-rdsdataservice (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-redshift (1.18.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-rekognition (1.17.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-resourcegroups (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-resourcegroupstaggingapi (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-resources (3.39.0) - aws-sdk-acm (~> 1) - aws-sdk-acmpca (~> 1) - aws-sdk-alexaforbusiness (~> 1) - aws-sdk-amplify (~> 1) - aws-sdk-apigateway (~> 1) - aws-sdk-apigatewaymanagementapi (~> 1) - aws-sdk-apigatewayv2 (~> 1) - aws-sdk-applicationautoscaling (~> 1) - aws-sdk-applicationdiscoveryservice (~> 1) - aws-sdk-appmesh (~> 1) - aws-sdk-appstream (~> 1) - aws-sdk-appsync (~> 1) - aws-sdk-athena (~> 1) - aws-sdk-autoscaling (~> 1) - aws-sdk-autoscalingplans (~> 1) - aws-sdk-backup (~> 1) - aws-sdk-batch (~> 1) - aws-sdk-budgets (~> 1) - aws-sdk-chime (~> 1) - aws-sdk-cloud9 (~> 1) - aws-sdk-clouddirectory (~> 1) - aws-sdk-cloudformation (~> 1) - aws-sdk-cloudfront (~> 1) - aws-sdk-cloudhsm (~> 1) - aws-sdk-cloudhsmv2 (~> 1) - aws-sdk-cloudsearch (~> 1) - aws-sdk-cloudsearchdomain (~> 1) - aws-sdk-cloudtrail (~> 1) - aws-sdk-cloudwatch (~> 1) - aws-sdk-cloudwatchevents (~> 1) - aws-sdk-cloudwatchlogs (~> 1) - aws-sdk-codebuild (~> 1) - aws-sdk-codecommit (~> 1) - aws-sdk-codedeploy (~> 1) - aws-sdk-codepipeline (~> 1) - aws-sdk-codestar (~> 1) - aws-sdk-cognitoidentity (~> 1) - aws-sdk-cognitoidentityprovider (~> 1) - aws-sdk-cognitosync (~> 1) - aws-sdk-comprehend (~> 1) - aws-sdk-comprehendmedical (~> 1) - aws-sdk-configservice (~> 1) - aws-sdk-connect (~> 1) - aws-sdk-costandusagereportservice (~> 1) - aws-sdk-costexplorer (~> 1) - aws-sdk-databasemigrationservice (~> 1) - aws-sdk-datapipeline (~> 1) - aws-sdk-datasync (~> 1) - aws-sdk-dax (~> 1) - aws-sdk-devicefarm (~> 1) - aws-sdk-directconnect (~> 1) - aws-sdk-directoryservice (~> 1) - aws-sdk-dlm (~> 1) - aws-sdk-docdb (~> 1) - aws-sdk-dynamodb (~> 1) - aws-sdk-dynamodbstreams (~> 1) - aws-sdk-ec2 (~> 1) - aws-sdk-ecr (~> 1) - aws-sdk-ecs (~> 1) - aws-sdk-efs (~> 1) - aws-sdk-eks (~> 1) - aws-sdk-elasticache (~> 1) - aws-sdk-elasticbeanstalk (~> 1) - aws-sdk-elasticloadbalancing (~> 1) - aws-sdk-elasticloadbalancingv2 (~> 1) - aws-sdk-elasticsearchservice (~> 1) - aws-sdk-elastictranscoder (~> 1) - aws-sdk-emr (~> 1) - aws-sdk-firehose (~> 1) - aws-sdk-fms (~> 1) - aws-sdk-fsx (~> 1) - aws-sdk-gamelift (~> 1) - aws-sdk-glacier (~> 1) - aws-sdk-globalaccelerator (~> 1) - aws-sdk-glue (~> 1) - aws-sdk-greengrass (~> 1) - aws-sdk-guardduty (~> 1) - aws-sdk-health (~> 1) - aws-sdk-iam (~> 1) - aws-sdk-importexport (~> 1) - aws-sdk-inspector (~> 1) - aws-sdk-iot (~> 1) - aws-sdk-iot1clickdevicesservice (~> 1) - aws-sdk-iot1clickprojects (~> 1) - aws-sdk-iotanalytics (~> 1) - aws-sdk-iotdataplane (~> 1) - aws-sdk-iotjobsdataplane (~> 1) - aws-sdk-kafka (~> 1) - aws-sdk-kinesis (~> 1) - aws-sdk-kinesisanalytics (~> 1) - aws-sdk-kinesisanalyticsv2 (~> 1) - aws-sdk-kinesisvideo (~> 1) - aws-sdk-kinesisvideoarchivedmedia (~> 1) - aws-sdk-kinesisvideomedia (~> 1) - aws-sdk-kms (~> 1) - aws-sdk-lambda (~> 1) - aws-sdk-lambdapreview (~> 1) - aws-sdk-lex (~> 1) - aws-sdk-lexmodelbuildingservice (~> 1) - aws-sdk-licensemanager (~> 1) - aws-sdk-lightsail (~> 1) - aws-sdk-machinelearning (~> 1) - aws-sdk-macie (~> 1) - aws-sdk-marketplacecommerceanalytics (~> 1) - aws-sdk-marketplaceentitlementservice (~> 1) - aws-sdk-marketplacemetering (~> 1) - aws-sdk-mediaconnect (~> 1) - aws-sdk-mediaconvert (~> 1) - aws-sdk-medialive (~> 1) - aws-sdk-mediapackage (~> 1) - aws-sdk-mediastore (~> 1) - aws-sdk-mediastoredata (~> 1) - aws-sdk-mediatailor (~> 1) - aws-sdk-migrationhub (~> 1) - aws-sdk-mobile (~> 1) - aws-sdk-mq (~> 1) - aws-sdk-mturk (~> 1) - aws-sdk-neptune (~> 1) - aws-sdk-opsworks (~> 1) - aws-sdk-opsworkscm (~> 1) - aws-sdk-organizations (~> 1) - aws-sdk-pi (~> 1) - aws-sdk-pinpoint (~> 1) - aws-sdk-pinpointemail (~> 1) - aws-sdk-pinpointsmsvoice (~> 1) - aws-sdk-polly (~> 1) - aws-sdk-pricing (~> 1) - aws-sdk-quicksight (~> 1) - aws-sdk-ram (~> 1) - aws-sdk-rds (~> 1) - aws-sdk-rdsdataservice (~> 1) - aws-sdk-redshift (~> 1) - aws-sdk-rekognition (~> 1) - aws-sdk-resourcegroups (~> 1) - aws-sdk-resourcegroupstaggingapi (~> 1) - aws-sdk-robomaker (~> 1) - aws-sdk-route53 (~> 1) - aws-sdk-route53domains (~> 1) - aws-sdk-route53resolver (~> 1) - aws-sdk-s3 (~> 1) - aws-sdk-s3control (~> 1) - aws-sdk-sagemaker (~> 1) - aws-sdk-sagemakerruntime (~> 1) - aws-sdk-secretsmanager (~> 1) - aws-sdk-securityhub (~> 1) - aws-sdk-serverlessapplicationrepository (~> 1) - aws-sdk-servicecatalog (~> 1) - aws-sdk-servicediscovery (~> 1) - aws-sdk-ses (~> 1) - aws-sdk-shield (~> 1) - aws-sdk-signer (~> 1) - aws-sdk-simpledb (~> 1) - aws-sdk-sms (~> 1) - aws-sdk-snowball (~> 1) - aws-sdk-sns (~> 1) - aws-sdk-sqs (~> 1) - aws-sdk-ssm (~> 1) - aws-sdk-states (~> 1) - aws-sdk-storagegateway (~> 1) - aws-sdk-support (~> 1) - aws-sdk-swf (~> 1) - aws-sdk-transcribeservice (~> 1) - aws-sdk-transfer (~> 1) - aws-sdk-translate (~> 1) - aws-sdk-waf (~> 1) - aws-sdk-wafregional (~> 1) - aws-sdk-workdocs (~> 1) - aws-sdk-worklink (~> 1) - aws-sdk-workmail (~> 1) - aws-sdk-workspaces (~> 1) - aws-sdk-xray (~> 1) - aws-sdk-robomaker (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-route53 (1.17.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-route53domains (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-route53resolver (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.30.1) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.0) - aws-sdk-s3control (1.1.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-sagemaker (1.28.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-sagemakerruntime (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-secretsmanager (1.20.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-securityhub (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-serverlessapplicationrepository (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-servicecatalog (1.15.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-servicediscovery (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) aws-sdk-ses (1.14.0) aws-sdk-core (~> 3, >= 3.39.0) aws-sigv4 (~> 1.0) - aws-sdk-shield (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-signer (1.5.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-simpledb (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv2 (~> 1.0) - aws-sdk-sms (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-snowball (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-sns (1.9.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-sqs (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-ssm (1.35.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-states (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-storagegateway (1.14.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-support (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-swf (1.6.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-transcribeservice (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-transfer (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-translate (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-waf (1.12.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-wafregional (1.13.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-workdocs (1.8.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-worklink (1.0.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-workmail (1.7.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-workspaces (1.11.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sdk-xray (1.10.0) - aws-sdk-core (~> 3, >= 3.39.0) - aws-sigv4 (~> 1.0) - aws-sigv2 (1.0.1) aws-sigv4 (1.0.3) bcrypt (3.1.12) - better_errors (2.5.0) + better_errors (2.5.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) bindata (2.4.4) - bootsnap (1.3.2) + bootsnap (1.4.0) msgpack (~> 1.0) - bootstrap-email (0.2.4) + bootstrap-email (0.2.5) actionmailer (>= 3, < 6) nokogiri (~> 1.6) premailer-rails (~> 1.9) rails (>= 3, < 6) browser (2.5.3) builder (3.2.3) - byebug (10.0.2) + byebug (11.0.0) climate_control (0.2.0) coderay (1.1.2) concurrent-ruby (1.1.4) connection_pool (2.2.2) crass (1.0.4) - css_parser (1.6.0) + css_parser (1.7.0) addressable erubi (1.8.0) - ffi (1.10.0) + ffi (1.9.25) globalid (0.4.2) activesupport (>= 4.2.0) health_check (3.0.0) @@ -841,6 +158,7 @@ GEM premailer-rails (1.10.2) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) + psych (3.1.0) public_suffix (3.0.3) puma (3.12.0) pundit (2.0.1) @@ -881,22 +199,27 @@ GEM rb-inotify (0.10.0) ffi (~> 1.0) redis (4.1.0) - rubocop (0.63.1) + rubocop (0.65.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) powerpack (~> 0.1) + psych (>= 3.1.0) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.4.0) ruby-progressbar (1.10.0) ruby_dep (1.5.0) - sass (3.7.3) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - scenic (1.4.1) + sassc (2.0.0) + ffi (~> 1.9.6) + rake + sassc-rails (2.1.0) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + scenic (1.5.1) activerecord (>= 4.0.0) railties (>= 4.0.0) seed-fu (2.3.9) @@ -929,6 +252,7 @@ GEM climate_control (>= 0.0.3, < 1.0) thor (0.20.3) thread_safe (0.3.6) + tilt (2.0.9) timecop (0.9.1) tzinfo (1.2.5) thread_safe (~> 0.1) @@ -943,7 +267,7 @@ PLATFORMS DEPENDENCIES audited (~> 4.7) auto_strip_attributes (~> 2.5) - aws-sdk (~> 3.0) + aws-sdk-cognitoidentityprovider (~> 1) aws-sdk-rails (~> 2.0) bcrypt (~> 3.1.7) better_errors (~> 2.5) @@ -953,7 +277,7 @@ DEPENDENCIES byebug health_check (~> 3.0) json-jwt - jsonapi-authorization (~> 1.0.0.beta2) + jsonapi-authorization (~> 1.0.0) jsonapi-resources (~> 0.9) letter_opener (~> 1.6) listen (>= 3.0.5, < 3.2) @@ -962,7 +286,7 @@ DEPENDENCIES puma (~> 3.11) rails (~> 5.2.1) rubocop - sass + sassc-rails scenic (~> 1.4) seed-fu (~> 2.3) sidekiq (~> 5.2) @@ -974,7 +298,7 @@ DEPENDENCIES tzinfo-data RUBY VERSION - ruby 2.5.1p57 + ruby 2.5.3p105 BUNDLED WITH 2.0.1 diff --git a/README.md b/README.md index 088a684..b1d30f4 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # README - ermahgerd-rails-api-cognito -The Rails API server for Canadian Pump & Packing Distribution. Serves up JSONAPI payloads for an EmberJs SPA -over at [https://github.com/cybertooth-io/ccpdist-com-emberjs](https://github.com/cybertooth-io/ccpdist-com-emberjs). +The Rails API server for the Ermahgerd Demo. Serves up JSONAPI payloads, conveniently +consumed by an EmberJs Single Page Application using Ember-Data. + +The EmberJs demo application that talks to this API is found at: +[https://github.com/cybertooth-io/ermahgerd-emberjs-cognito](https://github.com/cybertooth-io/ermahgerd-emberjs-cognito) ## Development - Getting Started You need the following: -* Ruby-2.3+ - suggest Ruby-2.5 but check your production environment to be sure -- e.g. AWS EB +* Ruby-2.3+ - this was however built with Ruby-2.5.x (latest). Make sure to check your +production environment and match it (e.g. Amazon's Elastic Beanstalk) * Docker - we use two containers, one for the PostgreSQL database and one for Redis -* The `config/master.key` file +* A `config/master.key` file that we've given you or generate your own along with your _credentials_ (see below) ### First Time Setting Up @@ -29,19 +33,27 @@ Perform the following from the command line: ### Database Seeds -For development, feel free to edit the `db/fixtures/development/002_users.rb` file to add yourself. - Seed the database with: ```bash $ rake db:seed_fu ``` +#### Seeding Administrator Users + +You need to create at least one Administrator in both this database and the corresponding user over in +AWS Cognito. + +1. Feel free to edit the `db/fixtures/development/002_users.rb` file to a new user to the database +1. Sign in to AWS Web Console and create a user in your pool that shares the same email address + +_Once one administrator has been created, you can create new users inside this app._ + ### Redis Redis is used by Sidekiq to queue up jobs. -Sidekiq is configured in `config/initializers/sidekiq.rb` to use database `1`. +Sidekiq is configured in `config/initializers/sidekiq.rb` to use database `0`. ### Crons/Jobs/Queues @@ -92,17 +104,18 @@ initialize method. ## No Secrets Here - Credentials Instead -As of Rails-5.2 secrets are hashed and locked down with the `config/master.key` file. Run `rails credentials:help` for -more information. +As of Rails-5.2 secrets are hashed and locked down with the `config/master.key` file. Run +`rails credentials:help` for more information. -This application ships with an already created `config/credentials.yml.enc` and we share the `master.key` amongst -ourselves ... but not with Joe Public (or Josephine Public) +This application ships with an already created `config/credentials.yml.enc` and +we share the `master.key` amongst ourselves ... but not with Joe Public (or Josephine Public). If you're forking this or trying it yourself, you'll want to: 1. `rm config/credentials.yml.enc` to get rid of the current credentials -1. `rails credentials:edit` -1. Add the keys that are described in the section below; don't forget to use the `rake secret` to create your keys +1. `rails credentials:edit` to begin editing your credentials file +1. Add the keys that are described in the section below. Please leverage +the `rake secret` tool to create your keys that you're placing into your credentials file. ### Keys in `config/credentials.yml.enc` @@ -110,12 +123,48 @@ If you're forking this or trying it yourself, you'll want to: $ rails credentials:edit # you might have to destroy the existing `config/credentials.yml.enc` if this command fails ``` +#### Example File Contents + +```yaml +# AWS client credentials for S3, Cognito, etc. +aws: + access_key_id: xxxxxxxxxxxxxxxxxxxx + secret_access_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + region: xx-region-x + cognito: + # Used by operations of the Aws::CognitoIdentityProvider::Client; + # the Pool ID (found in the Cognito web console) + user_pool_id: xx-region-x_abcdefghi + # the CLIENT ID (there could be multiple, we may need to refactor this into an array) + client_id: abcdefghijklmnopqrstuvwxyz + +# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. +secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + +# the jwk_set from AWS Cognito: see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-step-2 +jwk_set: '...' + +# the token's AUDience that is verified during authentication; also known as the App Client ID (check out Cognito web console) +token_aud: xxxxxxxxxxxxxxxxxxxxxxxxxx + +# the token's ISSuer url; again verified during authentication +token_iss: https://cognito-idp.xx-region-x.amazonaws.com/xx-region-x_xxxxxxxxx +``` + +#### Keys Explained + `aws:access_key_id` - the access key used to interface with S3, Cognito, etc. `aws:secret_access_key` - the secret access key used to interface with S3, Cognito, etc. `aws:region` - the default region that all AWS Client connections will be directed to. +`aws:cognito:user_pool_id` - this is your AWS Cognito's User Pool Id; looks something like: `xx-region-x_abcdefghi` + +`aws:cognito:client_id` - this your AWS Cognito's Client Id, it's 26 characters long: +e.g. `abcdefghijklmnopqrstuvwxyz`. **There's a possibility that your pool has multiple clients +interacting with this API, server as such this key may need to be refactored.** + `secret_key_base` - used by most Rails apps in one way or another (e.g. BCrypt). Please set this to a strong key; all environments (development, test, etc.) require this to be set. @@ -131,36 +180,11 @@ from your authentication requests to Cognito. By default, the TEST environment not use this setting; it makes up a fake audience value. DEVELOPMENT & PRODUCTION do use this unless you change the configuration through `config/initializers/ermahgerd.rb`. - `token_iss` - the url that issued the token. You can get this information from your Cognito configuration or the payloads from your authentication requests to Cognito. By default, the TEST environment of this app does not use this setting; it makes up a fake audience value. DEVELOPMENT & PRODUCTION do use this unless you change the configuration through `config/initializers/ermahgerd.rb`. -#### Example File Contents - -```yaml -# AWS client credentials for S3, Cognito, etc. -aws: - access_key_id: xxxxxxxxxxxxxxxxxxxx - secret_access_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - region: xx-region-x - cognito: - # Used by operations of the Aws::CognitoIdentityProvider::Client; the Pool ID (found in the Cognito web console) - user_pool_id: ca-central-1_ocRK2nsIR - -# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. -secret_key_base: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - -# the jwk_set from AWS Cognito: see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-step-2 -jwk_set: '...' - -# the token's AUDience that is verified during authentication; also known as the App Client ID (check out Cognito web console) -token_aud: xxxxxxxxxxxxxxxxxxxxxxxxxx - -# the token's ISSuer url; again verified during authentication -token_iss: https://cognito-idp.xx-region-x.amazonaws.com/xx-region-x_xxxxxxxxx -``` ---- ## Releasing diff --git a/app/controllers/api/v1/base_jsonapi_resources_controller.rb b/app/controllers/api/v1/base_jsonapi_resources_controller.rb index e0044b8..7045e9d 100644 --- a/app/controllers/api/v1/base_jsonapi_resources_controller.rb +++ b/app/controllers/api/v1/base_jsonapi_resources_controller.rb @@ -5,8 +5,8 @@ module V1 # This is a JSONAPI-Resources ready controller that tests authentication using Ermahgerd::Authorizer concern. # Authorization is managed through Pundit policies. class BaseJsonapiResourcesController < ApplicationController - include Ermahgerd::AuthenticatedUser - include Ermahgerd::Authorizer + include Ermahgerd::Controllers::Concerns::AuthenticatedUser + include Ermahgerd::Controllers::Concerns::Authorizer include JSONAPI::ActsAsResourceController include Pundit # included for Posterity sake should we override a controller and need to `authorize` @@ -28,6 +28,7 @@ def record_session_activity browser = Browser.new(request.headers['User-Agent']) RecordSessionActivityWorker.perform_async( + authorization_from_header!, browser.name, browser.full_version, Time.zone.now.iso8601, diff --git a/app/controllers/api/v1/sessions_controller.rb b/app/controllers/api/v1/sessions_controller.rb index a5f3b52..85b6ce1 100644 --- a/app/controllers/api/v1/sessions_controller.rb +++ b/app/controllers/api/v1/sessions_controller.rb @@ -9,6 +9,18 @@ module V1 # An `invalidate` action has been added to this controller. This allows sessions to be destroyed # application-side for security reasons. class SessionsController < BaseJsonapiResourcesController + def invalidate + session = Session.find_by!(id: params[:id]) + + authorize session + + InvalidateCognitoSessionWorker.perform_async(current_user.id, Time.zone.now.iso8601, session.id) + + render(status: :ok, + json: JSONAPI::ResourceSerializer + .new(Api::V1::SessionResource, base_url: base_url) + .serialize_to_hash(Api::V1::SessionResource.new(session, context))) + end end end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 4aa3ee6..115046e 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,8 +2,8 @@ # All models inherit from this class. class ApplicationRecord < ActiveRecord::Base - include ScopeByDistinct - include ScopeById + include Ermahgerd::Models::Concerns::ScopeByDistinct + include Ermahgerd::Models::Concerns::ScopeById self.abstract_class = true end diff --git a/app/models/concerns/scope_by_distinct.rb b/app/models/concerns/scope_by_distinct.rb deleted file mode 100644 index 5ae3f1a..0000000 --- a/app/models/concerns/scope_by_distinct.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# A very simple concern that provides the `by_id` scope to all models. -# Unit tested in the `User` model test. -module ScopeByDistinct - extend ActiveSupport::Concern - included do - scope :by_distinct, -> { distinct } - end -end diff --git a/app/models/concerns/scope_by_id.rb b/app/models/concerns/scope_by_id.rb deleted file mode 100644 index 4e06813..0000000 --- a/app/models/concerns/scope_by_id.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -# A very simple concern that provides the `by_id` scope to all models. -# Unit tested in the `User` model test. -module ScopeById - extend ActiveSupport::Concern - included do - scope :by_id, ->(ids) { where id: ids } - end -end diff --git a/app/models/role.rb b/app/models/role.rb index a1d1dfb..f62a19d 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -32,9 +32,9 @@ class Role < ApplicationRecord # Relationships # -------------------------------------------------------------------------------------------------------------------- - # rubocop:disable Rails/HasAndBelongsToMany - has_and_belongs_to_many :users - # rubocop:enable Rails/HasAndBelongsToMany + has_many :roles_users, dependent: :destroy + + has_many :users, through: :roles_users # Scopes # -------------------------------------------------------------------------------------------------------------------- diff --git a/app/models/roles_user.rb b/app/models/roles_user.rb new file mode 100644 index 0000000..fd3afd8 --- /dev/null +++ b/app/models/roles_user.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# This is the linking table for the Has And Belongs To Many relationship between users & roles +class RolesUser < ApplicationRecord + # Relationships + # -------------------------------------------------------------------------------------------------------------------- + + belongs_to :role + belongs_to :user +end diff --git a/app/models/session.rb b/app/models/session.rb index 8ae2a64..808199e 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -1,6 +1,22 @@ # frozen_string_literal: true # A `Session` represents all of the sign in information collected from an authenticated user. +# * `authenticated_at` - the date and time that the user authenticated with the Cognito server (not the last time +# their access token refreshed) +# * `browser` - the name of the browser pulled from the USER_AGENT (e.g. Chrome) +# * `browser_version` - the browser version pulled from the USER_AGENT (e.g. 74.0.3714.0) +# * `created_at` - when was this session created in our database +# * `device` - the device name pulled from the USER_AGENT +# * `device_key` - the device key generated by COGNITO +# * `expires_at` - the name of the browser pulled from the USER_AGENT +# * `invalidated_at` - when was this session invalidated (NULLABLE) +# * `invalidated_by` - who invalidated this session (NULLABLE) +# * `ip_address` - the IP address that the user authenticated with the first time the session was created, +# pulled from the USER AGENT +# * `platform` - the platform pulled from the USER AGENT (e.g. Macintosh) +# * `platform_version` - the platform version pulled from the USER AGENT (e.g. 10.14.3) +# * `updated_at` - when the session was updated last, updated using `touch` from associated Session Activities +# * `user_id` - the associated user class Session < ApplicationRecord # Ignore Removed Columns # -------------------------------------------------------------------------------------------------------------------- @@ -27,6 +43,7 @@ class Session < ApplicationRecord :browser_version, :device, :device_key, + :expires_at, :ip_address, :platform, :platform_version, @@ -34,6 +51,11 @@ class Session < ApplicationRecord presence: true ) + validates( + :device_key, + uniqueness: { scope: :authenticated_at } + ) + # Relationships # -------------------------------------------------------------------------------------------------------------------- @@ -43,9 +65,18 @@ class Session < ApplicationRecord has_many :session_activities, dependent: :destroy + has_one( + :last_session_activity, -> { order(created_at: :desc).limit(1) }, + class_name: 'SessionActivity', inverse_of: false + ) + # Scopes # -------------------------------------------------------------------------------------------------------------------- + scope :by_active, -> { where invalidated_at: nil } + + scope :by_invalidated, -> { where.not invalidated_at: nil } + scope :by_jti, ->(jti) { joins(:session_activities).merge(SessionActivity.by_jti(jti)) } scope :by_user, ->(ids) { where user_id: ids } diff --git a/app/models/session_activity.rb b/app/models/session_activity.rb index d98904d..f08d356 100644 --- a/app/models/session_activity.rb +++ b/app/models/session_activity.rb @@ -13,7 +13,10 @@ class SessionActivity < ApplicationRecord # -------------------------------------------------------------------------------------------------------------------- validates( + :access_token, + :created_at, :ip_address, + :jti, :path, :session, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index 4acff4b..b513233 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -38,9 +38,9 @@ class User < ApplicationRecord # Relationships # -------------------------------------------------------------------------------------------------------------------- - # rubocop:disable Rails/HasAndBelongsToMany - has_and_belongs_to_many :roles - # rubocop:enable Rails/HasAndBelongsToMany + has_many :roles_users, dependent: :destroy + + has_many :roles, through: :roles_users has_many :sessions, dependent: :restrict_with_error diff --git a/app/policies/session_policy.rb b/app/policies/session_policy.rb index 94ce92c..faf6331 100644 --- a/app/policies/session_policy.rb +++ b/app/policies/session_policy.rb @@ -8,6 +8,10 @@ def index? true end + def invalidate? + show? + end + # Any administrator can show any session. # Others will only be able to show sessions that are bound to their user account. def show? diff --git a/app/resources/api/v1/session_activity_resource.rb b/app/resources/api/v1/session_activity_resource.rb index ffed7d8..9eb277a 100644 --- a/app/resources/api/v1/session_activity_resource.rb +++ b/app/resources/api/v1/session_activity_resource.rb @@ -13,6 +13,8 @@ class SessionActivityResource < BaseResource attributes( :ip_address, :path, + # do not serialize the jti + # do not serialize the access_token {} ) @@ -21,7 +23,7 @@ def self.creatable_fields(_context) [] # immutable end - def self.fetchable_fields(_context) + def fetchable_fields super - [:updated_at] end diff --git a/app/resources/api/v1/session_resource.rb b/app/resources/api/v1/session_resource.rb index c041712..740a34a 100644 --- a/app/resources/api/v1/session_resource.rb +++ b/app/resources/api/v1/session_resource.rb @@ -4,7 +4,7 @@ module Api module V1 # Protected access to the `Session` model. class SessionResource < BaseResource - immutable # no CUD through controller + immutable # no CUD through controller except :invalidate (which is hand-coded) # Attributes # ---------------------------------------------------------------------------------------------------------------- @@ -14,6 +14,7 @@ class SessionResource < BaseResource :browser, :browser_version, :device, + :expires_at, :invalidated_at, :ip_address, :platform, @@ -36,6 +37,8 @@ def self.creatable_fields(_context) has_one :invalidated_by + has_one :last_session_activity, foreign_key_on: :related + has_many :session_activities has_one :user diff --git a/app/workers/invalidate_cognito_session_worker.rb b/app/workers/invalidate_cognito_session_worker.rb new file mode 100644 index 0000000..ea616fa --- /dev/null +++ b/app/workers/invalidate_cognito_session_worker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# This worker is responsible for attempting to invalidate an identified session using AWS-SDK and the +# access token from the user. +class InvalidateCognitoSessionWorker + include Sidekiq::Worker + + def perform(invalidated_at_iso8601, performed_by_id, session_id) + invalidated_at = Time.zone.parse(invalidated_at_iso8601) + invalidated_by = User.find_by(id: performed_by_id) # if user isn't found oh well, nil will be placed in the record + session = Session.find_by! id: session_id + + # COGNITO could throw an exception; Sidekiq will retry + User::COGNITO.global_sign_out(access_token: session.last_session_activity.access_token) + session.update!(invalidated_at: invalidated_at, invalidated_by: invalidated_by) + end +end diff --git a/app/workers/invalidate_session_worker.rb b/app/workers/invalidate_session_worker.rb index ecd2c32..43a7dc2 100644 --- a/app/workers/invalidate_session_worker.rb +++ b/app/workers/invalidate_session_worker.rb @@ -7,7 +7,7 @@ class InvalidateSessionWorker def perform(performed_by_id, invalidated_at_iso8601, jti) invalidated_at = Time.zone.parse(invalidated_at_iso8601) - invalidated_by = User.find_by(id: performed_by_id) + invalidated_by = User.find_by(id: performed_by_id) # if user isn't found, oh well ActiveRecord::Base.transaction do Session.by_jti(jti).by_distinct.each do |session| diff --git a/app/workers/record_session_activity_worker.rb b/app/workers/record_session_activity_worker.rb index 8ff01b2..14052ae 100644 --- a/app/workers/record_session_activity_worker.rb +++ b/app/workers/record_session_activity_worker.rb @@ -4,27 +4,21 @@ class RecordSessionActivityWorker include Sidekiq::Worker - def perform(browser, browser_version, created_at_iso8601, device, device_key, ip_address, auth_time_iso8601, - path, platform, platform_version, user_id, jti) + def perform(access_token, browser, browser_version, created_at_iso8601, device, device_key, ip_address, + auth_time_iso8601, path, platform, platform_version, user_id, jti) authenticated_at = Time.zone.parse(auth_time_iso8601) session = Session.find_by(authenticated_at: authenticated_at, device_key: device_key) ActiveRecord::Base.transaction do if session.nil? - session = Session.create!( - authenticated_at: authenticated_at, - browser: browser, - browser_version: browser_version, - device: device, - device_key: device_key, - ip_address: ip_address, - platform: platform, - platform_version: platform_version, - user_id: user_id + session = create_session( + authenticated_at, browser, browser_version, device, device_key, ip_address, + platform, platform_version, user_id ) end SessionActivity.create!( + access_token: access_token, created_at: Time.zone.parse(created_at_iso8601), ip_address: ip_address, jti: jti, @@ -33,4 +27,29 @@ def perform(browser, browser_version, created_at_iso8601, device, device_key, ip ) end end + + private + + def create_session(authenticated_at, browser, browser_version, device, device_key, ip_address, + platform, platform_version, user_id) + user_pool_resp = User::COGNITO.describe_user_pool_client( + user_pool_id: Rails.application.credentials.dig(:aws, :cognito, :user_pool_id), + client_id: Rails.application.credentials.dig(:aws, :cognito, :client_id) + ) + + refresh_token_validity_in_days = user_pool_resp[:user_pool_client][:refresh_token_validity] + + Session.create!( + authenticated_at: authenticated_at, + browser: browser, + browser_version: browser_version, + device: device, + device_key: device_key, + expires_at: refresh_token_validity_in_days.days.from_now(authenticated_at), + ip_address: ip_address, + platform: platform, + platform_version: platform_version, + user_id: user_id + ) + end end diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index fc0ed5f..0d73c22 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -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--ccGa6KAUgpYOiI7J--r6gRUrj9/eFEqb2TTcEP8Q== \ No newline at end of file +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--Ingwbstd1iUSwKNS--fpmugGsN81aPSlJWIbiX3A== \ No newline at end of file diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 1810ebd..e6c4d67 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -4,9 +4,9 @@ # Redis supports databases 0 - 15 Sidekiq.configure_server do |config| - config.redis = { url: "#{ENV.fetch('REDIS_URL') { 'redis://localhost:6379' }}/1" } + config.redis = { url: "#{ENV.fetch('REDIS_URL') { 'redis://localhost:6379' }}/0" } end Sidekiq.configure_client do |config| - config.redis = { url: "#{ENV.fetch('REDIS_URL') { 'redis://localhost:6379' }}/1" } + config.redis = { url: "#{ENV.fetch('REDIS_URL') { 'redis://localhost:6379' }}/0" } end diff --git a/config/routes.rb b/config/routes.rb index 563ecac..0b5dda2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,13 +10,20 @@ namespace :api, constraints: { format: :json } do namespace :v1 do jsonapi_resource :current_user do + # recognize this is a SINGULAR resource route! No need for Member or Collection routes jsonapi_relationships patch :sign_out, path: 'sign-out' put :sign_out, path: 'sign-out' end jsonapi_resources :roles jsonapi_resources :session_activities - jsonapi_resources :sessions + jsonapi_resources :sessions do + jsonapi_relationships + member do + patch :invalidate, path: 'invalidate' + put :invalidate, path: 'invalidate' + end + end jsonapi_resources :users end end diff --git a/db/migrate/20190127020834_add_jti_to_session_activity.rb b/db/migrate/20190127020834_add_jti_to_session_activity.rb index d151e1b..c9a079b 100644 --- a/db/migrate/20190127020834_add_jti_to_session_activity.rb +++ b/db/migrate/20190127020834_add_jti_to_session_activity.rb @@ -1,6 +1,6 @@ class AddJtiToSessionActivity < ActiveRecord::Migration[5.2] def up - add_column :session_activities, :jti, :string, null: false + add_column :session_activities, :jti, :string, index: true, null: false change_column_default :session_activities, :jti, "FAKE_DEFAULT" end diff --git a/db/migrate/20190224225123_add_access_token_to_session_activities.rb b/db/migrate/20190224225123_add_access_token_to_session_activities.rb new file mode 100644 index 0000000..315e1ee --- /dev/null +++ b/db/migrate/20190224225123_add_access_token_to_session_activities.rb @@ -0,0 +1,5 @@ +class AddAccessTokenToSessionActivities < ActiveRecord::Migration[5.2] + def change + add_column :session_activities, :access_token, :string, null: false + end +end diff --git a/db/migrate/20190225030945_add_expires_at_to_sessions.rb b/db/migrate/20190225030945_add_expires_at_to_sessions.rb new file mode 100644 index 0000000..1628038 --- /dev/null +++ b/db/migrate/20190225030945_add_expires_at_to_sessions.rb @@ -0,0 +1,5 @@ +class AddExpiresAtToSessions < ActiveRecord::Migration[5.2] + def change + add_column :sessions, :expires_at, :timestamp, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4ed8749..4fd27c1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_01_27_020834) do +ActiveRecord::Schema.define(version: 2019_02_25_030945) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -59,6 +59,7 @@ t.string "path", null: false t.bigint "session_id", null: false t.string "jti", default: "FAKE_DEFAULT", null: false + t.string "access_token", null: false t.index ["session_id"], name: "index_session_activities_on_session_id" end @@ -76,6 +77,7 @@ t.string "device_key", null: false t.datetime "invalidated_at" t.bigint "invalidated_by_id" + t.datetime "expires_at", null: false t.index ["authenticated_at", "device_key"], name: "index_sessions_on_authenticated_at_and_device_key", unique: true t.index ["invalidated_by_id"], name: "index_sessions_on_invalidated_by_id" t.index ["user_id"], name: "index_sessions_on_user_id" diff --git a/lib/ermahgerd.rb b/lib/ermahgerd.rb index 4c584cd..cbf62c1 100644 --- a/lib/ermahgerd.rb +++ b/lib/ermahgerd.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -require 'ermahgerd/authorizer' require 'ermahgerd/configuration' -require 'ermahgerd/authenticated_user' +require 'ermahgerd/controllers/concerns/authenticated_user' +require 'ermahgerd/controllers/concerns/authorizer' require 'ermahgerd/errors' +require 'ermahgerd/models/concerns/scope_by_distinct' +require 'ermahgerd/models/concerns/scope_by_id' # The Ermahgerd module has a bunch of constants and configuration elements found within. # There are also a number of very reusable concerns and classes that can be used throughout this project, or diff --git a/lib/ermahgerd/authenticated_user.rb b/lib/ermahgerd/authenticated_user.rb deleted file mode 100644 index 90d046d..0000000 --- a/lib/ermahgerd/authenticated_user.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Ermahgerd - # Controller concern that can be used to provide a `current_user` method that will return a reference to - # the authenticated `User` instance whose `Role` relations are included. - # - # The `User` instance is placed in the `@current_user` member variable. - # - # There is nothing stopping an implementor from changing this to return a JSON representation - # of this user that is stored inside the JWT access token. - module AuthenticatedUser - extend ActiveSupport::Concern - included do - private - - # See https://github.com/cybertooth-io/ermahgerd-rails-api-cognito/issues/10 - def assert_current_user! - raise ActiveRecord::RecordNotFound, 'Authenticated user not found in API server' if current_user.nil? - end - - # The current_user can be found from the email address in the token payloard - # Eagerly load the user's roles so they can be discriminated against - def current_user - @current_user ||= CurrentUser.includes(:roles).find_by(email: id_token[:email]) - end - - # In JSONAPI the `context` function returns a hash that is available at every lifecycle moments in the JSONAPI - # implementation. For Pundit in particular, it needs the current_user in order to authorize access to - # controller actions. - def context - { controller: self, current_user: current_user } - end - end - end -end diff --git a/lib/ermahgerd/authorizer.rb b/lib/ermahgerd/authorizer.rb deleted file mode 100644 index 4c2948e..0000000 --- a/lib/ermahgerd/authorizer.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -require 'json/jwt' - -module Ermahgerd - # The AWS Cognito access & id token is decoded and verified and then used in before controller hooks to determine - # whether requests are from an authenticated source. - module Authorizer - extend ActiveSupport::Concern - # rubocop:disable Metrics/BlockLength - included do - private - - # Your protected controllers should call this private method with a before-hook - # ``` - # before_action :authorize_request! # for every action in your controller - # before_action :authorize_request!, only: [:create, :destroy, :update] # include specific actions - # ``` - # Raises Ermahgerd::Errors::Unauthorized if either the id or access token is: - # 1. Missing from the headers - # 2. Have been tampered with - # 3. Cannot be decoded. - # 4. If specific claims are missing or incorrect (e.g. :email, :aud, :iss, :sub) - # 5. If the access token has expired - # @private - def authorize_request! - verify_id_token! - - verify_access_token! - - raise Ermahgerd::Errors::SignatureExpired, 'Signature has expired' unless - Time.zone.at(access_token[:exp] + Ermahgerd.configuration.access_token_leeway_seconds) > Time.zone.now - end - - # Cached per request & decoded version of the access token. - # Raises Ermahgerd::Errors::Unauthorized if the token has been tampered with or cannot be decoded. - # @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-access-token - # @private - def access_token - @access_token ||= JSON::JWT.decode(authorization_from_header!, JSON::JWK::Set.new(jwk_set)) - rescue JSON::JWT::InvalidFormat, JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound - Rails.logger.error 'Access token could not be decoded; possible key set issue or just an invalid token' - raise Ermahgerd::Errors::Unauthorized, 'Invalid ACCESS token' - end - - # Cached per request & decoded version of the id token. - # Raises Ermahgerd::Errors::Unauthorized if the token has been tampered with or cannot be decoded. - # @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-id-token - # @private - def id_token - @id_token ||= JSON::JWT.decode(identification_from_header!, JSON::JWK::Set.new(jwk_set)) - rescue JSON::JWT::InvalidFormat, JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound - Rails.logger.error 'Token could not be decoded; possible key set issue or just an invalid token' - raise Ermahgerd::Errors::Unauthorized, 'Invalid ID token' - end - - # Helpers & Supporting Methods - # ---------------------------------------------------------------------------------------------------------------- - - # Extract the `Authorization` header and split out the token from the `Bearer ...` string. - # Expecting key: `Authorization` (see Ermahgerd::HEADER_AUTHORIZATION) - # Expecting value format: `Bearer some-sort.of-token.jibberish` - # Raises Ermahgerd::Errors::Unauthorized if the token is missing. - # @private - def authorization_from_header! - raw_token = request.headers[Ermahgerd::HEADER_AUTHORIZATION] || '' - token = raw_token.split(' ')[-1] - raise Ermahgerd::Errors::Unauthorized, 'ACCESS token is not found' if token.blank? - - token - end - - # The key set from Cognito; should be in your secrets as `jwk_set` - # https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json - # NOTE ABOUT TESTING: we've created our own RSA private keys and public key sets - def jwk_set - return JSON.parse(File.read(Rails.root.join('config', 'test-jwk-set.json'))) if Rails.env.test? - - JSON.parse(Rails.application.credentials.dig(:jwk_set)) - end - - # Extract the `Identification` header value which is the id token. - # Expecting key: `Identification` (see Ermahgerd::HEADER_IDENTIFICATION) - # Expecting value format: `some-sort.of-token.jibberish` (NOTICE: NO Bearer declaration) - # Raises Ermahgerd::Errors::Unauthorized if the token is missing. - def identification_from_header! - token = request.headers[Ermahgerd::HEADER_IDENTIFICATION] || '' - raise Ermahgerd::Errors::Unauthorized, 'ID token is not found' if token.blank? - - token - end - - # Raises Ermahgerd::Errors::ClaimsVerification if the ACCESS token is missing the :auth_time and :device_key - # claim. Also raised if the :iss claim is not what is configured in the app. - # @see Ermahgerd.configuration.token_iss - def verify_access_token! - raise Ermahgerd::Errors::ClaimsVerification, 'ACCESS token claim is invalid' unless - access_token[:iss] == Ermahgerd.configuration.token_iss && access_token[:device_key].present? && - access_token[:auth_time].present? - end - - # Raises Ermahgerd::Errors::ClaimsVerification if the ID token is missing :email or :sub claims. Also raised - # if the :aud claim or :iss claim is not what is configured in the app. - # @see Ermahgerd.configuration.token_aud - # @see Ermahgerd.configuration.token_iss - def verify_id_token! - raise Ermahgerd::Errors::ClaimsVerification, 'ID token claim is invalid' unless - id_token[:aud] == Ermahgerd.configuration.token_aud && - id_token[:email].present? && - id_token[:iss] == Ermahgerd.configuration.token_iss && - id_token[:sub].present? - end - # rubocop:enable Metrics/BlockLength - end - end -end diff --git a/lib/ermahgerd/configuration.rb b/lib/ermahgerd/configuration.rb index 78cd055..a0abfb4 100644 --- a/lib/ermahgerd/configuration.rb +++ b/lib/ermahgerd/configuration.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# WhyTF does Rubocop insist I put a comment here, but not on my other classes. FML. +# The Ermahgerd namespace. module Ermahgerd # The default configuration object for the Ermahgerd AWS Cognito project. class Configuration diff --git a/lib/ermahgerd/controllers/concerns/authenticated_user.rb b/lib/ermahgerd/controllers/concerns/authenticated_user.rb new file mode 100644 index 0000000..386467e --- /dev/null +++ b/lib/ermahgerd/controllers/concerns/authenticated_user.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Ermahgerd + module Controllers + module Concerns + # Controller concern that can be used to provide a `current_user` method that will return a reference to + # the authenticated `User` instance whose `Role` relations are included. + # + # The `User` instance is placed in the `@current_user` member variable. + # + # There is nothing stopping an implementor from changing this to return a JSON representation + # of this user that is stored inside the JWT access token. + module AuthenticatedUser + extend ActiveSupport::Concern + included do + private + + # See https://github.com/cybertooth-io/ermahgerd-rails-api-cognito/issues/10 + def assert_current_user! + raise ActiveRecord::RecordNotFound, 'Authenticated user not found in API server' if current_user.nil? + end + + # The current_user can be found from the email address in the token payloard + # Eagerly load the user's roles so they can be discriminated against + def current_user + @current_user ||= CurrentUser.includes(:roles).find_by(email: id_token[:email]) + end + + # In JSONAPI the `context` function returns a hash that is available at every lifecycle moments in the JSONAPI + # implementation. For Pundit in particular, it needs the current_user in order to authorize access to + # controller actions. + def context + { controller: self, current_user: current_user } + end + end + end + end + end +end diff --git a/lib/ermahgerd/controllers/concerns/authorizer.rb b/lib/ermahgerd/controllers/concerns/authorizer.rb new file mode 100644 index 0000000..6578117 --- /dev/null +++ b/lib/ermahgerd/controllers/concerns/authorizer.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'json/jwt' + +module Ermahgerd + module Controllers + module Concerns + # The AWS Cognito access & id token is decoded and verified and then used in before controller hooks to determine + # whether requests are from an authenticated source. + module Authorizer + extend ActiveSupport::Concern + # rubocop:disable Metrics/BlockLength + included do + private + + # Your protected controllers should call this private method with a before-hook + # ``` + # before_action :authorize_request! # for every action in your controller + # before_action :authorize_request!, only: [:create, :destroy, :update] # include specific actions + # ``` + # Raises Ermahgerd::Errors::Unauthorized if either the id or access token is: + # 1. Missing from the headers + # 2. Have been tampered with + # 3. Cannot be decoded. + # 4. If specific claims are missing or incorrect (e.g. :email, :aud, :iss, :sub) + # 5. If the access token has expired + # @private + def authorize_request! + verify_id_token! + + verify_access_token! + + access_time = Time.zone.at(access_token[:exp] + Ermahgerd.configuration.access_token_leeway_seconds) + raise Ermahgerd::Errors::SignatureExpired, 'Signature has expired' unless access_time > Time.zone.now + end + + # Cached per request & decoded version of the access token. + # Raises Ermahgerd::Errors::Unauthorized if the token has been tampered with or cannot be decoded. + # @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-access-token + # @private + def access_token + @access_token ||= JSON::JWT.decode(authorization_from_header!, JSON::JWK::Set.new(jwk_set)) + rescue JSON::JWT::InvalidFormat, JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound + Rails.logger.error 'Access token could not be decoded; possible key set issue or just an invalid token' + raise Ermahgerd::Errors::Unauthorized, 'Invalid ACCESS token' + end + + # Cached per request & decoded version of the id token. + # Raises Ermahgerd::Errors::Unauthorized if the token has been tampered with or cannot be decoded. + # @see https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-user-pools-using-the-id-token + # @private + def id_token + @id_token ||= JSON::JWT.decode(identification_from_header!, JSON::JWK::Set.new(jwk_set)) + rescue JSON::JWT::InvalidFormat, JSON::JWT::VerificationFailed, JSON::JWK::Set::KidNotFound + Rails.logger.error 'Token could not be decoded; possible key set issue or just an invalid token' + raise Ermahgerd::Errors::Unauthorized, 'Invalid ID token' + end + + # Helpers & Supporting Methods + # ------------------------------------------------------------------------------------------------------------ + + # Extract the `Authorization` header and split out the token from the `Bearer ...` string. + # Expecting key: `Authorization` (see Ermahgerd::HEADER_AUTHORIZATION) + # Expecting value format: `Bearer some-sort.of-token.jibberish` + # This raw token is cached because the `RecordSessionActivityWorker` will use this information + # It is not performant to do this parsing multiple times + # Raises Ermahgerd::Errors::Unauthorized if the token is missing. + # @private + def authorization_from_header! + return @access_token_raw if @access_token_raw.present? + + raw_token = request.headers[Ermahgerd::HEADER_AUTHORIZATION] || '' + token = raw_token.split(' ')[-1] + raise Ermahgerd::Errors::Unauthorized, 'ACCESS token is not found' if token.blank? + + @access_token_raw = token + end + + # The key set from Cognito; should be in your secrets as `jwk_set` + # https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json + # NOTE ABOUT TESTING: we've created our own RSA private keys and public key sets + def jwk_set + return JSON.parse(File.read(Rails.root.join('config', 'test-jwk-set.json'))) if Rails.env.test? + + JSON.parse(Rails.application.credentials.dig(:jwk_set)) + end + + # Extract the `Identification` header value which is the id token. + # Expecting key: `Identification` (see Ermahgerd::HEADER_IDENTIFICATION) + # Expecting value format: `some-sort.of-token.jibberish` (NOTICE: NO Bearer declaration) + # Raises Ermahgerd::Errors::Unauthorized if the token is missing. + def identification_from_header! + token = request.headers[Ermahgerd::HEADER_IDENTIFICATION] || '' + raise Ermahgerd::Errors::Unauthorized, 'ID token is not found' if token.blank? + + token + end + + # Raises Ermahgerd::Errors::ClaimsVerification if the ACCESS token is missing the :auth_time and :device_key + # claim. Also raised if the :iss claim is not what is configured in the app. + # @see Ermahgerd.configuration.token_iss + def verify_access_token! + unless access_token[:iss] == Ermahgerd.configuration.token_iss && access_token[:device_key].present? && + access_token[:auth_time].present? + raise Ermahgerd::Errors::ClaimsVerification, 'ACCESS token claim is invalid' + end + end + + # Raises Ermahgerd::Errors::ClaimsVerification if the ID token is missing :email or :sub claims. Also raised + # if the :aud claim or :iss claim is not what is configured in the app. + # @see Ermahgerd.configuration.token_aud + # @see Ermahgerd.configuration.token_iss + def verify_id_token! + unless id_token[:aud] == Ermahgerd.configuration.token_aud && + id_token[:email].present? && + id_token[:iss] == Ermahgerd.configuration.token_iss && + id_token[:sub].present? + raise Ermahgerd::Errors::ClaimsVerification, 'ID token claim is invalid' + end + end + # rubocop:enable Metrics/BlockLength + end + end + end + end +end diff --git a/lib/ermahgerd/models/concerns/scope_by_distinct.rb b/lib/ermahgerd/models/concerns/scope_by_distinct.rb new file mode 100644 index 0000000..2e24977 --- /dev/null +++ b/lib/ermahgerd/models/concerns/scope_by_distinct.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Ermahgerd + module Models + module Concerns + # A very simple concern that provides the `by_id` scope to all models. + # Unit tested in the `User` model test. + module ScopeByDistinct + extend ActiveSupport::Concern + included do + scope :by_distinct, -> { distinct } + end + end + end + end +end diff --git a/lib/ermahgerd/models/concerns/scope_by_id.rb b/lib/ermahgerd/models/concerns/scope_by_id.rb new file mode 100644 index 0000000..6c69c93 --- /dev/null +++ b/lib/ermahgerd/models/concerns/scope_by_id.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Ermahgerd + module Models + module Concerns + # A very simple concern that provides the `by_id` scope to all models. + # Unit tested in the `User` model test. + module ScopeById + extend ActiveSupport::Concern + included do + scope :by_id, ->(ids) { where id: ids } + end + end + end + end +end diff --git a/test/controllers/api/v1/sessions_controller_test.rb b/test/controllers/api/v1/sessions_controller_test.rb index 19cb2ee..22edf70 100644 --- a/test/controllers/api/v1/sessions_controller_test.rb +++ b/test/controllers/api/v1/sessions_controller_test.rb @@ -5,6 +5,44 @@ module Api module V1 class SessionsControllerTest < ActionDispatch::IntegrationTest + test 'when invalidate' do + session = sessions(:sterling_archer_session) + + assert_difference ['InvalidateCognitoSessionWorker.jobs.size'] do + patch invalidate_api_v1_session_url(session), + headers: auth(users(:some_administrator)), + params: { + data: { + id: session.id, + type: 'sessions' + } + }.to_json + end + + assert_response :ok + end + + test 'when index and include last session activity' do + get api_v1_sessions_url, headers: auth(users(:some_administrator)), params: { + include: 'last-session-activity' + } + + assert_response :ok + end + + test 'when index and include all session activities' do + get api_v1_sessions_url, headers: auth(users(:some_administrator)), params: { + include: 'session-activities' + } + + assert_response :ok + end + + test 'when index' do + get api_v1_sessions_url, headers: auth(users(:some_administrator)) + + assert_response :ok + end end end end diff --git a/test/fixtures/roles_users.yml b/test/fixtures/roles_users.yml new file mode 100644 index 0000000..c7597de --- /dev/null +++ b/test/fixtures/roles_users.yml @@ -0,0 +1 @@ +# this is the linking table for the Has And Belongs To Many relationship between users & roles diff --git a/test/fixtures/session_activities.yml b/test/fixtures/session_activities.yml index 426a89a..59ff24b 100644 --- a/test/fixtures/session_activities.yml +++ b/test/fixtures/session_activities.yml @@ -1,4 +1,5 @@ sterling_archer_session_activity: + access_token: xxxxx.yyyyy.zzzzz created_at: <%= -10.seconds.from_now %> ip_address: 1.2.3.4 jti: vvvvvvvv-wwww-xxxx-yyyy-zzzzzzzzzzzz diff --git a/test/fixtures/sessions.yml b/test/fixtures/sessions.yml index 8bb70f7..0572f0d 100644 --- a/test/fixtures/sessions.yml +++ b/test/fixtures/sessions.yml @@ -4,6 +4,7 @@ sterling_archer_session: browser_version: 6 device: Unknown device_key: uu-region-#_vvvvvvvv-wwww-xxxx-yyyy-zzzzzzzzzzzz + expires_at: <%= 30.days.from_now %> invalidated_at: invalidated_by: ip_address: 1.2.3.4 diff --git a/test/models/roles_user_test.rb b/test/models/roles_user_test.rb new file mode 100644 index 0000000..9c8f5e4 --- /dev/null +++ b/test/models/roles_user_test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'test_helper' + +class RolesUserTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/session_test.rb b/test/models/session_test.rb index 0ffa3da..761798e 100644 --- a/test/models/session_test.rb +++ b/test/models/session_test.rb @@ -3,6 +3,66 @@ require 'test_helper' class SessionTest < ActiveSupport::TestCase + test 'when fetching the last session activity' do + assert_equal( + session_activities(:sterling_archer_session_activity).access_token, + sessions(:sterling_archer_session).last_session_activity.access_token + ) + end + + test 'when filtering by validated' do + assert_equal 0, Session.by_invalidated.count + sessions(:sterling_archer_session).update! invalidated_at: Time.zone.now + assert_equal 1, Session.by_invalidated.count + end + + test 'when filtering by active' do + assert_equal 1, Session.by_active.count + sessions(:sterling_archer_session).update! invalidated_at: Time.zone.now + assert_equal 0, Session.by_active.count + end + + test 'when adding a session that already exists' do + sterling_archer_session = sessions(:sterling_archer_session) + + session = Session.new( + authenticated_at: sterling_archer_session.authenticated_at, + browser: 'Internet Exploder', + browser_version: 6, + device: 'Unknown', + device_key: sterling_archer_session.device_key, + invalidated_at: nil, + invalidated_by: nil, + ip_address: '1.2.3.4', + platform: 'Windows XP', + platform_version: '5.1.2600', + user: users(:some_guest) + ) + + assert_not session.valid? + end + + test 'when adding a session that does not already exist' do + sterling_archer_session = sessions(:sterling_archer_session) + + session = Session.new( + authenticated_at: sterling_archer_session.authenticated_at, + browser: 'Internet Exploder', + browser_version: 6, + device: 'Unknown', + device_key: 'uu-region-#_vvvvvvvv-wwww-xxxx-yyyy-012345678901', + expires_at: sterling_archer_session.expires_at, + invalidated_at: nil, + invalidated_by: nil, + ip_address: '1.2.3.4', + platform: 'Windows XP', + platform_version: '5.1.2600', + user: users(:some_guest) + ) + + assert session.valid? + end + test 'when filtering by distinct' do assert_equal Session.all.count, Session.all.by_distinct.count end diff --git a/test/policies/session_policy_test.rb b/test/policies/session_policy_test.rb index 38f37cd..2eb6efc 100644 --- a/test/policies/session_policy_test.rb +++ b/test/policies/session_policy_test.rb @@ -18,12 +18,21 @@ class SessionPolicyTest < ActiveSupport::TestCase assert SessionPolicy.new(users(:some_guest), Session).index? end + test 'when invalidate' do + assert SessionPolicy.new(users(:some_administrator), sessions(:sterling_archer_session)).invalidate? + assert_not SessionPolicy.new(users(:some_guest), sessions(:sterling_archer_session)).invalidate? + end + + test 'when invalidate mine' do + assert SessionPolicy.new(users(:sterling_archer), sessions(:sterling_archer_session)).invalidate? + end + test 'when show' do assert SessionPolicy.new(users(:some_administrator), sessions(:sterling_archer_session)).show? assert_not SessionPolicy.new(users(:some_guest), sessions(:sterling_archer_session)).show? end - test 'when showing my user record' do + test 'when showing mine' do assert SessionPolicy.new(users(:sterling_archer), sessions(:sterling_archer_session)).show? end diff --git a/test/workers/invalidate_cognito_session_worker_test.rb b/test/workers/invalidate_cognito_session_worker_test.rb new file mode 100644 index 0000000..5d4f552 --- /dev/null +++ b/test/workers/invalidate_cognito_session_worker_test.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'test_helper' + +class InvalidateCognitoSessionWorkerTest < ActiveSupport::TestCase + test 'when session id does not yield a session' do + assert_raises ActiveRecord::RecordNotFound do + InvalidateCognitoSessionWorker.new.perform(Time.zone.now.iso8601, users(:some_administrator).id, -1) + end + end + + test 'when Cognito errors to sign out' do + User::COGNITO.stub_responses( + :global_sign_out, + Aws::CognitoIdentityProvider::Errors::ResourceNotFoundException.new({}, 'Some Sort Of Error') + ) + + sterling = users(:sterling_archer) + session = sterling.sessions.first + + assert_raises Aws::CognitoIdentityProvider::Errors::ResourceNotFoundException do + InvalidateCognitoSessionWorker.new.perform(Time.zone.now.iso8601, sterling.id, session.id) + end + end + + test 'when session is invalidated' do + User::COGNITO.stub_responses(:global_sign_out) + + sterling = users(:sterling_archer) + session = sterling.sessions.first + + assert_changes -> { [session.reload.invalidated_at, session.reload.invalidated_by] } do + InvalidateCognitoSessionWorker.new.perform(Time.zone.now.iso8601, sterling.id, session.id) + end + end +end diff --git a/test/workers/record_session_activity_worker_test.rb b/test/workers/record_session_activity_worker_test.rb index 97d4658..c910cba 100644 --- a/test/workers/record_session_activity_worker_test.rb +++ b/test/workers/record_session_activity_worker_test.rb @@ -3,9 +3,12 @@ require 'test_helper' class RecordSessionActivityWorkerTest < ActiveSupport::TestCase - test 'when session & session activity is successfully created' do + test 'when session and session activity is successfully created' do + User::COGNITO.stub_responses(:describe_user_pool_client) + assert_difference ['Session.count', 'SessionActivity.count'] do RecordSessionActivityWorker.new.perform( + 'xxxxx.yyyyy.zzzzz', 'someBrowser', 'someBrowserVersion', -10.seconds.from_now.iso8601, @@ -28,6 +31,7 @@ class RecordSessionActivityWorkerTest < ActiveSupport::TestCase assert_difference ['SessionActivity.count'] do assert_no_difference ['Session.count'] do RecordSessionActivityWorker.new.perform( + 'xxxxx.yyyyy.zzzzz', 'someBrowser', 'someBrowserVersion', -10.seconds.from_now.iso8601,