with_accounts 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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/controllers/with_accounts/application_controller.rb +54 -0
  6. data/app/controllers/with_accounts/meaningful_controller.rb +44 -0
  7. data/app/models/with_accounts/account.rb +41 -0
  8. data/app/models/with_accounts/application_record.rb +5 -0
  9. data/app/scenarios/with_accounts/jwts/access_tokens_creating/check_refresh_token.rb +23 -0
  10. data/app/scenarios/with_accounts/jwts/access_tokens_creating/find_and_check_account.rb +30 -0
  11. data/app/scenarios/with_accounts/jwts/access_tokens_creating/prepare_access_token_payload.rb +15 -0
  12. data/app/scenarios/with_accounts/jwts/access_tokens_creating/scenario.rb +15 -0
  13. data/app/scenarios/with_accounts/jwts/create_token.rb +10 -0
  14. data/app/scenarios/with_accounts/jwts/refresh_tokens_creating/check_login_invitation.rb +32 -0
  15. data/app/scenarios/with_accounts/jwts/refresh_tokens_creating/prepare_refresh_token_payload.rb +15 -0
  16. data/app/scenarios/with_accounts/jwts/refresh_tokens_creating/scenario.rb +15 -0
  17. data/app/scenarios/with_accounts/jwts/refresh_tokens_creating/update_account.rb +21 -0
  18. data/app/scenarios/with_accounts/login_invitations_creating/check_params.rb +20 -0
  19. data/app/scenarios/with_accounts/login_invitations_creating/find_and_check_account.rb +21 -0
  20. data/app/scenarios/with_accounts/login_invitations_creating/scenario.rb +13 -0
  21. data/app/scenarios/with_accounts/login_invitations_creating/send_email_to_user.rb +15 -0
  22. data/app/scenarios/with_accounts/login_invitations_creating/update_account_login_invitation.rb +24 -0
  23. data/app/scenarios/with_accounts/passwords_creating/check_params.rb +24 -0
  24. data/app/scenarios/with_accounts/passwords_creating/check_password_set_invitation.rb +31 -0
  25. data/app/scenarios/with_accounts/passwords_creating/scenario.rb +12 -0
  26. data/app/scenarios/with_accounts/passwords_creating/update_account.rb +28 -0
  27. data/config/routes.rb +8 -0
  28. data/db/migrate/20231004123741_create_with_accounts_accounts.rb +20 -0
  29. data/lib/tasks/with_accounts_tasks.rake +4 -0
  30. data/lib/with_accounts/engine.rb +10 -0
  31. data/lib/with_accounts/errors_helpers.rb +54 -0
  32. data/lib/with_accounts/version.rb +3 -0
  33. data/lib/with_accounts.rb +6 -0
  34. metadata +104 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0dfa1a4d1da17c960a154c17640b0d3d493c1edb625effb5833a3b0f11a02ed1
4
+ data.tar.gz: 86e20e8238250680d21155e061335571b4ca917378b690d8efb98f36a7aa94eb
5
+ SHA512:
6
+ metadata.gz: 7ba1aa74f8316fff0988ae5d548e2d1fa1067b4a19a8f247da2d6fb6913105eae057a1ffca83522e331c28f6b3a0c33680e679ee2c17333215fe2462e0c3cb4f
7
+ data.tar.gz: b9593e21e1a3098e3fc201ab40425300443d1bdf833d64d8d7dbf33850d218c71c8cf8a589d5cd268c8f359e22bad7868610460b786d2a7cb6e2f5c501ea748b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2023 М.Забодаев
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # WithAccounts
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "with_accounts"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install with_accounts
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
@@ -0,0 +1,54 @@
1
+ module WithAccounts
2
+ class ApplicationController < ActionController::API
3
+ include ErrorsHelpers
4
+
5
+ before_action :snake_case_params
6
+ after_action :set_json_content_type
7
+ around_action :render_around_action_errors
8
+
9
+ @@log_errors = ENV['LOG_ERRORS'] == 'true' ||
10
+ Rails.env.in?(%w[development test staging]) ||
11
+ false
12
+
13
+ def render_errors(errors_hash, status = :bad_request)
14
+ if @@log_errors
15
+ logger.warn(errors_hash)
16
+ render_low_camel_cased({ errors: format_errors(errors_hash) }, status)
17
+ else
18
+ render body: '{}', status: :bad_request
19
+ end
20
+ end
21
+
22
+ def render_low_camel_cased(hash_or_array_of_hashes, status)
23
+ low_camelized = case hash_or_array_of_hashes
24
+ when Hash
25
+ hash_or_array_of_hashes.deep_transform_keys { _1.to_s.camelize(:lower) }
26
+ when Array
27
+ hash_or_array_of_hashes.map { |hash| hash.deep_transform_keys { _1.to_s.camelize(:lower) } }
28
+ else
29
+ raise StandardError
30
+ end
31
+ st = status.is_a?(Integer) ? status : status.to_sym
32
+ render json: low_camelized, status: st
33
+ end
34
+
35
+ def set_json_content_type
36
+ response.headers['Content-Type'] = 'application/json; charset=utf-8'
37
+ end
38
+
39
+ def render_around_action_errors
40
+ yield
41
+ rescue StandardError => e
42
+ message = 'filtered'
43
+ if @@log_errors
44
+ message = Rails.env.production? ? e.message : e.full_message
45
+ end
46
+ render_low_camel_cased({ errors: {internal_server_error: message} }, :internal_server_error)
47
+ end
48
+
49
+ def snake_case_params
50
+ params.to_snake_case!
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ module WithAccounts
2
+ class MeaningfulController < ApplicationController
3
+ def not_found
4
+ render body: '{"errors": "not found"}', status: 404
5
+ end
6
+
7
+ def create_login_invitation
8
+ performance = LoginInvitationsCreating::Scenario.new(params: params).act_out!
9
+ (render_errors(performance.errors) && return) unless performance.success?
10
+
11
+ render status: :no_content
12
+ end
13
+
14
+ def create_tokens
15
+ data = { access_token: '', refresh_token: '' }
16
+ performance = Jwts::RefreshTokensCreating::Scenario.new(params: params).act_out!
17
+ (render_errors(performance.errors) && return) unless performance.success?
18
+
19
+ data.merge!(refresh_token: performance.stage.new_token)
20
+ performance = Jwts::AccessTokensCreating::Scenario.new(params: { refresh_token: data[:refresh_token] }).act_out!
21
+ (render_errors(performance.errors) && return) unless performance.success?
22
+
23
+ data.merge!(access_token: performance.stage.new_token)
24
+ render_low_camel_cased(data, :created)
25
+ end
26
+
27
+ def create_access_token
28
+ data = { access_token: '' }
29
+ performance = Jwts::AccessTokensCreating::Scenario.new(params: params).act_out!
30
+ (render_errors(performance.errors) && return) unless performance.success?
31
+
32
+ data.merge!(access_token: performance.stage.new_token)
33
+ render_low_camel_cased(data, :created)
34
+ end
35
+
36
+ def set_password
37
+ performance = PasswordsCreating::Scenario.new(params: params).act_out!
38
+ (render_errors(performance.errors) && return) unless performance.success?
39
+
40
+ render_low_camel_cased({}, :created)
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ module WithAccounts
2
+ class Account < ApplicationRecord
3
+ belongs_to :user, class_name: 'User'
4
+
5
+ has_secure_password :password
6
+ has_secure_password :refresh_token
7
+
8
+ LOGIN_INVITATION_TTL = 3.hours
9
+ REFRESH_TOKEN_TTL = 12.hours
10
+ ACCESS_TOKEN_TTL = 1.minute
11
+
12
+ scope :ready_to_login, lambda { |_|
13
+ where.not(login_invitation: '', password_set_at: nil)
14
+ }
15
+
16
+ scope :ready_to_set_password, lambda { |_|
17
+ where(password_set_at: nil).where('password_set_before, > ?', Time.now)
18
+ }
19
+
20
+ scope :ready_to_change_password, lambda { |_|
21
+ where.not(password_change_invitation: '', password_set_at: nil).where('password_change_before, > ?', Time.now)
22
+ }
23
+
24
+ def cipher_decipher(type, timestamp_attr_name)
25
+ timestamp = public_send(timestamp_attr_name) || created_at
26
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
27
+ case type
28
+ when :encrypt
29
+ cipher.encrypt
30
+ when :decrypt
31
+ cipher.decrypt
32
+ else
33
+ raise StandardError
34
+ end
35
+ cipher.tap do
36
+ _1.key = id.byteslice(0..31)
37
+ _1.iv = "#{timestamp&.strftime('%Y%m%d%H%M%S%6N')}---".byteslice(0..15)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ module WithAccounts
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module AccessTokensCreating
4
+ class CheckRefreshToken < LetsDoThis::Act
5
+ include ErrorsHelpers
6
+ HMAC_SECRET = ENV.fetch('HMAC_SECRET', 'stemporalyasdasdasdasdasdjdhfjds').byteslice(0..31)
7
+
8
+ def instructions(stage)
9
+ refresh_token = stage.params[:refresh_token]
10
+ decoded_token = JWT.decode(refresh_token,
11
+ HMAC_SECRET,
12
+ true,
13
+ { algorithm: 'HS256' })
14
+ token_data = decoded_token.reduce({}, :merge)
15
+ { said: token_data['said'] }
16
+ rescue JWT::DecodeError
17
+ errors.add(error_key, 'err')
18
+ NOTHING_NEW
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module AccessTokensCreating
4
+ class FindAndCheckAccount < LetsDoThis::Act
5
+ include ErrorsHelpers
6
+
7
+ def instructions(stage)
8
+ unless stage.said.present?
9
+ errors.add(error_key, 'err')
10
+ return NOTHING_NEW
11
+ end
12
+
13
+ saved_72_char_version = stage.params[:refresh_token][0..71]
14
+
15
+ account = Account.joins(:user)
16
+ .where('users.disabled': false)
17
+ .find_by_said(stage.said)
18
+ &.authenticate_refresh_token(saved_72_char_version)
19
+
20
+ unless account
21
+ errors.add(error_key, 'err')
22
+ return NOTHING_NEW
23
+ end
24
+
25
+ { account: account }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module AccessTokensCreating
4
+ class PrepareAccessTokenPayload < LetsDoThis::Act
5
+
6
+ def instructions(stage)
7
+ payload = { exp: (Time.now + Account::ACCESS_TOKEN_TTL).to_i,
8
+ said: stage.account.said }
9
+
10
+ { payload: payload }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module AccessTokensCreating
4
+ class Scenario < LetsDoThis::Scenario
5
+ ACT_SEQUENCE = [
6
+ CheckRefreshToken,
7
+ FindAndCheckAccount,
8
+ PrepareAccessTokenPayload,
9
+ CreateToken
10
+ ].freeze
11
+ STAGE_ATTRS_WHITELIST = %i[params said account payload new_token].freeze
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ class CreateToken < LetsDoThis::Act
4
+ HMAC_SECRET = ENV.fetch('HMAC_SECRET', 'stemporalyasdasdasdasdasdjdhfjds').byteslice(0..31)
5
+ def instructions(stage)
6
+ { new_token: (JWT.encode stage.payload, HMAC_SECRET, 'HS256') }
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,32 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module RefreshTokensCreating
4
+ class CheckLoginInvitation < LetsDoThis::Act
5
+ include ErrorsHelpers
6
+
7
+ def instructions(stage)
8
+ login_invitation = stage.params[:invitation]
9
+ account = Account.joins(:user)
10
+ .where('users.disabled': false)
11
+ .find_by_login_invitation(login_invitation)
12
+ unless account
13
+ errors.add(error_key, 'err')
14
+ return NOTHING_NEW
15
+ end
16
+
17
+ encrypted = Base64.urlsafe_decode64(login_invitation)
18
+ decipher = account.cipher_decipher(:decrypt, :login_at)
19
+ plain = decipher.update(encrypted) + decipher.final
20
+ deadline = Time.at(plain.to_i)
21
+
22
+ if Time.now > deadline
23
+ errors.add(error_key, 'err')
24
+ return NOTHING_NEW
25
+ end
26
+
27
+ { account: account }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module RefreshTokensCreating
4
+ class PrepareRefreshTokenPayload < LetsDoThis::Act
5
+
6
+ def instructions(stage)
7
+ payload = { exp: (Time.now + Account::REFRESH_TOKEN_TTL).to_i,
8
+ said: stage.account.said }
9
+
10
+ { payload: payload }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module RefreshTokensCreating
4
+ class Scenario < LetsDoThis::Scenario
5
+ ACT_SEQUENCE = [
6
+ CheckLoginInvitation,
7
+ PrepareRefreshTokenPayload,
8
+ CreateToken,
9
+ UpdateAccount
10
+ ].freeze
11
+ STAGE_ATTRS_WHITELIST = %i[params account payload new_token].freeze
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module WithAccounts
2
+ module Jwts
3
+ module RefreshTokensCreating
4
+ class UpdateAccount < LetsDoThis::Act
5
+
6
+ def instructions(stage)
7
+ saved_72_char_version = stage.new_token[0..71]
8
+
9
+ update = stage.account.update(login_invitation: '',
10
+ refresh_token: saved_72_char_version,
11
+ refresh_token_confirmation: saved_72_char_version,
12
+ login_at: Time.now)
13
+
14
+ errors.add(error_key, 'err') unless update
15
+
16
+ NOTHING_NEW
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module WithAccounts
2
+ module LoginInvitationsCreating
3
+ class CheckParams < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ email = stage.params[:email].to_s
8
+ password = stage.params[:password].to_s
9
+
10
+ # TODO
11
+ if 1 != 1
12
+ errors.add(error_key, 'err')
13
+ return NOTHING_NEW
14
+ end
15
+
16
+ { email: email, password: password }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module WithAccounts
2
+ module LoginInvitationsCreating
3
+ class FindAndCheckAccount < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ account = Account.ready_to_login(:_)
8
+ .joins(:user)
9
+ .where('users.disabled': false, 'users.email': stage.email)
10
+ .take&.authenticate(stage.password)
11
+
12
+ unless account
13
+ errors.add(error_key, 'err')
14
+ return NOTHING_NEW
15
+ end
16
+
17
+ { account: account }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module WithAccounts
2
+ module LoginInvitationsCreating
3
+ class Scenario < LetsDoThis::Scenario
4
+ ACT_SEQUENCE = [
5
+ CheckParams,
6
+ FindAndCheckAccount,
7
+ UpdateAccountLoginInvitation,
8
+ SendEmailToUser
9
+ ].freeze
10
+ STAGE_ATTRS_WHITELIST = %i[params email password account login_invitation].freeze
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module WithAccounts
2
+ module LoginInvitationsCreating
3
+ class SendEmailToUser < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ stage.account.user.send_email_with_login_invitation(stage.login_invitation)
8
+ NOTHING_NEW
9
+ rescue StandardError
10
+ errors.add(error_key, 'err')
11
+ NOTHING_NEW
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ module WithAccounts
2
+ module LoginInvitationsCreating
3
+ class UpdateAccountLoginInvitation < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ account = stage.account
8
+ data = (Time.now + Account::LOGIN_INVITATION_TTL).to_i.to_s
9
+
10
+ cipher = account.cipher_decipher(:encrypt, :login_at)
11
+
12
+ encrypted = cipher.update(data) + cipher.final
13
+ new_invitation = Base64.urlsafe_encode64(encrypted, padding: false)
14
+
15
+ unless account.update(login_invitation: new_invitation)
16
+ errors.add(error_key, 'err')
17
+ return NOTHING_NEW
18
+ end
19
+
20
+ { login_invitation: new_invitation }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module WithAccounts
2
+ module PasswordsCreating
3
+ class CheckParams < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ password = stage.params[:password]
8
+ password_confirmation = stage.params[:password_confirmation]
9
+
10
+ unless password == password_confirmation
11
+ errors.add(error_key, 'err')
12
+ return NOTHING_NEW
13
+ end
14
+
15
+ unless password.to_s.match?(PASSWORD_REGEXP)
16
+ errors.add(error_key, 'err')
17
+ return NOTHING_NEW
18
+ end
19
+
20
+ { password: password }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ module WithAccounts
2
+ module PasswordsCreating
3
+ class CheckPasswordSetInvitation < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+ def instructions(stage)
6
+ password_set_invitation = stage.params[:invitation]
7
+ account = Account.ready_to_set_password(:_)
8
+ .joins(:user)
9
+ .where('users.disabled': false)
10
+ .find_by_password_set_invitation(password_set_invitation)
11
+
12
+ unless account
13
+ errors.add(error_key, 'err')
14
+ return NOTHING_NEW
15
+ end
16
+
17
+ encrypted = Base64.urlsafe_decode64(password_set_invitation)
18
+ decipher = account.cipher_decipher(:decrypt, :password_set_at)
19
+ plain = decipher.update(encrypted) + decipher.final
20
+ deadline = Time.at(plain.to_i)
21
+
22
+ if Time.now > deadline
23
+ errors.add(error_key, 'err')
24
+ return NOTHING_NEW
25
+ end
26
+
27
+ { account: account }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ module WithAccounts
2
+ module PasswordsCreating
3
+ class Scenario < LetsDoThis::Scenario
4
+ ACT_SEQUENCE = [
5
+ CheckParams,
6
+ CheckPasswordSetInvitation,
7
+ UpdateAccount
8
+ ].freeze
9
+ STAGE_ATTRS_WHITELIST = %i[params password account].freeze
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module WithAccounts
2
+ module PasswordsCreating
3
+ class UpdateAccount < LetsDoThis::Act
4
+ include ErrorsHelpers
5
+
6
+ def instructions(stage)
7
+ account = stage.account
8
+
9
+ account.tap do
10
+ account.password = stage.password
11
+ account.password_confirmation = stage.password
12
+ account.password_set_invitation = ''
13
+ account.password_set_at = Time.now
14
+ end
15
+
16
+ unless account.save
17
+ errors.add(error_key, 'err')
18
+ return NOTHING_NEW
19
+ end
20
+
21
+ { account: account }
22
+ rescue StandardError
23
+ errors.add(error_key, 'err')
24
+ NOTHING_NEW
25
+ end
26
+ end
27
+ end
28
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ WithAccounts::Engine.routes.draw do
2
+ root to: 'meaningful#not_found'
3
+
4
+ post '/passwords_setters', to: 'meaningful#set_user_password'
5
+ post '/invitations', to: 'meaningful#create_login_invitation'
6
+ post '/tokens', to: 'meaningful#create_tokens'
7
+ post '/access_tokens', to: 'meaningful#create_access_token'
8
+ end
@@ -0,0 +1,20 @@
1
+ class CreateWithAccountsAccounts < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :with_accounts_accounts, id: :uuid do |t|
4
+ t.references :user, null: false, foreign_key: true, type: :uuid
5
+ t.string :password_digest
6
+ t.string :refresh_token_digest
7
+ t.string :said, null: false
8
+
9
+ t.datetime :login_at
10
+ t.datetime :password_changed_at
11
+ t.datetime :password_set_at
12
+
13
+ t.string :login_invitation, default: ''
14
+ t.string :password_set_invitation, default: ''
15
+ t.string :password_change_invitation, default: ''
16
+
17
+ t.timestamps
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :with_accounts do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,10 @@
1
+ module WithAccounts
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace WithAccounts
4
+ config.generators.api_only = true
5
+ # config.autoload_paths += %W(#{config.root}/lib/modules)
6
+ config.generators do |g|
7
+ g.test_framework :rspec
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,54 @@
1
+ module WithAccounts
2
+ module ErrorsHelpers
3
+ def error_key
4
+ self.class.name.demodulize.to_sym
5
+ end
6
+
7
+ def format_errors(errors_hash)
8
+ raise StandardError unless errors_hash.present? && errors_hash.is_a?(Hash) && errors_hash.keys.size == 1
9
+
10
+ errors_key = errors_hash.keys.first
11
+ errors = errors_hash.values.flatten.first
12
+
13
+ errors = errors.respond_to?(:to_h) ? errors.to_h : errors
14
+
15
+ case errors
16
+ when Hash
17
+ sort_hash_errors(errors, errors_key)
18
+ when Array
19
+ sort_array_errors(errors, errors_key)
20
+ when String
21
+ sort_string_errors(errors, errors_key)
22
+ else
23
+ raise StandardError
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def sort_hash_errors(errors, errors_hash_key)
30
+ errors.each_with_object({}) do |error, hash|
31
+ key = error.flatten.first
32
+ hash[:other_errors] ||= { "#{errors_hash_key}": [] }
33
+ hash[:other_errors][errors_hash_key] << key
34
+ end
35
+ end
36
+
37
+ def sort_array_errors(errors, errors_hash_key)
38
+ hash = {}
39
+ errors.flatten.each do
40
+ key = _1.split(' ').first
41
+ hash[:other_errors] ||= { "#{errors_hash_key}": [] }
42
+ hash[:other_errors][errors_hash_key] << key
43
+ hash
44
+ end
45
+ end
46
+
47
+ def sort_string_errors(errors, errors_hash_key)
48
+ hash = {}
49
+ hash[:other_errors] ||= { "#{errors_hash_key}": [errors] }
50
+ hash
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,3 @@
1
+ module WithAccounts
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "with_accounts/version"
2
+ require "with_accounts/engine"
3
+
4
+ module WithAccounts
5
+ # Your code goes here...
6
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: with_accounts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - zabodaevmm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-10-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - zabodaevmm@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/controllers/with_accounts/application_controller.rb
52
+ - app/controllers/with_accounts/meaningful_controller.rb
53
+ - app/models/with_accounts/account.rb
54
+ - app/models/with_accounts/application_record.rb
55
+ - app/scenarios/with_accounts/jwts/access_tokens_creating/check_refresh_token.rb
56
+ - app/scenarios/with_accounts/jwts/access_tokens_creating/find_and_check_account.rb
57
+ - app/scenarios/with_accounts/jwts/access_tokens_creating/prepare_access_token_payload.rb
58
+ - app/scenarios/with_accounts/jwts/access_tokens_creating/scenario.rb
59
+ - app/scenarios/with_accounts/jwts/create_token.rb
60
+ - app/scenarios/with_accounts/jwts/refresh_tokens_creating/check_login_invitation.rb
61
+ - app/scenarios/with_accounts/jwts/refresh_tokens_creating/prepare_refresh_token_payload.rb
62
+ - app/scenarios/with_accounts/jwts/refresh_tokens_creating/scenario.rb
63
+ - app/scenarios/with_accounts/jwts/refresh_tokens_creating/update_account.rb
64
+ - app/scenarios/with_accounts/login_invitations_creating/check_params.rb
65
+ - app/scenarios/with_accounts/login_invitations_creating/find_and_check_account.rb
66
+ - app/scenarios/with_accounts/login_invitations_creating/scenario.rb
67
+ - app/scenarios/with_accounts/login_invitations_creating/send_email_to_user.rb
68
+ - app/scenarios/with_accounts/login_invitations_creating/update_account_login_invitation.rb
69
+ - app/scenarios/with_accounts/passwords_creating/check_params.rb
70
+ - app/scenarios/with_accounts/passwords_creating/check_password_set_invitation.rb
71
+ - app/scenarios/with_accounts/passwords_creating/scenario.rb
72
+ - app/scenarios/with_accounts/passwords_creating/update_account.rb
73
+ - config/routes.rb
74
+ - db/migrate/20231004123741_create_with_accounts_accounts.rb
75
+ - lib/tasks/with_accounts_tasks.rake
76
+ - lib/with_accounts.rb
77
+ - lib/with_accounts/engine.rb
78
+ - lib/with_accounts/errors_helpers.rb
79
+ - lib/with_accounts/version.rb
80
+ homepage: https://github.com/zabodaevmm/with_accounts
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ homepage_uri: https://github.com/zabodaevmm/with_accounts
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: 2.7.0
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.4.10
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Accounts logic on demand.
104
+ test_files: []