Skip to content
Draft
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
62 changes: 62 additions & 0 deletions modules/ensemble/lib/widget/fintech/finicity_connect_script.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:convert';

/// Builds the JavaScript snippet that launches Finicity Connect inside a
/// [JsWidget] WebView.
///
/// All externally influenced string values are embedded via [jsonEncode] so
/// they cannot break out of the generated script.
String buildFinicityConnectInstantiateScript({
required String connectUri,
required String widgetId,
required int left,
required int top,
required String position,
String? overlay,
}) {
final overlayLine =
overlay != null ? 'overlay: ${jsonEncode(overlay)},\n ' : '';
final uriLiteral = jsonEncode(connectUri);
final widgetIdLiteral = jsonEncode(widgetId);
final positionLiteral = jsonEncode(position);

return '''
window.finicityConnect.launch($uriLiteral, {
$overlayLine success: (event) => {
console.log('Yay! User went through Connect', event);
event = {type:'success',data:event};
handleMessage($widgetIdLiteral,JSON.stringify(event));
},
cancel: (event) => {
console.log('The user cancelled the iframe', event);
event = {type:'cancel',data:event};
handleMessage($widgetIdLiteral,JSON.stringify(event));
},
error: (error) => {
console.error('Some runtime error was generated during insideConnect ', error);
event = {type:'error',data:error};
handleMessage($widgetIdLiteral,JSON.stringify(error));
},
loaded: (event) => {
console.log('This gets called only once after the iframe has finished loading ');
event = {type:'loaded',data:event};
handleMessage($widgetIdLiteral,JSON.stringify(event));
},
route: (event) => {
console.log('This is called as the user navigates through Connect ', event);
event = {type:'route',data:event};
handleMessage($widgetIdLiteral,JSON.stringify(event));
},
user: (event) => {
console.log('This is called as the user interacts with Connect ', event);
event = {type:'user',data:event};
handleMessage($widgetIdLiteral,JSON.stringify(event));
}
});
const finIFrame = document.getElementById("finicityConnectIframe");
if ( finIFrame ) {
finIFrame.style.left = '${left}px';
finIFrame.style.top = '${top}px';
finIFrame.style.position = $positionLiteral;
}
''';
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,15 @@
import 'package:ensemble/widget/fintech/finicityconnect/finicityconnect.dart';
import 'package:ensemble/widget/fintech/finicity_connect_script.dart';
import 'package:flutter/material.dart';
import 'package:js_widget/js_widget.dart';
import 'dart:convert';

class FinicityConnectState extends FinicityConnectStateBase {
String getScriptToInstantiate(
String c, String width, String height, String overlay) {
return '''
window.finicityConnect.launch("$c", {
$overlay
success: (event) => {
console.log('Yay! User went through Connect', event);
event = {type:'success',data:event};
handleMessage('${widget.controller.id}',JSON.stringify(event));
},
cancel: (event) => {
console.log('The user cancelled the iframe', event);
event = {type:'cancel',data:event};
handleMessage('${widget.controller.id}',JSON.stringify(event));
},
error: (error) => {
console.error('Some runtime error was generated during insideConnect ', error);
event = {type:'error',data:error};
handleMessage('${widget.controller.id}',JSON.stringify(error));
},
loaded: (event) => {
console.log('This gets called only once after the iframe has finished loading ');
event = {type:'loaded',data:event};
handleMessage('${widget.controller.id}',JSON.stringify(event));
},
route: (event) => {
console.log('This is called as the user navigates through Connect ', event);
event = {type:'route',data:event};
handleMessage('${widget.controller.id}',JSON.stringify(event));
},
user: (event) => {
console.log('This is called as the user interacts with Connect ', event);
event = {type:'user',data:event};
handleMessage('${widget.controller.id}',JSON.stringify(event));
}
});
const finIFrame = document.getElementById("finicityConnectIframe");
if ( finIFrame ) {
finIFrame.style.left = '${widget.controller.left}px';
finIFrame.style.top = '${widget.controller.top}px';
finIFrame.style.position = '${widget.controller.position}';
//finIFrame.style.width = '$width';
//finIFrame.style.height = '$height';
}
''';
}

@override
Widget buildWidget(BuildContext context) {
if (widget.controller.uri == '') {
return const Text("");
}
String overlay = '';
if (widget.controller.overlay != null) {
overlay = 'overlay: ${widget.controller.overlay!},';
}
String width = '100%';
if (widget.controller.width != 0) {
width = '${widget.controller.width}px';
Expand All @@ -75,9 +25,15 @@ class FinicityConnectState extends FinicityConnectStateBase {
print('message inside finicity and the message is $msg!');
executeAction(json.decode(msg));
},
scriptToInstantiate: (String c) {
return getScriptToInstantiate(c, width, height, overlay);
//return 'if (typeof ${widget.controller.chartVar} !== "undefined") ${widget.controller.chartVar}.destroy();${widget.controller.chartVar} = new Chart(document.getElementById("${widget.controller.chartId}"), $c);${widget.controller.chartVar}.update();';
scriptToInstantiate: (String connectUri) {
return buildFinicityConnectInstantiateScript(
connectUri: connectUri,
widgetId: widget.controller.id!,
left: widget.controller.left,
top: widget.controller.top,
position: widget.controller.position,
overlay: widget.controller.overlay?.toString(),
);
},
size: Size(widget.controller.width.toDouble(),
widget.controller.height.toDouble()),
Expand Down
48 changes: 48 additions & 0 deletions modules/ensemble/test/finicity_connect_script_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:ensemble/widget/fintech/finicity_connect_script.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('buildFinicityConnectInstantiateScript', () {
test('JSON-encodes connect URI for safe embedding', () {
final script = buildFinicityConnectInstantiateScript(
connectUri: 'https://connect2.finicity.com?token=abc',
widgetId: 'finicityConnect',
left: 0,
top: 10,
position: 'absolute',
);
expect(
script,
contains('window.finicityConnect.launch("https://connect2.finicity.com?token=abc"'),
);
});

test('neutralizes JavaScript breakout in connect URI', () {
const malicious = '"); alert(1); //';
final script = buildFinicityConnectInstantiateScript(
connectUri: malicious,
widgetId: 'finicityConnect',
left: 0,
top: 0,
position: 'absolute',
);
expect(script, contains('window.finicityConnect.launch('));
expect(script, isNot(contains('launch(""); alert(1);')));
expect(script, contains(r'\"); alert(1); //'));
});

test('JSON-encodes overlay and position values', () {
final script = buildFinicityConnectInstantiateScript(
connectUri: 'https://example.com',
widgetId: 'widget-1',
left: 4,
top: 8,
position: "absolute'; alert(1); '",
overlay: "rgba(0,0,0,0.5)",
);
expect(script, contains('overlay: "rgba(0,0,0,0.5)",'));
expect(script, contains(r"position = \"absolute'; alert(1); '\""));
expect(script, isNot(contains("position = 'absolute'; alert(1);")));
});
});
}
Loading