punk 0.1.4 → 0.2.0

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +9 -0
  3. data/.github/workflows/test.yml +26 -1
  4. data/.rdoc_options +23 -0
  5. data/.rgignore +1 -0
  6. data/.rspec +2 -0
  7. data/Gemfile +5 -6
  8. data/Gemfile.lock +16 -29
  9. data/README.md +1 -1
  10. data/VERSION +1 -1
  11. data/app/migrations/001_lets_punk.rb +3 -0
  12. data/app/routes/hello.rb +4 -0
  13. data/env/.gitignore +3 -0
  14. data/env/spec/test.sh +3 -0
  15. data/lib/punk/actions/.keep +0 -0
  16. data/lib/punk/actions/groups/list.rb +24 -0
  17. data/lib/punk/actions/sessions/clear.rb +21 -0
  18. data/lib/punk/actions/sessions/create.rb +64 -0
  19. data/lib/punk/actions/sessions/list.rb +18 -0
  20. data/lib/punk/actions/sessions/verify.rb +24 -0
  21. data/lib/punk/actions/tenants/list.rb +18 -0
  22. data/lib/punk/actions/users/list_group.rb +18 -0
  23. data/lib/punk/actions/users/list_tenant.rb +18 -0
  24. data/lib/punk/actions/users/show.rb +18 -0
  25. data/lib/punk/commands/list.rb +12 -6
  26. data/lib/punk/config/defaults.json +3 -0
  27. data/lib/punk/config/schema.json +3 -0
  28. data/lib/punk/core/app.rb +4 -6
  29. data/lib/punk/core/commander.rb +7 -4
  30. data/lib/punk/core/exec.rb +2 -0
  31. data/lib/punk/core/load.rb +0 -1
  32. data/lib/punk/framework/command.rb +5 -1
  33. data/lib/punk/framework/plugins/validation.rb +0 -14
  34. data/lib/punk/helpers/loggable.rb +1 -1
  35. data/lib/punk/migrations/001_punk.rb +103 -0
  36. data/lib/punk/models/.keep +0 -0
  37. data/lib/punk/models/group.rb +20 -0
  38. data/lib/punk/models/group_user_metadata.rb +17 -0
  39. data/lib/punk/models/identity.rb +29 -0
  40. data/lib/punk/models/session.rb +89 -0
  41. data/lib/punk/models/tenant.rb +19 -0
  42. data/lib/punk/models/tenant_user_metadata.rb +17 -0
  43. data/lib/punk/models/user.rb +31 -0
  44. data/lib/punk/routes/groups.rb +31 -0
  45. data/lib/punk/routes/plivo.rb +4 -0
  46. data/lib/punk/routes/sessions.rb +108 -0
  47. data/lib/punk/routes/swagger.rb +9 -0
  48. data/lib/punk/routes/tenants.rb +29 -0
  49. data/lib/punk/routes/users.rb +36 -0
  50. data/lib/punk/services/.keep +0 -0
  51. data/lib/punk/services/challenge_claim.rb +46 -0
  52. data/lib/punk/services/create_identities.rb +25 -0
  53. data/lib/punk/services/generate_swagger.rb +25 -0
  54. data/lib/punk/services/prove_claim.rb +29 -0
  55. data/lib/punk/services/secret.rb +9 -0
  56. data/lib/punk/templates/groups/list.jbuilder +7 -0
  57. data/lib/punk/templates/plivo.slim +16 -0
  58. data/lib/punk/templates/sessions/list.jbuilder +6 -0
  59. data/lib/punk/templates/sessions/pending.jbuilder +4 -0
  60. data/lib/punk/templates/tenants/list.jbuilder +7 -0
  61. data/lib/punk/templates/tenants/list.slim +8 -0
  62. data/lib/punk/templates/users/list.jbuilder +7 -0
  63. data/lib/punk/templates/users/list.rcsv +4 -0
  64. data/lib/punk/templates/users/show.jbuilder +5 -0
  65. data/lib/punk/views/groups/list.rb +22 -0
  66. data/lib/punk/views/plivo_store.rb +15 -0
  67. data/lib/punk/views/sessions/list.rb +22 -0
  68. data/lib/punk/views/sessions/pending.rb +28 -0
  69. data/lib/punk/views/tenants/list.rb +22 -0
  70. data/lib/punk/views/users/list.rb +22 -0
  71. data/lib/punk/views/users/show.rb +22 -0
  72. data/lib/punk/workers/.keep +0 -0
  73. data/lib/punk/workers/expire_sessions.rb +9 -0
  74. data/lib/punk/workers/geocode_session_worker.rb +48 -0
  75. data/lib/punk/workers/identify_session_worker.rb +45 -0
  76. data/lib/punk/workers/secret.rb +18 -0
  77. data/lib/punk/workers/send_email_worker.rb +51 -0
  78. data/lib/punk/workers/send_sms_worker.rb +40 -0
  79. data/punk.gemspec +140 -14
  80. data/schema.psql +345 -0
  81. data/spec/actions/groups/punk/list_groups_action_spec.rb +36 -0
  82. data/spec/actions/sessions/punk/clear_session_action_spec.rb +29 -0
  83. data/spec/actions/sessions/punk/create_session_action_spec.rb +33 -0
  84. data/spec/actions/sessions/punk/list_sessions_action_spec.rb +26 -0
  85. data/spec/actions/sessions/punk/verify_session_action_spec.rb +59 -0
  86. data/spec/actions/tenants/punk/list_tenants_action_spec.rb +25 -0
  87. data/spec/actions/users/punk/list_group_users_action_spec.rb +26 -0
  88. data/spec/actions/users/punk/list_tenant_users_action_spec.rb +26 -0
  89. data/spec/factories/group.rb +12 -0
  90. data/spec/factories/group_user_metadata.rb +10 -0
  91. data/spec/factories/identity.rb +19 -0
  92. data/spec/factories/session.rb +12 -0
  93. data/spec/factories/tenant.rb +10 -0
  94. data/spec/factories/tenant_user_metadata.rb +10 -0
  95. data/spec/factories/user.rb +12 -0
  96. data/spec/lib/commands/auth_spec.rb +11 -0
  97. data/spec/lib/commands/generate_spec.rb +7 -0
  98. data/spec/lib/commands/http_spec.rb +23 -0
  99. data/spec/lib/commands/list_spec.rb +7 -0
  100. data/spec/lib/commands/swagger_spec.rb +7 -0
  101. data/spec/lib/engine/punk_env_spec.rb +13 -0
  102. data/spec/lib/engine/punk_exec_spec.rb +9 -0
  103. data/spec/lib/engine/punk_init_spec.rb +9 -0
  104. data/spec/lib/engine/punk_store_spec.rb +10 -0
  105. data/spec/lib/punk.env +7 -0
  106. data/spec/models/punk/group_spec.rb +50 -0
  107. data/spec/models/punk/group_user_metadata_spec.rb +61 -0
  108. data/spec/models/punk/identity_spec.rb +61 -0
  109. data/spec/models/punk/session_spec.rb +156 -0
  110. data/spec/models/punk/tenant_spec.rb +51 -0
  111. data/spec/models/punk/tenant_user_metadata_spec.rb +61 -0
  112. data/spec/models/punk/user_spec.rb +115 -0
  113. data/spec/routes/groups/get_groups_spec.rb +33 -0
  114. data/spec/routes/plivo/get_plivo_spec.rb +11 -0
  115. data/spec/routes/sessions/delete_session_spec.rb +11 -0
  116. data/spec/routes/sessions/get_sessions_spec.rb +30 -0
  117. data/spec/routes/sessions/patch_session_spec.rb +11 -0
  118. data/spec/routes/sessions/post_session_spec.rb +11 -0
  119. data/spec/routes/swagger/get_swagger_spec.rb +12 -0
  120. data/spec/routes/tenants/get_tenants_spec.rb +31 -0
  121. data/spec/routes/users/get_users_spec.rb +60 -0
  122. data/spec/services/punk/challenge_claim_service_spec.rb +7 -0
  123. data/spec/services/punk/create_identities_service_spec.rb +14 -0
  124. data/spec/services/punk/generate_swagger_service_spec.rb +7 -0
  125. data/spec/services/punk/prove_claim_service_spec.rb +7 -0
  126. data/spec/services/punk/secret_service_spec.rb +7 -0
  127. data/spec/spec_helper.rb +122 -0
  128. data/spec/vcr_cassettes/PUNK_GeocodeSessionWorker/updates_the_session_data.yml +57 -0
  129. data/spec/vcr_cassettes/PUNK_IdentifySessionWorker/updates_the_session_data.yml +112 -0
  130. data/spec/views/punk/plivo_store_spec.rb +7 -0
  131. data/spec/views/sessions/punk/list_sessions_view_spec.rb +7 -0
  132. data/spec/views/sessions/punk/pending_session_view_spec.rb +7 -0
  133. data/spec/views/tenants/punk/list_tenants_view_spec.rb +7 -0
  134. data/spec/views/users/punk/list_groups_view_spec.rb +7 -0
  135. data/spec/views/users/punk/list_users_view_spec.rb +7 -0
  136. data/spec/workers/punk/expire_sessions_worker_spec.rb +31 -0
  137. data/spec/workers/punk/geocode_session_worker_spec.rb +14 -0
  138. data/spec/workers/punk/identify_session_worker_spec.rb +15 -0
  139. data/spec/workers/punk/secret_worker_spec.rb +20 -0
  140. data/spec/workers/punk/send_email_worker_spec.rb +46 -0
  141. data/spec/workers/punk/send_sms_worker_spec.rb +33 -0
  142. metadata +148 -11
  143. data/lib/punk/views/all.rb +0 -4
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class GenerateSwaggerService < Service
5
+ def process
6
+ path = File.join(PUNK.get.app.path, '..', 'www', 'swagger.json')
7
+ raise InternalServerError, 'swagger.json already exists' if File.exist?(path) && !PUNK.env.test?
8
+ require 'swagger_yard'
9
+ require_relative '../helpers/swagger'
10
+ SwaggerYard.register_custom_yard_tags!
11
+ SwaggerYard.configure do |config|
12
+ config.api_version = PUNK.version
13
+ config.title = PUNK.get.app.name
14
+ config.description = PUNK.get.app.description
15
+ config.api_base_path = PUNK.get.app.url
16
+ config.controller_path = [File.join(PUNK.get.app.path, 'routes', '**', '*'), File.join(__dir__, '..', 'routes', '**', '*')]
17
+ config.model_path = [File.join(__dir__, '..', 'models', '**', '*'), File.join(__dir__, '..', 'views', '**', '*'), File.join(PUNK.get.app.path, 'models', '**', '*')]
18
+ end
19
+ spec = SwaggerYard::OpenAPI.new
20
+ blob = JSON.pretty_generate(spec.to_h)
21
+ File.open(path, "w") { |f| f << blob } unless PUNK.env.test?
22
+ blob
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbnacl'
4
+
5
+ module PUNK
6
+ class ProveClaimService < Service
7
+ args :session, :secret
8
+
9
+ def validate
10
+ validates_not_null :session
11
+ validates_not_empty :session
12
+ return if session.blank?
13
+ session.timeout?
14
+ validates_type Session, :session
15
+ validates_state :session, :pending
16
+ validates_event :session, :verify
17
+ end
18
+
19
+ def process
20
+ session.increment_attempts
21
+ session.reload
22
+ raise BadRequest, "Too many attempts" if session.attempt_count >= 3
23
+ hash = RbNaCl::PasswordHash.scrypt(secret, session.salt, 1_048_576, 16_777_216)
24
+ proven = (session[:hash] == hash)
25
+ session.verify! if proven
26
+ proven
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class SecretService < Service
5
+ def process
6
+ (SecureRandom.random_number(900_000) + 100_000).to_s
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.array!(groups) do |group|
4
+ json.id group.id
5
+ json.name group.name
6
+ json.icon group.icon
7
+ end
@@ -0,0 +1,16 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title Plivo
5
+ body
6
+ h2 Plivo
7
+ - plivo = PUNK.cache.get(:plivo) || []
8
+ - plivo.each do |message|
9
+ h4= message[:sent]
10
+ dl
11
+ dt from
12
+ dd= message[:from]
13
+ dt to
14
+ dd= message[:to]
15
+ dt body
16
+ dd= message[:body]
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.array!(sessions) do |session|
4
+ json.id session.id
5
+ json.data session.data
6
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.slug session.slug
4
+ json.message message
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.array!(tenants) do |tenant|
4
+ json.id tenant.id
5
+ json.name tenant.name
6
+ json.icon tenant.icon
7
+ end
@@ -0,0 +1,8 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title Tenants
5
+ body
6
+ - tenants.each do |tenant|
7
+ h1= tenant.name
8
+ img src= tenant.icon
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.array!(users) do |user|
4
+ json.id user.id
5
+ json.name user.name
6
+ json.icon user.icon
7
+ end
@@ -0,0 +1,4 @@
1
+ csv << ['ID','NAME','ICON','EMAIL','PHONE','TITLE','COMPANY','WEBSITE','LOCATION']
2
+ users.each do |user|
3
+ csv << [user.id, user.name, user.icon, user.email, user.phone, user.profile[:professional][:title], user.profile[:professional][:company], user.profile[:professional][:website], user.profile[:professional][:location]]
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ json.id user.id
4
+ json.name user.name
5
+ json.icon user.icon
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListGroupsView < View
5
+ args :groups
6
+
7
+ def validate
8
+ validates_not_null :groups
9
+ validates_type Array, :groups
10
+ end
11
+
12
+ def process
13
+ 'groups/list'
14
+ end
15
+
16
+ protected
17
+
18
+ def _dir
19
+ File.join(__dir__, '..', '..', 'templates')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class PlivoStore < View
5
+ def process
6
+ 'plivo'
7
+ end
8
+
9
+ protected
10
+
11
+ def _dir
12
+ File.join(__dir__, '..', 'templates')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListSessionsView < View
5
+ args :sessions
6
+
7
+ def validate
8
+ validates_not_null :sessions
9
+ validates_type Array, :sessions
10
+ end
11
+
12
+ def process
13
+ 'sessions/list'
14
+ end
15
+
16
+ protected
17
+
18
+ def _dir
19
+ File.join(__dir__, '..', '..', 'templates')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class PendingSessionView < View
5
+ args :session, :message
6
+
7
+ def validate
8
+ validates_not_null :session
9
+ validates_not_empty :session
10
+ return if session.blank?
11
+ validates_type Session, :session
12
+ validates_state :session, :pending
13
+ validates_not_null :message
14
+ validates_not_empty :message
15
+ validates_type String, :message
16
+ end
17
+
18
+ def process
19
+ 'sessions/pending'
20
+ end
21
+
22
+ protected
23
+
24
+ def _dir
25
+ File.join(__dir__, '..', '..', 'templates')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListTenantsView < View
5
+ args :tenants
6
+
7
+ def validate
8
+ validates_not_null :tenants
9
+ validates_type Array, :tenants
10
+ end
11
+
12
+ def process
13
+ 'tenants/list'
14
+ end
15
+
16
+ protected
17
+
18
+ def _dir
19
+ File.join(__dir__, '..', '..', 'templates')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ListUsersView < View
5
+ args :users
6
+
7
+ def validate
8
+ validates_not_null :users
9
+ validates_type Array, :users
10
+ end
11
+
12
+ def process
13
+ 'users/list'
14
+ end
15
+
16
+ protected
17
+
18
+ def _dir
19
+ File.join(__dir__, '..', '..', 'templates')
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ShowUserView < View
5
+ args :user
6
+
7
+ def validate
8
+ validates_not_null :user
9
+ validates_type User, :user
10
+ end
11
+
12
+ def process
13
+ 'users/show'
14
+ end
15
+
16
+ protected
17
+
18
+ def _dir
19
+ File.join(__dir__, '..', '..', 'templates')
20
+ end
21
+ end
22
+ end
File without changes
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class ExpireSessionsWorker < Worker
5
+ def process
6
+ Session.expiring.each(&:timeout?)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class GeocodeSessionWorker < Worker
5
+ args :session_id
6
+
7
+ def validate
8
+ validates_not_null :session_id
9
+ validates_not_empty :session_id
10
+ end
11
+
12
+ def process
13
+ require 'ipstack'
14
+
15
+ session = Session[session_id]
16
+ return if session.blank?
17
+
18
+ ip_address = session.remote_addr.to_s
19
+ return if ip_address == '127.0.0.1'
20
+
21
+ return if PUNK.get.ipstack.api_key.blank?
22
+ result = Ipstack::API.standard(ip_address).deep_symbolize_keys
23
+
24
+ raise if result.blank?
25
+
26
+ timezone = result[:time_zone][:code] if result[:time_zone].present?
27
+ language = result[:location][:languages].first[:code] if result[:location].present? && result[:location][:languages].present?
28
+ currency = result[:currency][:code] if result[:currency].present?
29
+ session.update(data: session.data.merge(
30
+ tz: timezone,
31
+ lang: language,
32
+ currency: currency,
33
+ geo: {
34
+ lat: result[:latitude],
35
+ lng: result[:longitude]
36
+ },
37
+ location: {
38
+ city: result[:city],
39
+ region: result[:region_name],
40
+ country: result[:country_name],
41
+ continent: result[:continent_name]
42
+ }
43
+ ))
44
+
45
+ session.save
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class IdentifySessionWorker < Worker
5
+ args :session_id
6
+
7
+ def validate
8
+ validates_not_null :session_id
9
+ validates_not_empty :session_id
10
+ end
11
+
12
+ def process
13
+ require 'userstack'
14
+
15
+ session = Session[session_id]
16
+ return if session.blank?
17
+
18
+ return if PUNK.get.userstack.api_key.blank?
19
+ client = Userstack::Client.new(PUNK.get.userstack.api_key, use_ssl: PUNK.get.userstack.use_ssl)
20
+ result = client.parse(session.user_agent).deep_symbolize_keys
21
+
22
+ raise if result[:success] == false || result[:type].nil?
23
+
24
+ session.update(data: session.data.merge(
25
+ os: {
26
+ name: result[:os][:name],
27
+ family: result[:os][:family],
28
+ vendor: result[:os][:family_vendor]
29
+ },
30
+ browser: {
31
+ name: result[:browser][:name],
32
+ version: result[:browser][:version]
33
+ },
34
+ device: {
35
+ name: result[:device][:name],
36
+ brand: result[:device][:brand],
37
+ type: result[:device][:type],
38
+ mobile: result[:device][:is_mobile_device]
39
+ }
40
+ ))
41
+
42
+ GeocodeSessionWorker.perform_now(session_id: session.id)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class SecretWorker < Worker
5
+ args :name
6
+
7
+ def validate
8
+ validates_not_null :name
9
+ validates_not_empty :name
10
+ end
11
+
12
+ def process
13
+ secret = SecretService.run.result
14
+ sleep 5 unless PUNK.env.test?
15
+ logger.info "#{name}: #{secret}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PUNK
4
+ class SendEmailWorker < Worker
5
+ args :from, :to, :subject, :template, :variables, :tags
6
+
7
+ def validate
8
+ validates_type String, :from
9
+ validates_type String, :to
10
+ validates_email :to
11
+ validates_includes PUNK.get.mailgun.whitelist, :to if !PUNK.env.test? && !PUNK.get.mailgun.mock && PUNK.get.mailgun.whitelist.present?
12
+ validates_type String, :subject
13
+ validates_type String, :template
14
+ validates_type Hash, :variables, allow_nil: true
15
+ validates_type Array, :tags, allow_nil: true
16
+ validates_length_range 0..1, :tags, allow_nil: true
17
+ end
18
+
19
+ def process
20
+ require 'mailgun-ruby'
21
+
22
+ client =
23
+ if !PUNK.env.test? && PUNK.get.mailgun.mock
24
+ Mailgun::Client.new(PUNK.get.mailgun.api_key, 'bin.mailgun.net', PUNK.get.mailgun.postbin, ssl=false) # rubocop:disable Lint/UselessAssignment,Layout/SpaceAroundOperators
25
+ else
26
+ Mailgun::Client.new(PUNK.get.mailgun.api_key)
27
+ end
28
+
29
+ client.enable_test_mode! if PUNK.env.test?
30
+
31
+ message =
32
+ {
33
+ from: from,
34
+ to: to,
35
+ subject: subject,
36
+ template: template
37
+ }
38
+ variables.each { |variable, value| message["v:#{variable}"] = value } if variables.present?
39
+ tags.each { |tag| message["o:tag"] = tag } if tags.present?
40
+
41
+ # TODO: store return value in Message table
42
+ # TODO: update Message table with events from mailgun webhooks
43
+ client.send_message(PUNK.get.mailgun.domain, message)
44
+
45
+ return unless !PUNK.env.test? && PUNK.get.mailgun.mock
46
+
47
+ require 'launchy'
48
+ Launchy.open("http://bin.mailgun.net/#{PUNK.get.mailgun.postbin}")
49
+ end
50
+ end
51
+ end