ruby_everywhere 0.2.0 → 0.4.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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +255 -0
  3. data/bridge/README.md +48 -0
  4. data/bridge/everywhere/bridge.js +388 -3
  5. data/bridge/everywhere/native.css +203 -0
  6. data/bridge/package.json +6 -3
  7. data/lib/everywhere/android_resources.rb +217 -0
  8. data/lib/everywhere/android_sdk.rb +330 -0
  9. data/lib/everywhere/asset_catalog.rb +185 -0
  10. data/lib/everywhere/auth_handoff.rb +398 -0
  11. data/lib/everywhere/auth_token.rb +118 -0
  12. data/lib/everywhere/builders/android.rb +844 -0
  13. data/lib/everywhere/builders/ios.rb +72 -3
  14. data/lib/everywhere/commands/build.rb +51 -9
  15. data/lib/everywhere/commands/clean.rb +10 -5
  16. data/lib/everywhere/commands/dev.rb +46 -8
  17. data/lib/everywhere/commands/doctor.rb +89 -3
  18. data/lib/everywhere/commands/install.rb +1 -1
  19. data/lib/everywhere/commands/logs.rb +117 -8
  20. data/lib/everywhere/config.rb +513 -14
  21. data/lib/everywhere/emulator.rb +405 -0
  22. data/lib/everywhere/engine.rb +21 -0
  23. data/lib/everywhere/icon.rb +196 -0
  24. data/lib/everywhere/log_filter.rb +74 -0
  25. data/lib/everywhere/mobile_config_endpoint.rb +13 -0
  26. data/lib/everywhere/mobile_configs_controller.rb +18 -0
  27. data/lib/everywhere/native_helper.rb +198 -5
  28. data/lib/everywhere/omniauth.rb +88 -0
  29. data/lib/everywhere/paths.rb +47 -0
  30. data/lib/everywhere/version.rb +1 -1
  31. data/support/mobile/android/README.md +217 -0
  32. data/support/mobile/android/app/build.gradle.kts +156 -0
  33. data/support/mobile/android/app/everywhere.properties +8 -0
  34. data/support/mobile/android/app/native-packages.gradle.kts +16 -0
  35. data/support/mobile/android/app/proguard-rules.pro +21 -0
  36. data/support/mobile/android/app/src/debug/res/xml/network_security_config.xml +30 -0
  37. data/support/mobile/android/app/src/main/AndroidManifest.xml +42 -0
  38. data/support/mobile/android/app/src/main/assets/everywhere.json +9 -0
  39. data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.codepoints +2235 -0
  40. data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.ttf +0 -0
  41. data/support/mobile/android/app/src/main/assets/json/path-configuration.json +19 -0
  42. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/AuthFlow.kt +268 -0
  43. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereApplication.kt +178 -0
  44. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereConfig.kt +409 -0
  45. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereEvents.kt +68 -0
  46. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereHost.kt +101 -0
  47. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/IconFont.kt +211 -0
  48. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainActivity.kt +975 -0
  49. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainTabs.kt +201 -0
  50. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebBottomSheetFragment.kt +13 -0
  51. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebFragment.kt +36 -0
  52. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/BiometricsComponent.kt +535 -0
  53. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/HapticsComponent.kt +141 -0
  54. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/MenuComponent.kt +361 -0
  55. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/NotificationComponent.kt +151 -0
  56. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/PermissionsComponent.kt +271 -0
  57. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/StorageComponent.kt +101 -0
  58. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/WebControlChannel.kt +204 -0
  59. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/extensions/EverywhereExtensions.kt +51 -0
  60. data/support/mobile/android/app/src/main/res/color/bottom_navigation_active_indicator.xml +4 -0
  61. data/support/mobile/android/app/src/main/res/color/bottom_navigation_item.xml +11 -0
  62. data/support/mobile/android/app/src/main/res/drawable/ic_launcher_foreground.xml +26 -0
  63. data/support/mobile/android/app/src/main/res/drawable/ic_tab_placeholder.xml +28 -0
  64. data/support/mobile/android/app/src/main/res/layout/activity_main.xml +116 -0
  65. data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet.xml +83 -0
  66. data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet_item.xml +35 -0
  67. data/support/mobile/android/app/src/main/res/layout/tab_more_sheet.xml +48 -0
  68. data/support/mobile/android/app/src/main/res/layout/tab_more_sheet_item.xml +35 -0
  69. data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +12 -0
  70. data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  71. data/support/mobile/android/app/src/main/res/values/colors.xml +21 -0
  72. data/support/mobile/android/app/src/main/res/values/ic_launcher_background.xml +5 -0
  73. data/support/mobile/android/app/src/main/res/values/strings.xml +18 -0
  74. data/support/mobile/android/app/src/main/res/values/styles.xml +32 -0
  75. data/support/mobile/android/app/src/main/res/values/themes.xml +53 -0
  76. data/support/mobile/android/app/src/main/res/values-night/colors.xml +11 -0
  77. data/support/mobile/android/app/src/main/res/values-night/themes.xml +33 -0
  78. data/support/mobile/android/app/src/main/res/xml/network_security_config.xml +12 -0
  79. data/support/mobile/android/app/src/stamped/AndroidManifest.xml +34 -0
  80. data/support/mobile/android/build.gradle.kts +8 -0
  81. data/support/mobile/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  82. data/support/mobile/android/gradle/wrapper/gradle-wrapper.properties +10 -0
  83. data/support/mobile/android/gradle.properties +14 -0
  84. data/support/mobile/android/gradlew +251 -0
  85. data/support/mobile/android/gradlew.bat +94 -0
  86. data/support/mobile/android/settings.gradle.kts +28 -0
  87. data/support/mobile/ios/App/AppDelegate.swift +7 -1
  88. data/support/mobile/ios/App/AuthFlow.swift +101 -0
  89. data/support/mobile/ios/App/Bridge/MenuComponent.swift +192 -0
  90. data/support/mobile/ios/App/EverywhereConfig.swift +105 -0
  91. data/support/mobile/ios/App/SceneDelegate.swift +144 -0
  92. data/support/mobile/ios/App.xcodeproj/project.pbxproj +8 -0
  93. data/support/mobile/ios/README.md +10 -0
  94. metadata +69 -1
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "openssl"
6
+
7
+ module Everywhere
8
+ # The sealed envelopes the native auth flow passes around: the marker cookie
9
+ # that says "this browser session is completing a sign-in for the app", and
10
+ # the handoff token that carries the resulting cookie jar back into the app's
11
+ # web view.
12
+ #
13
+ # Self-contained (encrypt-then-authenticate, expiry inside the payload) rather
14
+ # than a server-side store: the flow crosses processes — and in a load-balanced
15
+ # deploy, hosts — between the auth browser and the web view, and a shared
16
+ # store would be one more thing to configure. AES-256-GCM with the purpose as
17
+ # associated data, so a marker can never be replayed as a handoff token.
18
+ module AuthToken
19
+ CIPHER = "aes-256-gcm"
20
+ NONCE_BYTES = 12
21
+ TAG_BYTES = 16
22
+
23
+ class MissingSecret < StandardError; end
24
+
25
+ class << self
26
+ # The secret the tokens are keyed from. Set explicitly, or left to be
27
+ # discovered: the Rails app's secret_key_base, else SECRET_KEY_BASE /
28
+ # EVERYWHERE_AUTH_SECRET from the environment (Sinatra/Hanami).
29
+ attr_writer :secret
30
+
31
+ def secret
32
+ @secret ||= discover_secret or raise(MissingSecret, <<~MESSAGE)
33
+ Native sign-in needs a secret to seal its handoff tokens. Rails apps use
34
+ secret_key_base automatically; elsewhere set SECRET_KEY_BASE (or
35
+ EVERYWHERE_AUTH_SECRET) in the environment, or assign
36
+ Everywhere::AuthToken.secret at boot.
37
+ MESSAGE
38
+ end
39
+
40
+ def encrypt(payload, purpose:, ttl:)
41
+ sealed = JSON.generate(payload.merge("exp" => now + ttl))
42
+ cipher = OpenSSL::Cipher.new(CIPHER).encrypt
43
+ cipher.key = key
44
+ nonce = cipher.random_iv
45
+ cipher.auth_data = purpose.to_s
46
+ ciphertext = cipher.update(sealed) + cipher.final
47
+ Base64.urlsafe_encode64(nonce + cipher.auth_tag + ciphertext, padding: false)
48
+ end
49
+
50
+ # The payload, or nil for anything we can't fully vouch for: tampered,
51
+ # wrong purpose, expired, or (when `once`) already spent. Never raises on
52
+ # bad input — every one of these arrives from outside.
53
+ def decrypt(token, purpose:, once: false)
54
+ raw = Base64.urlsafe_decode64(token.to_s)
55
+ return nil if raw.bytesize <= NONCE_BYTES + TAG_BYTES
56
+
57
+ decipher = OpenSSL::Cipher.new(CIPHER).decrypt
58
+ decipher.key = key
59
+ decipher.iv = raw.byteslice(0, NONCE_BYTES)
60
+ decipher.auth_tag = raw.byteslice(NONCE_BYTES, TAG_BYTES)
61
+ decipher.auth_data = purpose.to_s
62
+
63
+ payload = JSON.parse(decipher.update(raw.byteslice(NONCE_BYTES + TAG_BYTES..)) + decipher.final)
64
+ return nil unless payload.is_a?(Hash) && payload["exp"].to_i > now
65
+ return nil if once && !claim(token, payload["exp"].to_i)
66
+
67
+ payload
68
+ rescue ArgumentError, OpenSSL::Cipher::CipherError, JSON::ParserError
69
+ nil
70
+ end
71
+
72
+ # Test hook — a new secret means a new key.
73
+ def reset!
74
+ @secret = nil
75
+ @key = nil
76
+ @spent = nil
77
+ end
78
+
79
+ private
80
+
81
+ def key
82
+ @key ||= OpenSSL::Digest::SHA256.digest("ruby_everywhere/auth-token\0#{secret}")
83
+ end
84
+
85
+ def discover_secret
86
+ if defined?(::Rails) && ::Rails.respond_to?(:application) && ::Rails.application
87
+ base = ::Rails.application.secret_key_base
88
+ return base if base && !base.empty?
89
+ end
90
+ %w[SECRET_KEY_BASE EVERYWHERE_AUTH_SECRET].each do |name|
91
+ value = ENV[name]
92
+ return value if value && !value.empty?
93
+ end
94
+ nil
95
+ end
96
+
97
+ # Spend a token, returning false if it was already spent. In-process only:
98
+ # across workers the ceiling is the token's own (seconds-long) expiry,
99
+ # which is the guarantee that actually matters — the token never leaves
100
+ # the device, and a replay would only re-establish the session its owner
101
+ # already holds.
102
+ def claim(token, expires_at)
103
+ digest = OpenSSL::Digest::SHA256.hexdigest(token.to_s)
104
+ spent_mutex.synchronize do
105
+ spent.reject! { |_, exp| exp <= now }
106
+ return false if spent.key?(digest)
107
+
108
+ spent[digest] = expires_at
109
+ true
110
+ end
111
+ end
112
+
113
+ def spent = @spent ||= {}
114
+ def spent_mutex = @spent_mutex ||= Mutex.new
115
+ def now = Time.now.to_i
116
+ end
117
+ end
118
+ end