guacamole-connection-fetch 1.0.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.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # guacamole-connection-fetch
2
+
3
+ Isomorphic Guacamole token generation and connection helper for [encrypted JSON authentication](https://guacamole.apache.org/doc/gug/json-auth.html), written in vanilla JS. For use in Node.js, Cloudflare Workers, web browsers, or serverless environments
4
+
5
+ Requires the Fetch API (e.g. `fetch(request)`) to be available
6
+
7
+ ## Usage
8
+ ```js
9
+
10
+ import { GuacamoleUserConnection } from "guacamole-connection-fetch"
11
+ /**
12
+ makes request to '/api/tokens' via fetch()
13
+ */
14
+ async function interceptApiTokensRequest (request) {
15
+ // properly formed, minified JSON string as described in https://github.com/ridvanaltun/guacamole-rest-api-documentation/blob/master/docs/AUTHENTICATION.md#post-apitokens
16
+ const conns = {"Ubuntu origin 10.91.0.101":{"protocol":"ssh","parameters":{"dest-port":"22","hostname":"10.91.0.101","username":"UBUNTU_USERNAME","password":"UBUNTU_PASSWORD"}},"Win11 client 10.90.0.103":{"protocol":"rdp","parameters":{"ignore-cert":"true","enable-wallpaper":"true","security":"nla","username":"WINDOWS_USERNAME","password":"WINDOWS_PASSWORD","hostname":"10.90.0.103","dpi":"96","enable-font-smoothing":"true","enable-desktop-composition":"true","enable-menu-animations":"true","port":"3389"}}}
17
+ const res = await new GuacamoleUserConnection({
18
+ req: new Request("/api/tokens", request),
19
+ secretKey: GUAC_JSON_SECRET_KEY,
20
+ conns,
21
+ username: USER_EMAIL,
22
+ expires: 1990964469612,
23
+ headers: new Headers({
24
+ "Content-Type": "application/json",
25
+ // if using Cloudflare Access, pass these:
26
+ "cf-access-client-id": CF_ACCESS_SERVICE_TOKEN_CLIENT_ID,
27
+ "cf-access-client-secret": CF_ACCESS_SERVICE_TOKEN_CLIENT_SECRET,
28
+
29
+ ...request.headers,
30
+ }),
31
+ }).retrieveToken();
32
+
33
+ return new Response(res.body, res);
34
+ }
35
+
36
+ interceptApiTokensRequest(request);
37
+
38
+ ```
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install guacamole-connection --save-dev
44
+ # or
45
+ pnpm add guacamole-connection
46
+ # or
47
+ yarn add guacamole-connection
48
+ ```
@@ -0,0 +1,3 @@
1
+ declare const GuacamoleUserConnection: any;
2
+
3
+ export { GuacamoleUserConnection };
package/dist/index.mjs ADDED
@@ -0,0 +1,130 @@
1
+ // src/index.ts
2
+ var str2ab = (str) => Uint8Array.from(str, (x) => x.charCodeAt(0));
3
+ var ab2str = (buf) => String.fromCharCode(...new Uint8Array(buf));
4
+ var Auth = function(secretKey, jsonMsg) {
5
+ const auth = {
6
+ secretKey,
7
+ jsonMsg,
8
+ set key(val) {
9
+ this.secretKey = val;
10
+ },
11
+ /**
12
+ * Converts a UTF-8 "hex" string to an array buffer suitable for here
13
+ * in SubtleCrypto
14
+ */
15
+ get key() {
16
+ return new Uint8Array(
17
+ this.secretKey.match(/[\da-f]{2}/gi).map((h) => parseInt(h, 16))
18
+ );
19
+ },
20
+ /**
21
+ * @params jsonMsg - a standard Guacamole encrypted JSON object
22
+ * https://guacamole.apache.org/doc/gug/json-auth.html#json-format
23
+ */
24
+ async createHmac(jsonMsg2) {
25
+ const key = await crypto.subtle.importKey(
26
+ "raw",
27
+ this.key,
28
+ { name: "HMAC", hash: "SHA-256" },
29
+ false,
30
+ ["sign"]
31
+ );
32
+ const mac = await crypto.subtle.sign(
33
+ "HMAC",
34
+ key,
35
+ new TextEncoder().encode(jsonMsg2)
36
+ );
37
+ return ab2str(mac);
38
+ },
39
+ /**
40
+ * @params sig - the result of the HMAC signature
41
+ * @params authString - the plaintext JSON value
42
+ *
43
+ * Returns encrypts the concatenated result of sig + authString with AES-128-CBC
44
+ */
45
+ async encryptMessage(sig, authString) {
46
+ const prependedSig = str2ab(sig + authString);
47
+ const key = await crypto.subtle.importKey("raw", this.key, "AES-CBC", true, [
48
+ "encrypt",
49
+ "decrypt"
50
+ ]);
51
+ const ivBin = new ArrayBuffer(16);
52
+ const cipher = await crypto.subtle.encrypt(
53
+ { name: "AES-CBC", iv: ivBin },
54
+ key,
55
+ prependedSig
56
+ );
57
+ const cipherStr = ab2str(cipher);
58
+ return btoa(cipherStr);
59
+ },
60
+ async createToken() {
61
+ const signature = await this.createHmac(this.jsonMsg);
62
+ const encryptedSignature = await this.encryptMessage(signature, this.jsonMsg);
63
+ const encryptedSignatureEncoded = encodeURIComponent(encryptedSignature);
64
+ return encryptedSignatureEncoded;
65
+ }
66
+ };
67
+ return auth;
68
+ };
69
+ Object.create(Auth.prototype);
70
+ Auth.prototype.constructor = Auth.prototype;
71
+ var GuacamoleUserConnection = function(args) {
72
+ const {
73
+ req,
74
+ secretKey,
75
+ conns,
76
+ username,
77
+ expires = 1990964469612,
78
+ headers
79
+ } = args;
80
+ this.request = req;
81
+ this.expires = expires;
82
+ this.headers;
83
+ this.env = typeof secretKey === "string" ? { GUAC_JSON_SECRET_KEY: secretKey } : secretKey;
84
+ this.username = username ? username : req.headers.has("cf-access-authenticated-user-email") ? req.headers.get("cf-access-authenticated-user-email") : this.env.LOCAL_USER ? this.env.LOCAL_USER : "user@example.com";
85
+ this.url = new URL(req.url);
86
+ this.conns = {};
87
+ const parsedConns = typeof conns === "string" ? JSON.parse(conns) : conns;
88
+ for (const name of Object.keys(parsedConns)) {
89
+ this.conns[name] = {
90
+ protocol: parsedConns[name]["protocol"],
91
+ parameters: parsedConns[name]["parameters"]
92
+ };
93
+ }
94
+ this.authString = {
95
+ expires: this.expires,
96
+ username: this.username,
97
+ connections: this.conns
98
+ };
99
+ this.authenticatedGuacConnection = {};
100
+ this.auth = new Auth(
101
+ this.env.GUAC_JSON_SECRET_KEY,
102
+ JSON.stringify(this.authString)
103
+ );
104
+ this.expiration = new Date(this.expires).toLocaleDateString();
105
+ this.params = new URLSearchParams();
106
+ this.headers = new Headers(headers);
107
+ new Headers(this.request.headers).forEach((value, key) => {
108
+ this.headers.append(key, value);
109
+ });
110
+ };
111
+ GuacamoleUserConnection.prototype = Object.create(GuacamoleUserConnection.prototype);
112
+ GuacamoleUserConnection.prototype.constructor = GuacamoleUserConnection;
113
+ GuacamoleUserConnection.prototype.retrieveToken = async function() {
114
+ this.authenticatedGuacConnection = await this.auth.createToken();
115
+ if (this.request.url.includes("/api/tokens")) {
116
+ return fetch(this.request.url, {
117
+ headers: this.headers,
118
+ method: this.request.method,
119
+ body: new URLSearchParams(`data=${await this.authenticatedGuacConnection}`)
120
+ });
121
+ }
122
+ this.params.set("token", `${await this.authenticatedGuacConnection}`);
123
+ this.params.set("expiration", new Date(this.expires).toLocaleDateString());
124
+ this.url = new URL(this.request.url);
125
+ return fetch(this.url, this.request);
126
+ };
127
+ export {
128
+ GuacamoleUserConnection
129
+ };
130
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/* String to ArrayBuffer */\nconst str2ab = (str: string): ArrayBufferView<ArrayBuffer> =>\n Uint8Array.from(str, (x) => x.charCodeAt(0));\n\n/* ArrayBuffer to String */\nconst ab2str = (buf: ArrayBuffer): string =>\n String.fromCharCode(...new Uint8Array(buf));\n\ninterface Env {\n GUAC_JSON_SECRET_KEY: string;\n LOCAL_USER?: string;\n}\n\ninterface GuacConnection {\n protocol: string;\n parameters: Record<string, string>;\n}\n\ntype GuacConnections = Record<string, GuacConnection>;\n\nconst Auth = function (secretKey: string, jsonMsg: string) {\n // if (typeof jsonMsg !== 'string') jsonMsg = JSON.stringify(jsonMsg);\n const auth = {\n secretKey,\n jsonMsg,\n\n set key(val: string) {\n this.secretKey = val;\n },\n\n /**\n * Converts a UTF-8 \"hex\" string to an array buffer suitable for here\n * in SubtleCrypto\n */\n get key(): Uint8Array {\n return new Uint8Array(\n this.secretKey.match(/[\\da-f]{2}/gi)!.map((h) => parseInt(h, 16))\n );\n },\n\n /**\n * @params jsonMsg - a standard Guacamole encrypted JSON object\n * https://guacamole.apache.org/doc/gug/json-auth.html#json-format\n */\n async createHmac(jsonMsg: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n this.key,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"]\n );\n\n const mac = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(jsonMsg)\n );\n return ab2str(mac);\n },\n\n /**\n * @params sig - the result of the HMAC signature\n * @params authString - the plaintext JSON value\n *\n * Returns encrypts the concatenated result of sig + authString with AES-128-CBC\n */\n async encryptMessage(sig: string, authString: string): Promise<string> {\n const prependedSig = str2ab(sig + authString);\n const key = await crypto.subtle.importKey(\"raw\", this.key, \"AES-CBC\", true, [\n \"encrypt\",\n \"decrypt\",\n ]);\n const ivBin = new ArrayBuffer(16);\n const cipher = await crypto.subtle.encrypt(\n { name: \"AES-CBC\", iv: ivBin },\n key,\n prependedSig\n );\n const cipherStr = ab2str(cipher);\n return btoa(cipherStr);\n },\n\n async createToken(): Promise<string> {\n const signature = await this.createHmac(this.jsonMsg);\n const encryptedSignature = await this.encryptMessage(signature, this.jsonMsg);\n const encryptedSignatureEncoded = encodeURIComponent(encryptedSignature);\n return encryptedSignatureEncoded\n },\n };\n return auth;\n};\n\nObject.create(Auth.prototype);\nAuth.prototype.constructor = Auth.prototype;\n\n/**\n * @params request - the request object passed by CF workers\n * @params env - the env object passed by CF workers\n * @params conns - a 'connection' object or array of 'connection' objects conforming to\n * Guacamole's JSON spec: https://guacamole.apache.org/doc/gug/json-auth.html#json-format\n * @params expires - the number of days until token expires\n *\n * Returns a minified JSON string suitable for signing and encryption\n */\n// Create a constructor function\ninterface UserConnectionArgs {\n req: Request;\n secretKey: Env | string;\n conns: string | GuacConnections;\n username?: string;\n expires?: number;\n headers?: Headers;\n}\nconst GuacamoleUserConnection = function (this: any, args: UserConnectionArgs) {\n const {\n req,\n secretKey,\n conns,\n username,\n expires = 1990964469612,\n headers,\n } = args;\n\n this.request = req;\n this.expires = expires;\n this.headers \n this.env =\n typeof secretKey === \"string\"\n ? { GUAC_JSON_SECRET_KEY: secretKey }\n : secretKey;\n this.username = username\n ? username\n : req.headers.has(\"cf-access-authenticated-user-email\")\n ? (req.headers.get(\"cf-access-authenticated-user-email\") as string)\n : this.env.LOCAL_USER\n ? this.env.LOCAL_USER\n : \"user@example.com\";\n\n this.url = new URL(req.url);\n\n this.conns = {};\n\n const parsedConns: GuacConnections =\n typeof conns === \"string\" ? (JSON.parse(conns) as GuacConnections) : conns;\n\n for (const name of Object.keys(parsedConns)) {\n this.conns[name] = {\n protocol: parsedConns[name][\"protocol\"],\n parameters: parsedConns[name][\"parameters\"],\n };\n }\n\n this.authString = {\n expires: this.expires,\n username: this.username,\n connections: this.conns,\n };\n\n this.authenticatedGuacConnection = {};\n\n this.auth = new (Auth as any)(\n this.env.GUAC_JSON_SECRET_KEY,\n JSON.stringify(this.authString)\n );\n this.expiration = new Date(this.expires).toLocaleDateString();\n this.params = new URLSearchParams();\n this.headers = new Headers(headers)\n new Headers(this.request.headers).forEach((value, key) => {\n this.headers.append(key, value);\n });\n} as any;\n\nGuacamoleUserConnection.prototype = Object.create(GuacamoleUserConnection.prototype);\nGuacamoleUserConnection.prototype.constructor = GuacamoleUserConnection;\n\nGuacamoleUserConnection.prototype.retrieveToken = async function () {\n\n this.authenticatedGuacConnection = await this.auth.createToken();\n if (this.request.url.includes(\"/api/tokens\")) {\n return fetch(this.request.url, {\n headers: this.headers,\n method: this.request.method,\n body: new URLSearchParams(`data=${await this.authenticatedGuacConnection}`),\n });\n }\n this.params.set(\"token\", `${await this.authenticatedGuacConnection}`);\n this.params.set(\"expiration\", new Date(this.expires).toLocaleDateString());\n this.url = new URL(this.request.url);\n return fetch(this.url, this.request);\n};\n\nexport { GuacamoleUserConnection };"],"mappings":";AACA,IAAM,SAAS,CAAC,QACd,WAAW,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAG7C,IAAM,SAAS,CAAC,QACd,OAAO,aAAa,GAAG,IAAI,WAAW,GAAG,CAAC;AAc5C,IAAM,OAAO,SAAU,WAAmB,SAAiB;AAEzD,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IAEA,IAAI,IAAI,KAAa;AACnB,WAAK,YAAY;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAI,MAAkB;AACpB,aAAO,IAAI;AAAA,QACT,KAAK,UAAU,MAAM,cAAc,EAAG,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAAA,MAClE;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,WAAWA,UAAkC;AACjD,YAAM,MAAM,MAAM,OAAO,OAAO;AAAA,QAC9B;AAAA,QACA,KAAK;AAAA,QACL,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,QAChC;AAAA,QACA,CAAC,MAAM;AAAA,MACT;AAEA,YAAM,MAAM,MAAM,OAAO,OAAO;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,IAAI,YAAY,EAAE,OAAOA,QAAO;AAAA,MAClC;AACA,aAAO,OAAO,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,MAAM,eAAe,KAAa,YAAqC;AACrE,YAAM,eAAe,OAAO,MAAM,UAAU;AAC5C,YAAM,MAAM,MAAM,OAAO,OAAO,UAAU,OAAO,KAAK,KAAK,WAAW,MAAM;AAAA,QAC1E;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,IAAI,YAAY,EAAE;AAChC,YAAM,SAAS,MAAM,OAAO,OAAO;AAAA,QACjC,EAAE,MAAM,WAAW,IAAI,MAAM;AAAA,QAC7B;AAAA,QACA;AAAA,MACF;AACA,YAAM,YAAY,OAAO,MAAM;AAC/B,aAAO,KAAK,SAAS;AAAA,IACvB;AAAA,IAEA,MAAM,cAA+B;AACnC,YAAM,YAAY,MAAM,KAAK,WAAW,KAAK,OAAO;AACpD,YAAM,qBAAqB,MAAM,KAAK,eAAe,WAAW,KAAK,OAAO;AAC5E,YAAM,4BAA4B,mBAAmB,kBAAkB;AACvE,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,OAAO,OAAO,KAAK,SAAS;AAC5B,KAAK,UAAU,cAAc,KAAK;AAoBlC,IAAM,0BAA0B,SAAqB,MAA0B;AAC7E,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,EACF,IAAI;AAEJ,OAAK,UAAU;AACf,OAAK,UAAU;AACf,OAAK;AACL,OAAK,MACH,OAAO,cAAc,WACjB,EAAE,sBAAsB,UAAU,IAClC;AACN,OAAK,WAAW,WACZ,WACA,IAAI,QAAQ,IAAI,oCAAoC,IACnD,IAAI,QAAQ,IAAI,oCAAoC,IACrD,KAAK,IAAI,aACT,KAAK,IAAI,aACT;AAEJ,OAAK,MAAM,IAAI,IAAI,IAAI,GAAG;AAE1B,OAAK,QAAQ,CAAC;AAEd,QAAM,cACJ,OAAO,UAAU,WAAY,KAAK,MAAM,KAAK,IAAwB;AAEvE,aAAW,QAAQ,OAAO,KAAK,WAAW,GAAG;AAC3C,SAAK,MAAM,IAAI,IAAI;AAAA,MACjB,UAAU,YAAY,IAAI,EAAE,UAAU;AAAA,MACtC,YAAY,YAAY,IAAI,EAAE,YAAY;AAAA,IAC5C;AAAA,EACF;AAEA,OAAK,aAAa;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,UAAU,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,EACpB;AAEA,OAAK,8BAA8B,CAAC;AAEpC,OAAK,OAAO,IAAK;AAAA,IACf,KAAK,IAAI;AAAA,IACT,KAAK,UAAU,KAAK,UAAU;AAAA,EAChC;AACA,OAAK,aAAa,IAAI,KAAK,KAAK,OAAO,EAAE,mBAAmB;AAC5D,OAAK,SAAS,IAAI,gBAAgB;AAClC,OAAK,UAAU,IAAI,QAAQ,OAAO;AAClC,MAAI,QAAQ,KAAK,QAAQ,OAAO,EAAE,QAAQ,CAAC,OAAO,QAAQ;AACxD,SAAK,QAAQ,OAAO,KAAK,KAAK;AAAA,EAChC,CAAC;AACH;AAEA,wBAAwB,YAAY,OAAO,OAAO,wBAAwB,SAAS;AACnF,wBAAwB,UAAU,cAAc;AAEhD,wBAAwB,UAAU,gBAAgB,iBAAkB;AAElE,OAAK,8BAA8B,MAAM,KAAK,KAAK,YAAY;AAC/D,MAAI,KAAK,QAAQ,IAAI,SAAS,aAAa,GAAG;AAC5C,WAAO,MAAM,KAAK,QAAQ,KAAK;AAAA,MAC7B,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK,QAAQ;AAAA,MACrB,MAAM,IAAI,gBAAgB,QAAQ,MAAM,KAAK,2BAA2B,EAAE;AAAA,IAC5E,CAAC;AAAA,EACH;AACA,OAAK,OAAO,IAAI,SAAS,GAAG,MAAM,KAAK,2BAA2B,EAAE;AACpE,OAAK,OAAO,IAAI,cAAc,IAAI,KAAK,KAAK,OAAO,EAAE,mBAAmB,CAAC;AACzE,OAAK,MAAM,IAAI,IAAI,KAAK,QAAQ,GAAG;AACnC,SAAO,MAAM,KAAK,KAAK,KAAK,OAAO;AACrC;","names":["jsonMsg"]}
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "guacamole-connection-fetch",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "build": "npx tsup src/index.ts --format esm --dts --sourcemap --clean",
8
+ "build:esbuild": "npx esbuild src/index.ts --bundle --platform=browser --format=esm --outfile=dist/index.js"
9
+ },
10
+ "keywords": [],
11
+ "author": "shagamemnon",
12
+ "license": "ISC",
13
+ "devDependencies": {
14
+ "esbuild": "^0.27.2",
15
+ "tsup": "^8.5.1",
16
+ "typescript": "^5.9.3"
17
+ }
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,193 @@
1
+ /* String to ArrayBuffer */
2
+ const str2ab = (str: string): ArrayBufferView<ArrayBuffer> =>
3
+ Uint8Array.from(str, (x) => x.charCodeAt(0));
4
+
5
+ /* ArrayBuffer to String */
6
+ const ab2str = (buf: ArrayBuffer): string =>
7
+ String.fromCharCode(...new Uint8Array(buf));
8
+
9
+ interface Env {
10
+ GUAC_JSON_SECRET_KEY: string;
11
+ LOCAL_USER?: string;
12
+ }
13
+
14
+ interface GuacConnection {
15
+ protocol: string;
16
+ parameters: Record<string, string>;
17
+ }
18
+
19
+ type GuacConnections = Record<string, GuacConnection>;
20
+
21
+ const Auth = function (secretKey: string, jsonMsg: string) {
22
+ // if (typeof jsonMsg !== 'string') jsonMsg = JSON.stringify(jsonMsg);
23
+ const auth = {
24
+ secretKey,
25
+ jsonMsg,
26
+
27
+ set key(val: string) {
28
+ this.secretKey = val;
29
+ },
30
+
31
+ /**
32
+ * Converts a UTF-8 "hex" string to an array buffer suitable for here
33
+ * in SubtleCrypto
34
+ */
35
+ get key(): Uint8Array {
36
+ return new Uint8Array(
37
+ this.secretKey.match(/[\da-f]{2}/gi)!.map((h) => parseInt(h, 16))
38
+ );
39
+ },
40
+
41
+ /**
42
+ * @params jsonMsg - a standard Guacamole encrypted JSON object
43
+ * https://guacamole.apache.org/doc/gug/json-auth.html#json-format
44
+ */
45
+ async createHmac(jsonMsg: string): Promise<string> {
46
+ const key = await crypto.subtle.importKey(
47
+ "raw",
48
+ this.key,
49
+ { name: "HMAC", hash: "SHA-256" },
50
+ false,
51
+ ["sign"]
52
+ );
53
+
54
+ const mac = await crypto.subtle.sign(
55
+ "HMAC",
56
+ key,
57
+ new TextEncoder().encode(jsonMsg)
58
+ );
59
+ return ab2str(mac);
60
+ },
61
+
62
+ /**
63
+ * @params sig - the result of the HMAC signature
64
+ * @params authString - the plaintext JSON value
65
+ *
66
+ * Returns encrypts the concatenated result of sig + authString with AES-128-CBC
67
+ */
68
+ async encryptMessage(sig: string, authString: string): Promise<string> {
69
+ const prependedSig = str2ab(sig + authString);
70
+ const key = await crypto.subtle.importKey("raw", this.key, "AES-CBC", true, [
71
+ "encrypt",
72
+ "decrypt",
73
+ ]);
74
+ const ivBin = new ArrayBuffer(16);
75
+ const cipher = await crypto.subtle.encrypt(
76
+ { name: "AES-CBC", iv: ivBin },
77
+ key,
78
+ prependedSig
79
+ );
80
+ const cipherStr = ab2str(cipher);
81
+ return btoa(cipherStr);
82
+ },
83
+
84
+ async createToken(): Promise<string> {
85
+ const signature = await this.createHmac(this.jsonMsg);
86
+ const encryptedSignature = await this.encryptMessage(signature, this.jsonMsg);
87
+ const encryptedSignatureEncoded = encodeURIComponent(encryptedSignature);
88
+ return encryptedSignatureEncoded
89
+ },
90
+ };
91
+ return auth;
92
+ };
93
+
94
+ Object.create(Auth.prototype);
95
+ Auth.prototype.constructor = Auth.prototype;
96
+
97
+ /**
98
+ * @params request - the request object passed by CF workers
99
+ * @params env - the env object passed by CF workers
100
+ * @params conns - a 'connection' object or array of 'connection' objects conforming to
101
+ * Guacamole's JSON spec: https://guacamole.apache.org/doc/gug/json-auth.html#json-format
102
+ * @params expires - the number of days until token expires
103
+ *
104
+ * Returns a minified JSON string suitable for signing and encryption
105
+ */
106
+ // Create a constructor function
107
+ interface UserConnectionArgs {
108
+ req: Request;
109
+ secretKey: Env | string;
110
+ conns: string | GuacConnections;
111
+ username?: string;
112
+ expires?: number;
113
+ headers?: Headers;
114
+ }
115
+ const GuacamoleUserConnection = function (this: any, args: UserConnectionArgs) {
116
+ const {
117
+ req,
118
+ secretKey,
119
+ conns,
120
+ username,
121
+ expires = 1990964469612,
122
+ headers,
123
+ } = args;
124
+
125
+ this.request = req;
126
+ this.expires = expires;
127
+ this.headers
128
+ this.env =
129
+ typeof secretKey === "string"
130
+ ? { GUAC_JSON_SECRET_KEY: secretKey }
131
+ : secretKey;
132
+ this.username = username
133
+ ? username
134
+ : req.headers.has("cf-access-authenticated-user-email")
135
+ ? (req.headers.get("cf-access-authenticated-user-email") as string)
136
+ : this.env.LOCAL_USER
137
+ ? this.env.LOCAL_USER
138
+ : "user@example.com";
139
+
140
+ this.url = new URL(req.url);
141
+
142
+ this.conns = {};
143
+
144
+ const parsedConns: GuacConnections =
145
+ typeof conns === "string" ? (JSON.parse(conns) as GuacConnections) : conns;
146
+
147
+ for (const name of Object.keys(parsedConns)) {
148
+ this.conns[name] = {
149
+ protocol: parsedConns[name]["protocol"],
150
+ parameters: parsedConns[name]["parameters"],
151
+ };
152
+ }
153
+
154
+ this.authString = {
155
+ expires: this.expires,
156
+ username: this.username,
157
+ connections: this.conns,
158
+ };
159
+
160
+ this.authenticatedGuacConnection = {};
161
+
162
+ this.auth = new (Auth as any)(
163
+ this.env.GUAC_JSON_SECRET_KEY,
164
+ JSON.stringify(this.authString)
165
+ );
166
+ this.expiration = new Date(this.expires).toLocaleDateString();
167
+ this.params = new URLSearchParams();
168
+ this.headers = new Headers(headers)
169
+ new Headers(this.request.headers).forEach((value, key) => {
170
+ this.headers.append(key, value);
171
+ });
172
+ } as any;
173
+
174
+ GuacamoleUserConnection.prototype = Object.create(GuacamoleUserConnection.prototype);
175
+ GuacamoleUserConnection.prototype.constructor = GuacamoleUserConnection;
176
+
177
+ GuacamoleUserConnection.prototype.retrieveToken = async function () {
178
+
179
+ this.authenticatedGuacConnection = await this.auth.createToken();
180
+ if (this.request.url.includes("/api/tokens")) {
181
+ return fetch(this.request.url, {
182
+ headers: this.headers,
183
+ method: this.request.method,
184
+ body: new URLSearchParams(`data=${await this.authenticatedGuacConnection}`),
185
+ });
186
+ }
187
+ this.params.set("token", `${await this.authenticatedGuacConnection}`);
188
+ this.params.set("expiration", new Date(this.expires).toLocaleDateString());
189
+ this.url = new URL(this.request.url);
190
+ return fetch(this.url, this.request);
191
+ };
192
+
193
+ export { GuacamoleUserConnection };
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": false,
7
+ "declaration": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "allowSyntheticDefaultImports": true
14
+ },
15
+ "include": [
16
+ "src"
17
+ ]
18
+ }