Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); WIP - Syntax Parser by AndiLavera · Pull Request #24 · BridgeAPI-dev/BridgeAPI.rb · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_environment_variable.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidEnvironmentVariable < StandardError
end
4 changes: 4 additions & 0 deletions app/lib/exceptions/invalid_payload_key.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class InvalidPayloadKey < StandardError
end
2 changes: 1 addition & 1 deletion app/lib/json_web_token.rb
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require_relative './exceptions/jwt_expired_token'
require_relative './exceptions/jwt/expired_web_token'

class JsonWebToken
SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
Expand Down
11 changes: 11 additions & 0 deletions app/lib/request_deconstructor.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build
#
# Deconstructs the request & response from us & service B. Replaces
# any headers that contain environment variables with placeholder values
# to prevent data leakage.
class RequestDeconstructor
end
11 changes: 11 additions & 0 deletions app/lib/request_handler.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require_relative './exceptions'

# TODO: Build or delete
#
# Thinking some EventWorker functionality will be moved into here.
# SyntaxParser & RequestDeconstructor would be attributes and aid
# in setting up & properly saving requests/responses.
class RequestHandler
end
136 changes: 136 additions & 0 deletions app/lib/syntax_parser.rb
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require_relative './exceptions/invalid_environment_variable'
require_relative './exceptions/invalid_payload_key'

# This class parses user defined headers & payloads into the values we expect
# to send to the outbound service.
#
# Example:
#
# ```ruby
# custom_user_payload = {
# 'hello' => '$payload.top_level_key',
# 'environment_variable' => '$env.API_KEY',
# 'did_you' => '$payload.nested_key_1.nested_key_2.nested_key_3'
# }
#
# incoming_request_from_service_a = {
# 'top_level_key' => 'world',
# 'nested_key_1' => {
# 'nested_key_2' => {
# 'nested_key_3' => 'make it!'
# }
# }
# }
#
# bridge = Bridge.where(user_id: @current_user.id)
#
# syntax_parser = SyntaxParser.new(bridge)
#
# syntax_parser.parse_payload(
# incoming_request_from_service_a,
# custom_user_payload
# ) # => Hash(String, String) where $payload & $env are replaced with their respective values
#
# syntax_parser.parse_headers # => Array(Hash(String, String)) where $env is
# replaced with decrypted environment variable value
# ```
class SyntaxParser
# Set bridge headers & env's.
#
# @param [Bridge] bridge
def initialize(bridge)
@headers = bridge.headers
@environment_variables = bridge.environment_variables
end

# Parses the user's custom payload replacing any values containing `$env`
# or `$payload` with the respective value
#
# @param [Hash(String, String)] incoming_request
# @param [Hash(String, String)] user_data
#
# @return [Hash(String, String)]
def parse_payload(incoming_request, user_data)
@incoming_request = incoming_request
@user_data = user_data

parse_payload!

outbound_request
end

# Parses the user's headers replacing any values containing `$env`
# with the decrypted environment variable value
#
# @return [Array(Hash(String, String))]
def parse_headers
parse_headers!

outbound_headers
end

private

attr_reader :user_data, # User defined payload (bridge.data['payload'])
:outbound_request, # Parsed request returned from `parse_payload`
:incoming_request, # Inbound request from service A
:outbound_headers, # Parsed headers returned from `parse_headers`
:environment_variables,
:headers

# Iterates through user defined payload and parse values containing `$env` or `$payload`
# Reinitializes & mutates `outbound_request`
def parse_payload!
@outbound_request = {} # Reset

user_data.each do |key, val|
outbound_request[key] = if val.include?('$env')
fetch_environment_variable(val)
elsif val.include?('$payload')
fetch_payload_data(val)
else
val
end
end
end

# Iterates through user defined payload and parse values containing `$env`
# Reinitializes & mutates `outbound_headers`
def parse_headers!
@outbound_headers = [] # Reset
headers.each do |header|
parsed_header = {}
parsed_header[header.key] = if header.value.include?('$env')
fetch_environment_variable(header.value)
else
header.value
end

outbound_headers << parsed_header
end
end

def fetch_environment_variable(value)
key = value.split('.').last
environment_variable = environment_variables.where(key: key)
raise InvalidEnvironmentVariable unless environment_variable

environment_variable.decrypt
end

def fetch_payload_data(value)
values = value.split('.')
data = incoming_request # Set data to the incoming request

values.each_with_index do |val, idx|
next if idx.zero? # skip the $payload

data = data[val] # dig deeper into the request on each iteration
raise InvalidPayloadKey if data.nil?
end

data
end
end