api_engine_base 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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