@mug-lab/cookie-auth-proxy 0.1.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.
@@ -0,0 +1,228 @@
1
+ /**
2
+ * リモートAPIがローカルプロキシと同一Originに見えるように、
3
+ * ブラウザ向けリクエストと上流レスポンスを書き換える。
4
+ */
5
+
6
+ function rewriteRequestHeaders(req, targetOrigin, publicOrigin, options = {}) {
7
+ const headers = { ...req.headers };
8
+
9
+ headers.host = targetOrigin.host;
10
+ headers.origin = targetOrigin.origin;
11
+
12
+ if (headers.referer) {
13
+ headers.referer = String(headers.referer).replace(
14
+ publicOrigin.origin,
15
+ targetOrigin.origin,
16
+ );
17
+ }
18
+
19
+ headers["x-forwarded-host"] = publicOrigin.host;
20
+ headers["x-forwarded-proto"] = publicOrigin.protocol.replace(":", "");
21
+ headers["x-forwarded-port"] =
22
+ publicOrigin.port || (publicOrigin.protocol === "https:" ? "443" : "80");
23
+
24
+ if (options.apiCookie) {
25
+ headers.cookie = options.apiCookie;
26
+ }
27
+
28
+ delete headers["accept-encoding"];
29
+ delete headers.connection;
30
+
31
+ return headers;
32
+ }
33
+
34
+ /**
35
+ * Set-Cookieをlocalhostで保存できる属性へ書き換える。
36
+ */
37
+ function rewriteSetCookie(value, targetOrigin, publicOrigin) {
38
+ return value
39
+ .replace(
40
+ new RegExp(`;\\s*Domain=${escapeRegExp(targetOrigin.hostname)}`, "gi"),
41
+ "",
42
+ )
43
+ .replace(/;\s*Domain=[^;]+/gi, "")
44
+ .replace(
45
+ /;\s*Secure/gi,
46
+ publicOrigin.protocol === "https:" ? "; Secure" : "",
47
+ )
48
+ .replace(
49
+ /;\s*SameSite=None/gi,
50
+ publicOrigin.protocol === "https:" ? "; SameSite=None" : "; SameSite=Lax",
51
+ );
52
+ }
53
+
54
+ /**
55
+ * ヘッダー値内のAPI OriginをローカルOriginへ書き換える。
56
+ */
57
+ function rewriteHeaderValue(value, targetOrigin, publicOrigin) {
58
+ if (Array.isArray(value)) {
59
+ return value.map((entry) =>
60
+ rewriteHeaderValue(entry, targetOrigin, publicOrigin),
61
+ );
62
+ }
63
+
64
+ if (typeof value !== "string") return value;
65
+ return value.replaceAll(targetOrigin.origin, publicOrigin.origin);
66
+ }
67
+
68
+ /**
69
+ * Locationヘッダ内URLのクエリパラメータを、設定された汎用ルールで差し替える。
70
+ */
71
+ function rewriteLocationHeaderQuery(value, context) {
72
+ if (Array.isArray(value)) {
73
+ return value.map((entry) => rewriteLocationHeaderQuery(entry, context));
74
+ }
75
+
76
+ if (typeof value !== "string") return value;
77
+
78
+ let locationUrl;
79
+ try {
80
+ locationUrl = new URL(value);
81
+ } catch {
82
+ return value;
83
+ }
84
+
85
+ const matchedRules = (context.locationHeaderQueryOverrideRules || []).filter(
86
+ (rule) => {
87
+ if (!rule.pathMatcher.test(context.requestPathname)) return false;
88
+ if (rule.locationMatcher && !rule.locationMatcher.test(locationUrl.href)) {
89
+ return false;
90
+ }
91
+ return true;
92
+ },
93
+ );
94
+
95
+ if (matchedRules.length === 0) return value;
96
+
97
+ matchedRules.forEach((rule) => {
98
+ Object.entries(rule.queryParams).forEach(([name, template]) => {
99
+ locationUrl.searchParams.set(
100
+ name,
101
+ renderLocationQueryTemplate(String(template), context),
102
+ );
103
+ });
104
+ });
105
+
106
+ return locationUrl.href;
107
+ }
108
+
109
+ /**
110
+ * Locationヘッダクエリ差し替え値のテンプレート変数をOrigin値で展開する。
111
+ */
112
+ function renderLocationQueryTemplate(template, context) {
113
+ return template
114
+ .replaceAll("{publicOrigin}", context.publicOrigin.origin)
115
+ .replaceAll("{apiOrigin}", context.targetOrigin.origin)
116
+ .replaceAll("{clientOrigin}", context.clientOrigin.origin);
117
+ }
118
+
119
+ /**
120
+ * Cookieや同一OriginリンクがローカルプロキシOriginで動くように、
121
+ * 上流レスポンスヘッダーを書き換える。
122
+ *
123
+ * @param {object} headers 上流レスポンスヘッダー。
124
+ * @param {URL} targetOrigin リモートAPIまたはclientの上流Origin。
125
+ * @param {URL} publicOrigin ブラウザから見えるローカルプロキシOrigin。
126
+ * @param {object} options Locationヘッダの例外処理に使う書き換えオプション。
127
+ * @returns {object} ブラウザへ返せるヘッダー。
128
+ */
129
+ function rewriteResponseHeaders(
130
+ headers,
131
+ targetOrigin,
132
+ publicOrigin,
133
+ options = {},
134
+ ) {
135
+ const rewritten = {};
136
+
137
+ for (const [key, value] of Object.entries(headers)) {
138
+ const lowerKey = key.toLowerCase();
139
+
140
+ if (lowerKey === "set-cookie") {
141
+ rewritten[key] = Array.isArray(value)
142
+ ? value.map((entry) =>
143
+ rewriteSetCookie(entry, targetOrigin, publicOrigin),
144
+ )
145
+ : rewriteSetCookie(String(value), targetOrigin, publicOrigin);
146
+ continue;
147
+ }
148
+
149
+ if (lowerKey === "location" && options.preserveLocationHeader) {
150
+ rewritten[key] = rewriteLocationHeaderQuery(value, {
151
+ publicOrigin,
152
+ targetOrigin,
153
+ clientOrigin: options.clientOrigin,
154
+ requestPathname: options.requestPathname,
155
+ locationHeaderQueryOverrideRules:
156
+ options.locationHeaderQueryOverrideRules,
157
+ });
158
+ continue;
159
+ }
160
+
161
+ if (
162
+ lowerKey === "location" ||
163
+ lowerKey === "content-location" ||
164
+ lowerKey === "refresh"
165
+ ) {
166
+ rewritten[key] = rewriteHeaderValue(value, targetOrigin, publicOrigin);
167
+ continue;
168
+ }
169
+
170
+ if (lowerKey === "content-security-policy") {
171
+ rewritten[key] = rewriteHeaderValue(value, targetOrigin, publicOrigin);
172
+ continue;
173
+ }
174
+
175
+ rewritten[key] = value;
176
+ }
177
+
178
+ delete rewritten["content-length"];
179
+ delete rewritten["Content-Length"];
180
+ delete rewritten["transfer-encoding"];
181
+ delete rewritten["Transfer-Encoding"];
182
+ return rewritten;
183
+ }
184
+
185
+ /**
186
+ * レスポンス本文内Originを書き換える対象content-typeか判定する。
187
+ */
188
+ function shouldRewriteBody(headers) {
189
+ const contentType = String(headers["content-type"] || "");
190
+ return /text\/html|application\/json|text\/plain|application\/javascript|text\/javascript/i.test(
191
+ contentType,
192
+ );
193
+ }
194
+
195
+ /**
196
+ * レスポンス本文内のAPI OriginをローカルOriginへ書き換える。
197
+ */
198
+ function rewriteBody(buffer, targetOrigin, publicOrigin) {
199
+ return Buffer.from(
200
+ buffer
201
+ .toString("utf8")
202
+ .replaceAll(targetOrigin.origin, publicOrigin.origin),
203
+ "utf8",
204
+ );
205
+ }
206
+
207
+ /**
208
+ * content-typeからJSON Override適用対象レスポンスか判定する。
209
+ */
210
+ function shouldApplyJsonOverrides(headers) {
211
+ const contentType = String(headers["content-type"] || "");
212
+ return /application\/json|\+json/i.test(contentType);
213
+ }
214
+
215
+ /**
216
+ * 文字列を正規表現リテラル相当として扱えるようエスケープする。
217
+ */
218
+ function escapeRegExp(value) {
219
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
220
+ }
221
+
222
+ module.exports = {
223
+ rewriteBody,
224
+ rewriteRequestHeaders,
225
+ rewriteResponseHeaders,
226
+ shouldApplyJsonOverrides,
227
+ shouldRewriteBody,
228
+ };
@@ -0,0 +1,8 @@
1
+ Place exported stub JSON files in this folder to import them at proxy startup.
2
+
3
+ Files are loaded in filename order. Use names such as:
4
+
5
+ - `001-common.json`
6
+ - `010-user-admin.json`
7
+
8
+ The file format is the same JSON produced by the admin screen's Stub Export.