api_engine_base 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/api_engine_base/application_controller.rb +47 -0
  6. data/app/controllers/api_engine_base/auth/plain_text_controller.rb +132 -0
  7. data/app/controllers/api_engine_base/username_controller.rb +26 -0
  8. data/app/controllers/concerns/api_engine_base/schematizable.rb +5 -0
  9. data/app/helpers/api_engine_base/application_helper.rb +4 -0
  10. data/app/helpers/api_engine_base/schema_helper.rb +29 -0
  11. data/app/jobs/api_engine_base/application_job.rb +4 -0
  12. data/app/mailers/api_engine_base/application_mailer.rb +8 -0
  13. data/app/mailers/api_engine_base/email_verification_mailer.rb +12 -0
  14. data/app/models/api_engine_base/application_record.rb +7 -0
  15. data/app/models/user.rb +50 -0
  16. data/app/models/user_secret.rb +72 -0
  17. data/app/services/api_engine_base/argument_validation/class_methods.rb +179 -0
  18. data/app/services/api_engine_base/argument_validation/instance_methods.rb +136 -0
  19. data/app/services/api_engine_base/argument_validation.rb +11 -0
  20. data/app/services/api_engine_base/jwt/authenticate_user.rb +71 -0
  21. data/app/services/api_engine_base/jwt/decode.rb +21 -0
  22. data/app/services/api_engine_base/jwt/encode.rb +15 -0
  23. data/app/services/api_engine_base/jwt/login_create.rb +21 -0
  24. data/app/services/api_engine_base/jwt/time_delay_token.rb +17 -0
  25. data/app/services/api_engine_base/login_strategy/plain_text/create.rb +42 -0
  26. data/app/services/api_engine_base/login_strategy/plain_text/email_verification/generate.rb +29 -0
  27. data/app/services/api_engine_base/login_strategy/plain_text/email_verification/required.rb +20 -0
  28. data/app/services/api_engine_base/login_strategy/plain_text/email_verification/send.rb +23 -0
  29. data/app/services/api_engine_base/login_strategy/plain_text/email_verification/verify.rb +24 -0
  30. data/app/services/api_engine_base/login_strategy/plain_text/login.rb +50 -0
  31. data/app/services/api_engine_base/secrets/cleanse.rb +14 -0
  32. data/app/services/api_engine_base/secrets/generate.rb +62 -0
  33. data/app/services/api_engine_base/secrets/verify.rb +27 -0
  34. data/app/services/api_engine_base/secrets.rb +15 -0
  35. data/app/services/api_engine_base/service_base.rb +90 -0
  36. data/app/services/api_engine_base/service_logging.rb +41 -0
  37. data/app/services/api_engine_base/username/available.rb +64 -0
  38. data/app/views/api_engine_base/email_verification_mailer/verify_email.html.erb +26 -0
  39. data/config/routes.rb +23 -0
  40. data/db/migrate/20241117043720_create_api_engine_base_users.rb +33 -0
  41. data/db/migrate/20241204065708_create_api_engine_base_user_secrets.rb +16 -0
  42. data/lib/api_engine_base/configuration/application/config.rb +40 -0
  43. data/lib/api_engine_base/configuration/base.rb +11 -0
  44. data/lib/api_engine_base/configuration/config.rb +59 -0
  45. data/lib/api_engine_base/configuration/email/config.rb +87 -0
  46. data/lib/api_engine_base/configuration/jwt/config.rb +22 -0
  47. data/lib/api_engine_base/configuration/login/config.rb +18 -0
  48. data/lib/api_engine_base/configuration/login/strategy/plain_text/config.rb +57 -0
  49. data/lib/api_engine_base/configuration/login/strategy/plain_text/email_verify.rb +50 -0
  50. data/lib/api_engine_base/configuration/login/strategy/plain_text/lockable.rb +27 -0
  51. data/lib/api_engine_base/configuration/otp/config.rb +54 -0
  52. data/lib/api_engine_base/configuration/username/check.rb +31 -0
  53. data/lib/api_engine_base/configuration/username/config.rb +41 -0
  54. data/lib/api_engine_base/engine.rb +21 -0
  55. data/lib/api_engine_base/schema/error/base.rb +15 -0
  56. data/lib/api_engine_base/schema/error/invalid_argument.rb +15 -0
  57. data/lib/api_engine_base/schema/error/invalid_argument_response.rb +17 -0
  58. data/lib/api_engine_base/schema/plain_text/create_user_request.rb +18 -0
  59. data/lib/api_engine_base/schema/plain_text/create_user_response.rb +17 -0
  60. data/lib/api_engine_base/schema/plain_text/email_verify_request.rb +11 -0
  61. data/lib/api_engine_base/schema/plain_text/email_verify_response.rb +11 -0
  62. data/lib/api_engine_base/schema/plain_text/email_verify_send_request.rb +9 -0
  63. data/lib/api_engine_base/schema/plain_text/email_verify_send_response.rb +11 -0
  64. data/lib/api_engine_base/schema/plain_text/login_request.rb +15 -0
  65. data/lib/api_engine_base/schema/plain_text/login_response.rb +13 -0
  66. data/lib/api_engine_base/schema.rb +25 -0
  67. data/lib/api_engine_base/spec_helper.rb +18 -0
  68. data/lib/api_engine_base/version.rb +5 -0
  69. data/lib/api_engine_base.rb +33 -0
  70. data/lib/generators/api_engine_base/configure/USAGE +8 -0
  71. data/lib/generators/api_engine_base/configure/configure_generator.rb +12 -0
  72. data/lib/tasks/auto_annotate_models.rake +60 -0
  73. metadata +216 -0
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+
5
+ module ApiEngineBase
6
+ module Configuration
7
+ module Otp
8
+ class Config
9
+ include ClassComposer::Generator
10
+
11
+ add_composer :default_code_interval,
12
+ desc: "The length of time a code is good for",
13
+ allowed: ActiveSupport::Duration,
14
+ default: 30.seconds,
15
+ validator: -> (val) { (val <= 5.minutes) && (val >= 30.seconds) }
16
+
17
+ add_composer :default_code_length,
18
+ desc: "The default length of the OTP. Used when one not provided",
19
+ allowed: Integer,
20
+ default: 6,
21
+ validator: -> (val) { (val <= 10) && (val >= 4) },
22
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than or equal to 10 and greater than or equal to 4." }
23
+
24
+ add_composer :secret_code_length,
25
+ desc: "The size of each users base32 Secret value generated",
26
+ allowed: Integer,
27
+ default: 32,
28
+ validator: -> (val) { (val <= 128) && (val >= 32) },
29
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than or equal to 128 and greater than or equal to 32." }
30
+
31
+ add_composer :backup_code_length,
32
+ desc: "The length of each backup code for User",
33
+ allowed: Integer,
34
+ default: 32,
35
+ validator: -> (val) { (val <= 64) && (val >= 10) },
36
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than or equal to 64 and greater than or equal to 20." }
37
+
38
+ add_composer :backup_code_count,
39
+ desc: "The number of backup codes that get generated",
40
+ allowed: Integer,
41
+ default: 10,
42
+ validator: -> (val) { (val <= 10) && (val >= 2) },
43
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than or equal to 10 and greater than or equal to 2." }
44
+
45
+ add_composer :allowed_drift_behind,
46
+ desc: "Sometimes a user is just a tad slow. This allows a small drift behind to allow codes within drift to be accepted",
47
+ allowed: ActiveSupport::Duration,
48
+ default: 15.seconds,
49
+ validator: -> (val) { (val <= 15.seconds) && (val >= 0.seconds) },
50
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than or equal to 15.seconds" }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+
5
+ module ApiEngineBase
6
+ module Configuration
7
+ module Username
8
+ class Check
9
+ include ClassComposer::Generator
10
+
11
+ add_composer :enable,
12
+ desc: "Enable Controller method for checking Real time username availability",
13
+ allowed: [FalseClass, TrueClass],
14
+ default: true
15
+
16
+ add_composer :local_cache,
17
+ desc: "Local Cache store. Instantiated before fork",
18
+ allowed: [ActiveSupport::Cache::MemoryStore, ActiveSupport::Cache::FileStore],
19
+ default: ActiveSupport::Cache::MemoryStore.new
20
+
21
+ add_composer :local_cache_ttl,
22
+ desc: "TTL on local cache data before data is invalidated and upstream is queried",
23
+ allowed: ActiveSupport::Duration,
24
+ default_shown: "1.minutes",
25
+ default: 1.minute,
26
+ validator: -> (val) { val < 60.minutes },
27
+ invalid_message: ->(val) { "Provided #{val}. Value must be less than #{60.minutes}" }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+ require "api_engine_base/configuration/username/check"
5
+
6
+ module ApiEngineBase::Configuration
7
+ module Username
8
+ class Config
9
+ include ClassComposer::Generator
10
+
11
+ DEFAULT_MAX_LENGTH = 32
12
+ DEFAULT_MIN_LENGTH = 4
13
+
14
+ add_composer_blocking :realtime_username_check,
15
+ desc: "Adds components to check if the username is available in real time",
16
+ composer_class: Check,
17
+ enable_attr: :enable
18
+
19
+ add_composer :username_length_min,
20
+ desc: "Min Length for Username",
21
+ allowed: Integer,
22
+ default: DEFAULT_MIN_LENGTH
23
+
24
+ add_composer :username_length_max,
25
+ desc: "Max Length for Username",
26
+ allowed: Integer,
27
+ default: DEFAULT_MAX_LENGTH
28
+
29
+ add_composer :username_regex,
30
+ desc: "Regex for username.",
31
+ allowed: Regexp,
32
+ default: /\A\w{#{DEFAULT_MIN_LENGTH},#{DEFAULT_MAX_LENGTH}}\z/,
33
+ default_shown: "Regexp.new(\"/\A\w{#{DEFAULT_MIN_LENGTH},#{DEFAULT_MAX_LENGTH}}\z/\")"
34
+
35
+ add_composer :username_failure_message,
36
+ desc: "Max Length for Username",
37
+ allowed: String,
38
+ default: "Username length must be between #{DEFAULT_MIN_LENGTH} and #{DEFAULT_MAX_LENGTH}. Must contain only letters and/or numbers"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "api_engine_base/schema"
4
+
5
+ module ApiEngineBase
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace ApiEngineBase
8
+
9
+ # Run after Rails loads the initializes and environment files
10
+ # Ensures User has already set their desired config before we lock this down
11
+ initializer "api_engine_base.config.instantiate", after: :load_config_initializers do |_app|
12
+ # ensure defaults are instantiated and all variables are assigned
13
+ ApiEngineBase.config.class_composer_assign_defaults!(children: true)
14
+
15
+ unless Rails.env.test?
16
+ # Now that we can confirm all variables are defined, freeze all objects an their children
17
+ ApiEngineBase.config.class_composer_freeze_objects!(behavior: :raise, children: true)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json_schematize/generator"
4
+
5
+ module ApiEngineBase
6
+ module Schema
7
+ module Error
8
+ class Base < JsonSchematize::Generator
9
+ add_field name: :status, type: String
10
+ add_field name: :message, type: String
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module Error
6
+ class InvalidArgument < JsonSchematize::Generator
7
+ add_field name: :schema, type: JsonSchematize::Generator, required: true, converter: ->(val) { val }
8
+ add_field name: :argument, type: String, required: true
9
+ add_field name: :argument_type, type: String, required: true
10
+ add_field name: :reason, type: String
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "api_engine_base/schema/error/invalid_argument"
4
+
5
+ module ApiEngineBase
6
+ module Schema
7
+ module Error
8
+ class InvalidArgumentResponse < JsonSchematize::Generator
9
+ add_field name: :message, type: String, required: true
10
+ add_field name: :status, type: String, required: true
11
+ add_field name: :invalid_arguments, array_of_types: true, type: InvalidArgument
12
+ add_field name: :invalid_argument_keys, type: Array
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class CreateUserRequest < JsonSchematize::Generator
7
+ schema_default option: :dig_type, value: :string
8
+
9
+ add_field name: :first_name, type: String, required: false
10
+ add_field name: :last_name, type: String, required: false
11
+ add_field name: :username, type: String, required: false
12
+ add_field name: :email, type: String, required: false
13
+ add_field name: :password, type: String, required: false
14
+ add_field name: :password_confirmation, type: String, required: false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class CreateUserResponse < JsonSchematize::Generator
7
+ add_field name: :full_name, type: String
8
+ add_field name: :first_name, type: String
9
+ add_field name: :last_name, type: String
10
+ add_field name: :username, type: String
11
+ add_field name: :email, type: String
12
+ add_field name: :msg, type: String
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class EmailVerifyRequest < JsonSchematize::Generator
7
+ add_field name: :code, type: String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class EmailVerifyResponse< JsonSchematize::Generator
7
+ add_field name: :message, type: String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class EmailVerifySendRequest < JsonSchematize::Generator; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class EmailVerifySendResponse< JsonSchematize::Generator
7
+ add_field name: :message, type: String
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class LoginRequest < JsonSchematize::Generator
7
+ schema_default option: :dig_type, value: :string
8
+
9
+ add_field name: :username, type: String, required: false
10
+ add_field name: :email, type: String, required: false
11
+ add_field name: :password, type: String, required: false
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ module PlainText
6
+ class LoginResponse < JsonSchematize::Generator
7
+ add_field name: :token, type: String
8
+ add_field name: :header_name, type: String
9
+ add_field name: :message, type: String
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module Schema
5
+ require "json_schematize"
6
+ require "json_schematize/generator"
7
+
8
+ ## Generic Error Schemas
9
+ require "api_engine_base/schema/error/base"
10
+ require "api_engine_base/schema/error/invalid_argument_response"
11
+
12
+ ## Plain Text Controller
13
+ require "api_engine_base/schema/plain_text/create_user_response"
14
+ require "api_engine_base/schema/plain_text/create_user_request"
15
+
16
+ require "api_engine_base/schema/plain_text/email_verify_request"
17
+ require "api_engine_base/schema/plain_text/email_verify_response"
18
+
19
+ require "api_engine_base/schema/plain_text/email_verify_send_response"
20
+ require "api_engine_base/schema/plain_text/email_verify_send_request"
21
+
22
+ require "api_engine_base/schema/plain_text/login_request"
23
+ require "api_engine_base/schema/plain_text/login_response"
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ module SpecHelper
5
+ def set_jwt_token!(user:, token: nil)
6
+ if token.nil?
7
+ result = ApiEngineBase::Jwt::LoginCreate.(user:)
8
+ token = result.token
9
+ end
10
+
11
+ @request.headers[ApiEngineBase::ApplicationController::AUTHORIZATION_HEADER] = "Bearer: #{token}"
12
+ end
13
+
14
+ def unset_jwt_token!
15
+ @request.headers[ApiEngineBase::ApplicationController::AUTHORIZATION_HEADER] = nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiEngineBase
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,33 @@
1
+ require "api_engine_base/version"
2
+ require "api_engine_base/engine"
3
+ require "api_engine_base/configuration/config"
4
+
5
+ module ApiEngineBase
6
+ class Error < StandardError; end
7
+
8
+ def self.config
9
+ @config ||= Configuration::Config.new
10
+ end
11
+
12
+ def self.configure
13
+ yield(config)
14
+ end
15
+
16
+ def self.config=(configuration)
17
+ raise ArgumentError, "Expected Configuration::Config. Given #{configuration.class}" unless Configuration::Config === configuration
18
+
19
+ @config = configuration
20
+ end
21
+
22
+ def self.app_name
23
+ Proc === config.app.app_name ? config.app.app_name.() : config.app.app_name
24
+ end
25
+
26
+ def self.app_name_for_comms
27
+ Proc === config.app.communication_name ? config.app.communication_name.() : config.app.communication_name
28
+ end
29
+
30
+ def self.default_app_name
31
+ ::Rails.application.class.module_parent_name
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Creates a default Configuration file for the ApiBaseEngine.
3
+
4
+ Example:
5
+ bin/rails generate api_engine_base:configure
6
+
7
+ This will create:
8
+ config/initializers/api_engine_base.rb
@@ -0,0 +1,12 @@
1
+ class ApiEngineBase::ConfigureGenerator < Rails::Generators::Base
2
+ source_root File.expand_path("templates", __dir__)
3
+
4
+ def create_config_file
5
+ create_file Rails.root.join("config", "initializers", "api_engine_base.rb"),
6
+ ApiEngineBase.config.class.composer_generate_config(wrapping: "ApiEngineBase.configure", require_file: "api_engine_base")
7
+ end
8
+
9
+ def create_route
10
+ route "mount ApiEngineBase::Engine => \"/\""
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ # NOTE: only doing this in development as some production environments (Heroku)
2
+ # NOTE: are sensitive to local FS writes, and besides -- it's just not proper
3
+ # NOTE: to have a dev-mode tool do its thing in production.
4
+
5
+ # if Rails.env.development?
6
+ require 'annotate'
7
+ task :set_annotation_options do
8
+ # You can override any of these by setting an environment variable of the
9
+ # same name.
10
+ Annotate.set_defaults(
11
+ 'active_admin' => 'false',
12
+ 'additional_file_patterns' => [],
13
+ 'routes' => 'false',
14
+ 'models' => 'true',
15
+ 'position_in_routes' => 'before',
16
+ 'position_in_class' => 'before',
17
+ 'position_in_test' => 'before',
18
+ 'position_in_fixture' => 'before',
19
+ 'position_in_factory' => 'before',
20
+ 'position_in_serializer' => 'before',
21
+ 'show_foreign_keys' => 'true',
22
+ 'show_complete_foreign_keys' => 'false',
23
+ 'show_indexes' => 'true',
24
+ 'simple_indexes' => 'false',
25
+ 'model_dir' => 'app/models',
26
+ 'root_dir' => '',
27
+ 'include_version' => 'false',
28
+ 'require' => '',
29
+ 'exclude_tests' => 'false',
30
+ 'exclude_fixtures' => 'false',
31
+ 'exclude_factories' => 'false',
32
+ 'exclude_serializers' => 'false',
33
+ 'exclude_scaffolds' => 'true',
34
+ 'exclude_controllers' => 'true',
35
+ 'exclude_helpers' => 'true',
36
+ 'exclude_sti_subclasses' => 'false',
37
+ 'ignore_model_sub_dir' => 'false',
38
+ 'ignore_columns' => nil,
39
+ 'ignore_routes' => nil,
40
+ 'ignore_unknown_models' => 'false',
41
+ 'hide_limit_column_types' => 'integer,bigint,boolean',
42
+ 'hide_default_column_types' => 'json,jsonb,hstore',
43
+ 'skip_on_db_migrate' => 'false',
44
+ 'format_bare' => 'true',
45
+ 'format_rdoc' => 'false',
46
+ 'format_yard' => 'false',
47
+ 'format_markdown' => 'false',
48
+ 'sort' => 'false',
49
+ 'force' => 'false',
50
+ 'frozen' => 'false',
51
+ 'classified_sort' => 'true',
52
+ 'trace' => 'false',
53
+ 'wrapper_open' => nil,
54
+ 'wrapper_close' => nil,
55
+ 'with_comment' => 'true'
56
+ )
57
+ end
58
+
59
+ Annotate.load_tasks
60
+ # end