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.
- checksums.yaml +4 -4
- data/README.md +255 -0
- data/bridge/README.md +48 -0
- data/bridge/everywhere/bridge.js +388 -3
- data/bridge/everywhere/native.css +203 -0
- data/bridge/package.json +6 -3
- data/lib/everywhere/android_resources.rb +217 -0
- data/lib/everywhere/android_sdk.rb +330 -0
- data/lib/everywhere/asset_catalog.rb +185 -0
- data/lib/everywhere/auth_handoff.rb +398 -0
- data/lib/everywhere/auth_token.rb +118 -0
- data/lib/everywhere/builders/android.rb +844 -0
- data/lib/everywhere/builders/ios.rb +72 -3
- data/lib/everywhere/commands/build.rb +51 -9
- data/lib/everywhere/commands/clean.rb +10 -5
- data/lib/everywhere/commands/dev.rb +46 -8
- data/lib/everywhere/commands/doctor.rb +89 -3
- data/lib/everywhere/commands/install.rb +1 -1
- data/lib/everywhere/commands/logs.rb +117 -8
- data/lib/everywhere/config.rb +513 -14
- data/lib/everywhere/emulator.rb +405 -0
- data/lib/everywhere/engine.rb +21 -0
- data/lib/everywhere/icon.rb +196 -0
- data/lib/everywhere/log_filter.rb +74 -0
- data/lib/everywhere/mobile_config_endpoint.rb +13 -0
- data/lib/everywhere/mobile_configs_controller.rb +18 -0
- data/lib/everywhere/native_helper.rb +198 -5
- data/lib/everywhere/omniauth.rb +88 -0
- data/lib/everywhere/paths.rb +47 -0
- data/lib/everywhere/version.rb +1 -1
- data/support/mobile/android/README.md +217 -0
- data/support/mobile/android/app/build.gradle.kts +156 -0
- data/support/mobile/android/app/everywhere.properties +8 -0
- data/support/mobile/android/app/native-packages.gradle.kts +16 -0
- data/support/mobile/android/app/proguard-rules.pro +21 -0
- data/support/mobile/android/app/src/debug/res/xml/network_security_config.xml +30 -0
- data/support/mobile/android/app/src/main/AndroidManifest.xml +42 -0
- data/support/mobile/android/app/src/main/assets/everywhere.json +9 -0
- data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.codepoints +2235 -0
- data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.ttf +0 -0
- data/support/mobile/android/app/src/main/assets/json/path-configuration.json +19 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/AuthFlow.kt +268 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereApplication.kt +178 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereConfig.kt +409 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereEvents.kt +68 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereHost.kt +101 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/IconFont.kt +211 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainActivity.kt +975 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainTabs.kt +201 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebBottomSheetFragment.kt +13 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebFragment.kt +36 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/BiometricsComponent.kt +535 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/HapticsComponent.kt +141 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/MenuComponent.kt +361 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/NotificationComponent.kt +151 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/PermissionsComponent.kt +271 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/StorageComponent.kt +101 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/WebControlChannel.kt +204 -0
- data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/extensions/EverywhereExtensions.kt +51 -0
- data/support/mobile/android/app/src/main/res/color/bottom_navigation_active_indicator.xml +4 -0
- data/support/mobile/android/app/src/main/res/color/bottom_navigation_item.xml +11 -0
- data/support/mobile/android/app/src/main/res/drawable/ic_launcher_foreground.xml +26 -0
- data/support/mobile/android/app/src/main/res/drawable/ic_tab_placeholder.xml +28 -0
- data/support/mobile/android/app/src/main/res/layout/activity_main.xml +116 -0
- data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet.xml +83 -0
- data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet_item.xml +35 -0
- data/support/mobile/android/app/src/main/res/layout/tab_more_sheet.xml +48 -0
- data/support/mobile/android/app/src/main/res/layout/tab_more_sheet_item.xml +35 -0
- data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +12 -0
- data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- data/support/mobile/android/app/src/main/res/values/colors.xml +21 -0
- data/support/mobile/android/app/src/main/res/values/ic_launcher_background.xml +5 -0
- data/support/mobile/android/app/src/main/res/values/strings.xml +18 -0
- data/support/mobile/android/app/src/main/res/values/styles.xml +32 -0
- data/support/mobile/android/app/src/main/res/values/themes.xml +53 -0
- data/support/mobile/android/app/src/main/res/values-night/colors.xml +11 -0
- data/support/mobile/android/app/src/main/res/values-night/themes.xml +33 -0
- data/support/mobile/android/app/src/main/res/xml/network_security_config.xml +12 -0
- data/support/mobile/android/app/src/stamped/AndroidManifest.xml +34 -0
- data/support/mobile/android/build.gradle.kts +8 -0
- data/support/mobile/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- data/support/mobile/android/gradle/wrapper/gradle-wrapper.properties +10 -0
- data/support/mobile/android/gradle.properties +14 -0
- data/support/mobile/android/gradlew +251 -0
- data/support/mobile/android/gradlew.bat +94 -0
- data/support/mobile/android/settings.gradle.kts +28 -0
- data/support/mobile/ios/App/AppDelegate.swift +7 -1
- data/support/mobile/ios/App/AuthFlow.swift +101 -0
- data/support/mobile/ios/App/Bridge/MenuComponent.swift +192 -0
- data/support/mobile/ios/App/EverywhereConfig.swift +105 -0
- data/support/mobile/ios/App/SceneDelegate.swift +144 -0
- data/support/mobile/ios/App.xcodeproj/project.pbxproj +8 -0
- data/support/mobile/ios/README.md +10 -0
- 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
|