Skip to content

Commit 64d7407

Browse files
committed
fix: integration configuration serialization
1 parent ef8633f commit 64d7407

File tree

10 files changed

+292
-300
lines changed

10 files changed

+292
-300
lines changed

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
18

.whitesource

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"scanSettings": {
3+
"configMode": "AUTO",
4+
"configExternalURL": "",
5+
"enableLicenseViolations": "true",
6+
"projectToken": "",
7+
"baseBranches": []
8+
},
9+
"pullRequestStatusSettings": {
10+
"vulnerablePullRequestStatus": "failed",
11+
"displayMode": "diff",
12+
"useMendStatusNames": true
13+
},
14+
"issueSettings": {
15+
"minSeverityLevel": "MEDIUM"
16+
},
17+
"remediateSettings": {
18+
"workflowRules": {
19+
"enabled": true,
20+
"minVulnerabilitySeverity": "MEDIUM"
21+
}
22+
}
23+
}

README.md

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
# KnownUser.V3.Fastly
22

3-
Before getting started please read the [documentation](https://github.com/queueit/Documentation/tree/main/edge-connectors) to get acquainted with edge connectors.
3+
The Queue-it Security Framework ensures that end-users are not able to access your online application without first
4+
going through the queue for any and all �protected� areas and paths on your sites. The queue system is implemented by
5+
adding a server-side (request-level) integration that protects your online application by redirecting users to a waiting
6+
room according to web traffic settings in the Queue-it GO Platform. After the integration is complete, queue system
7+
behavior and operations are managed in Queue-it�s Go Platform and/or via the Queue-it Admin API.
48

5-
This Fastly Queue-it Connector (aka, Queue-it's server-side KnownUser connector) uses a Compute@Edge service to
9+
This Fastly Queue-it Connector SDK (aka, Queue-its server-side KnownUser connector) uses a Compute@Edge service to
610
integrate Queue-it's functionality into Fastly's network.
711

812
A Wasm service is required to utilize this connector.
913

1014
> You can find the latest released version [here](https://github.com/queueit/KnownUser.V3.Fastly/releases/latest).
1115
16+
## Introduction
17+
18+
When a user makes a request to your Fastly service our connector validates the request and if it is needed, it will
19+
redirect the user to the waiting room. After waiting in the waiting room, the queue engine will redirect the user back
20+
to your end attaching a query string parameter ( `queueittoken` ) containing some information about the user to the URL.
21+
The most important fields of the `queueittoken` are:
22+
23+
- q - The user's unique queue identifier
24+
- ts - A timestamp of how long this redirect is valid
25+
- h - A hash of the token
26+
27+
After the user returns from the queue, the connector will let the user continue his request to your backend ( without
28+
redirecting to the queue since the request has a valid queueittoken as query string) .
29+
1230
## Installation
1331

1432
There are two methods of installation:
@@ -24,14 +42,14 @@ There are two methods of installation:
2442
Edit the host, go to advanced options and fill in the same hostname in **Override host**
2543
- Go to **Dictionaries** and create a new dictionary named `IntegrationConfiguration`.
2644
Add the following items in the dictionary:
27-
- customerId: Your customer ID
28-
- apiKey: The API key for your account
29-
- secret: Your KnownUserV3 secret
30-
- queueItOrigin: The name of the queue-it host, in this case it's `queue-it`
45+
- customerId: Your customer ID
46+
- apiKey: The API key for your account
47+
- secret: Your KnownUserV3 secret
48+
- queueItOrigin: The name of the queue-it host, in this case it's `queue-it`
3149
You can find these values in the Go Queue-It self-service platform.
32-
- Download the latest package file (release-package.tar.gz) from the releases page and unarchive it.
50+
- Download the latest package from the releases page and unarchive it.
3351
- Edit the `fastly.toml` file and copy the ID of your service (you can see this by opening up the service in Fastly) and
34-
replace **{YourServiceId}** with it.
52+
replace __{YourServiceId}__ with it.
3553
- Archive the directory in the same format (tar.gz).
3654
- Go to `Package` in the Fastly service page and upload the package.
3755
- To finish up and deploy your service click on the **Activate** button.
@@ -40,17 +58,17 @@ There are two methods of installation:
4058

4159
- Go to the Fastly services page and create a new **Wasm** service and copy it's ID.
4260
- Clone this repository and edit the fastly.toml file, make sure to set the `service_id` field to the ID you copied.
43-
- Then click on *Origins* and add a new host that has the hostname of your origin server.
61+
- Then click on *Origins* and add a new host that has the hostname of your origin server.
4462
You can name the host **origin** or whatever you choose.
45-
- Create a host that has the hostname of `{yourCustomerId}.queue-it.net` and name it **queue-it**.
63+
- Create a host that has the hostname of `{yourCustomerId}.queue-it.net` and name it **queue-it**.
4664
Edit the host, go to advanced options and fill in the same hostname in **Override host**
4765
- Open up the service in Fastly and go to **Dictionaries** and create a new dictionary named `IntegrationConfiguration`
4866
.
4967
Add the following items in the dictionary:
50-
- customerId: Your customer ID
51-
- apiKey: The API key for your account
52-
- secret: Your KnownUserV3 secret
53-
- queueItOrigin: The name of the queue-it origin, in this case it's `queue-it`
68+
- customerId: Your customer ID
69+
- apiKey: The API key for your account
70+
- secret: Your KnownUserV3 secret
71+
- queueItOrigin: The name of the queue-it origin, in this case it's `queue-it`
5472
You can find these values in the Go Queue-It self-service platform.
5573
- You need to add some code that uses this connector. Here is an example:
5674

@@ -61,21 +79,17 @@ import {onQueueITRequest, IntegrationDetails, onQueueITResponse} from "@queue-it
6179
const req = Fastly.getClientRequest();
6280

6381
// This is optional and can be null if it's specified in your Dictionary
64-
/* const integrationDetails = new IntegrationDetails(
65-
{QueueItOriginName},
66-
{CustomerId},
67-
{SecretKey},
68-
{ApiKey},
69-
{workerHost});
82+
const integrationDetails = new IntegrationDetails(
83+
"QueueItOriginName",
84+
"CustomerId",
85+
"SecretKey",
86+
"ApiKey");
7087
let res = onQueueITRequest(req, integrationDetails);
71-
*/
72-
73-
let res = onQueueITRequest(req, null);
7488

7589
if (res != null) {
7690
Fastly.respondWith(res!);
7791
} else {
78-
const myOrigin = 'origin';
92+
const myOrigin = 'Ticketania';
7993
const cacheOverride = new Fastly.CacheOverride();
8094
const res = Fastly.fetch(req, {
8195
backend: myOrigin,
@@ -89,3 +103,27 @@ if (res != null) {
89103
- Build and deploy the package running `fastly compute build` and `fastly compute deploy` in the same directory.
90104
- Create desired waiting room(s), triggers, and actions in the Go Queue-It self-service platform.
91105
Then, save/publish the configuration.
106+
107+
## Providing the queue configuration
108+
109+
The recommended way is to use the Go Queue-it self-service portal to setup the configuration. The configuration
110+
specifies a set of Triggers and Actions. A Trigger is an expression matching one, more or all URLs on your website. When
111+
a user enter your website and the URL matches a Trigger-expression the corresponding Action will be triggered. The
112+
Action specifies which waiting room the users should be send to. In this way you can specify which waiting room(s)
113+
should protect which page(s) on the fly without changing the server-side integration.
114+
115+
## Protecting AJAX calls
116+
117+
If you need to protect AJAX calls beside page loads you need to add the below JavaScript tags to your pages:
118+
119+
```html
120+
121+
<script type="text/javascript" src="//static.queue-it.net/script/queueclient.min.js"></script>
122+
<script
123+
data-queueit-intercept-domain="{YOUR_CURRENT_DOMAIN}"
124+
data-queueit-intercept="true"
125+
data-queueit-c="{YOUR_CUSTOMER_ID}"
126+
type="text/javascript"
127+
src="//static.queue-it.net/script/queueconfigloader.min.js">
128+
</script>
129+
```

assembly/__tests__/CustomerIntegrationDecodingHandler.spec.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@ describe('CustomerIntegrationDecodingHandler', () => {
88
const integrationConfigString = `{
99
"Description":"tst",
1010
"Integrations":[
11+
{
12+
"Name":"Integration - With InvolvedWaitingRoomIds",
13+
"ActionType":"Cancel",
14+
"EventId":"event2",
15+
"InvolvedWaitingRoomIds":["event2"],
16+
"Triggers":[
17+
{
18+
"TriggerParts":[
19+
{
20+
"ValidatorType":"UrlValidator",
21+
"Operator":"Contains",
22+
"ValueToCompare":"test2",
23+
"IsNegative":false,
24+
"IsIgnoreCase":true
25+
}
26+
],
27+
"LogicalOperator":"And"
28+
}
29+
]
30+
},
1131
{
1232
"Name":"mojitest",
1333
"ActionType":"Queue",
@@ -1050,6 +1070,26 @@ describe('CustomerIntegrationDecodingHandler', () => {
10501070
"QueueDomain":"queueitknownusertst.test.queue-it.net",
10511071
"RedirectLogic":"AllowTParameter",
10521072
"ForcedTargetUrl":""
1073+
},
1074+
{
1075+
"Name":"Integration - With InvolvedWaitingRoomIds null",
1076+
"ActionType":"Queue",
1077+
"EventId":"event1",
1078+
"InvolvedWaitingRoomIds":null,
1079+
"Triggers":[
1080+
{
1081+
"TriggerParts":[
1082+
{
1083+
"ValidatorType":"UrlValidator",
1084+
"Operator":"Contains",
1085+
"ValueToCompare":"test1",
1086+
"IsNegative":false,
1087+
"IsIgnoreCase":true
1088+
}
1089+
],
1090+
"LogicalOperator":"And"
1091+
}
1092+
]
10531093
}
10541094
],
10551095
"CustomerId":"queueitknownusertst",
@@ -1066,21 +1106,29 @@ describe('CustomerIntegrationDecodingHandler', () => {
10661106

10671107
expect(customerIntegrationInfo.Version).toBe(55);
10681108
expect(customerIntegrationInfo.Description).toBe("tst", 'Description should be deserialized');
1069-
expect(customerIntegrationInfo.Integrations.length).toBe(23);
1070-
expect(customerIntegrationInfo.Integrations[0].Name).toBe("mojitest");
1071-
expect(customerIntegrationInfo.Integrations[1].Name).toBe("all pages");
1072-
let triggerModel = customerIntegrationInfo.Integrations[0].Triggers[0];
1109+
expect(customerIntegrationInfo.Integrations.length).toBe(25);
1110+
1111+
// Test InvolvedWaitingRoomIds array handling first (verifies no trigger accumulation bug)
1112+
expect(customerIntegrationInfo.Integrations[0].Name).toBe('Integration - With InvolvedWaitingRoomIds');
1113+
expect(customerIntegrationInfo.Integrations[0].Triggers.length).toBe(1, 'Integration - With InvolvedWaitingRoomIds should have exactly 1 trigger');
1114+
1115+
expect(customerIntegrationInfo.Integrations[1].Name).toBe("mojitest");
1116+
expect(customerIntegrationInfo.Integrations[2].Name).toBe("all pages");
1117+
let triggerModel = customerIntegrationInfo.Integrations[1].Triggers[0];
10731118
expect(triggerModel.LogicalOperator).toBe("And");
10741119
expect(triggerModel.TriggerParts[0].ValueToCompare).toBe("mojitest");
10751120
expect(triggerModel.TriggerParts[0].IsNegative).toBe(false);
10761121
expect(triggerModel.TriggerParts[0].IsIgnoreCase).toBe(true);
10771122

1078-
triggerModel = customerIntegrationInfo.Integrations[1].Triggers[0];
1123+
triggerModel = customerIntegrationInfo.Integrations[2].Triggers[0];
10791124
expect(triggerModel.TriggerParts[0].ValueToCompare).toBe('test02082018');
10801125

1081-
triggerModel = customerIntegrationInfo.Integrations[2].Triggers[0];
1126+
triggerModel = customerIntegrationInfo.Integrations[3].Triggers[0];
10821127
expect(triggerModel.TriggerParts[1].ValuesToCompare.length).toBe(2);
10831128
expect(triggerModel.TriggerParts[1].ValuesToCompare[0]).toBe('ignore-this-queue-event1-nodomain');
10841129
expect(triggerModel.TriggerParts[1].ValuesToCompare[1]).toBe('ignore-that-queue-event1-nodomain');
1130+
1131+
expect(customerIntegrationInfo.Integrations[24].Name).toBe('Integration - With InvolvedWaitingRoomIds null');
1132+
expect(customerIntegrationInfo.Integrations[24].Triggers.length).toBe(1, 'Integration - With InvolvedWaitingRoomIds null should have exactly 1 trigger');
10851133
});
10861134
})

assembly/helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Utils } from "./sdk/QueueITHelpers";
22
import { hmacString } from "./sdk/helpers/crypto";
33

44
export class QueueITHelper {
5-
static readonly KUP_VERSION: string = "fastly-1.0.4";
5+
static readonly KUP_VERSION: string = "fastly-1.1.0";
66

77
static configureKnownUserHashing(): void {
88
Utils.generateSHA256Hash = hmacString;

assembly/sdk/IntegrationConfig/CustomerIntegrationDecodingHandler.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import {
77
} from "./IntegrationConfigModel";
88
import {JSONDecoder} from "assemblyscript-json";
99

10+
const ARRAY_NAME_INTEGRATIONS = "Integrations";
11+
const ARRAY_NAME_TRIGGERS = "Triggers";
12+
const ARRAY_NAME_TRIGGER_PARTS = "TriggerParts";
13+
const ARRAY_NAME_VALUES_TO_COMPARE = "ValuesToCompare";
14+
1015
export class CustomerIntegrationDecodingHandler extends JSONHandler {
1116
private readonly _deserializedValue: CustomerIntegration;
1217
private isInIntegrations: bool = false;
@@ -19,6 +24,7 @@ export class CustomerIntegrationDecodingHandler extends JSONHandler {
1924
private currentTriggerPart: TriggerPart | null;
2025
private isInTriggerParts: bool = false;
2126
private isInTriggerPartValuesToCompare: bool = false;
27+
private arrayStack: string[] = [];
2228

2329
constructor() {
2430
super();
@@ -29,13 +35,18 @@ export class CustomerIntegrationDecodingHandler extends JSONHandler {
2935
// Handle array start
3036
// true means that nested object needs to be traversed, false otherwise
3137
// Note that returning false means JSONDecoder.startIndex need to be updated by handler
32-
if (name == "Integrations") {
38+
39+
// Push array name to stack BEFORE checking if we recognize it
40+
// This ensures every pushArray has a matching popArray
41+
this.arrayStack.push(name);
42+
43+
if (name == ARRAY_NAME_INTEGRATIONS) {
3344
this.isInIntegrations = true;
34-
} else if (name == "Triggers") {
45+
} else if (name == ARRAY_NAME_TRIGGERS) {
3546
this.isInTriggers = true;
36-
} else if (name == "TriggerParts") {
47+
} else if (name == ARRAY_NAME_TRIGGER_PARTS) {
3748
this.isInTriggerParts = true;
38-
} else if (name == "ValuesToCompare") {
49+
} else if (name == ARRAY_NAME_VALUES_TO_COMPARE) {
3950
this.isInTriggerPartValuesToCompare = true;
4051
}
4152
return this.isInIntegrations
@@ -46,15 +57,24 @@ export class CustomerIntegrationDecodingHandler extends JSONHandler {
4657

4758

4859
popArray(): void {
49-
if (this.isInTriggerPartValuesToCompare) {
60+
// Pop array name from stack
61+
if (this.arrayStack.length == 0) {
62+
return; // Defensive: should never happen
63+
}
64+
65+
const arrayName = this.arrayStack.pop();
66+
67+
// Only clear flags for recognized arrays
68+
if (arrayName == ARRAY_NAME_VALUES_TO_COMPARE) {
5069
this.isInTriggerPartValuesToCompare = false;
51-
} else if (this.isInTriggerParts) {
70+
} else if (arrayName == ARRAY_NAME_TRIGGER_PARTS) {
5271
this.isInTriggerParts = false;
53-
} else if (this.isInTriggers) {
72+
} else if (arrayName == ARRAY_NAME_TRIGGERS) {
5473
this.isInTriggers = false;
55-
} else if (this.isInIntegrations) {
74+
} else if (arrayName == ARRAY_NAME_INTEGRATIONS) {
5675
this.isInIntegrations = false;
5776
}
77+
// Note: Unrecognized arrays (like InvolvedWaitingRoomIds) are popped but don't affect flags
5878
}
5979

6080
pushObject(name: string): bool {
@@ -76,7 +96,10 @@ export class CustomerIntegrationDecodingHandler extends JSONHandler {
7696
this.currentTriggerModel!.TriggerParts.push(this.currentTriggerPart!);
7797
this.isInTriggerPart = false;
7898
} else if (this.isInTriggerModel) {
79-
this.currentIntegrationConfigModel!.Triggers.push(this.currentTriggerModel!);
99+
// Defensive: only add trigger if we have a valid integration
100+
if (this.currentIntegrationConfigModel != null) {
101+
this.currentIntegrationConfigModel!.Triggers.push(this.currentTriggerModel!);
102+
}
80103
this.isInTriggerModel = false;
81104
} else if (this.isInConfigModel) {
82105
this._deserializedValue.Integrations.push(this.currentIntegrationConfigModel!);

assembly/sdk/UserInQueueService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {ActionTypes, RequestValidationResult, QueueEventConfig, CancelEventConfi
33
import {StateInfo, UserInQueueStateCookieRepository} from './UserInQueueStateCookieRepository'
44

55
export class UserInQueueService {
6-
static readonly SDK_VERSION: string = "v3-asmscrpt-3.6.1";
6+
static readonly SDK_VERSION: string = "v3-asmscrpt-3.6.2";
77

88
constructor(private userInQueueStateRepository: UserInQueueStateCookieRepository) {
99
}

index.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
const fs = require("fs");
22
const loader = require("@assemblyscript/loader");
33
const imports = {};
4-
const wasmModule = loader.instantiateSync(
5-
fs.readFileSync(__dirname + "/build/optimized.wasm"),
6-
imports
7-
);
4+
const wasmModule = loader.instantiateSync(fs.readFileSync(__dirname + "/build/optimized.wasm"), imports);
85
module.exports = wasmModule.exports;

0 commit comments

Comments
 (0)