@usageflow/express 0.1.2 → 0.1.5
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/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +49 -6
- package/dist/plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/plugin.ts +243 -193
package/dist/plugin.d.ts
CHANGED
|
@@ -15,4 +15,5 @@ export declare class ExpressUsageFlowAPI extends UsageFlowAPI {
|
|
|
15
15
|
private collectRequestMetadata;
|
|
16
16
|
private executeRequestWithMetadata;
|
|
17
17
|
createMiddleware(routes: Route[], whitelistRoutes?: Route[]): (request: Request, response: Response, next: NextFunction) => Promise<void>;
|
|
18
|
+
protected guessLedgerId(request: Request): string;
|
|
18
19
|
}
|
package/dist/plugin.js
CHANGED
|
@@ -15,12 +15,12 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
15
15
|
method: request.method,
|
|
16
16
|
url: request.route?.path || request.path || request.url || "/",
|
|
17
17
|
rawUrl: request.originalUrl || "/",
|
|
18
|
-
clientIP:
|
|
18
|
+
clientIP: clientIP || "unknown",
|
|
19
19
|
userAgent: request.headers["user-agent"],
|
|
20
20
|
timestamp: new Date().toISOString(),
|
|
21
21
|
headers,
|
|
22
|
-
queryParams: request.query,
|
|
23
|
-
pathParams: request.params,
|
|
22
|
+
queryParams: Object.keys(request.query).length > 0 ? request.query : null,
|
|
23
|
+
pathParams: Object.keys(request.params).length > 0 ? request.params : null,
|
|
24
24
|
body: request.body,
|
|
25
25
|
};
|
|
26
26
|
return metadata;
|
|
@@ -81,8 +81,9 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
81
81
|
return next();
|
|
82
82
|
}
|
|
83
83
|
const metadata = await this.collectRequestMetadata(request);
|
|
84
|
+
let ledgerId = this.guessLedgerId(request);
|
|
84
85
|
try {
|
|
85
|
-
await this.executeRequestWithMetadata(
|
|
86
|
+
await this.executeRequestWithMetadata(ledgerId, metadata, request, response);
|
|
86
87
|
// Capture response data
|
|
87
88
|
const originalEnd = response.end;
|
|
88
89
|
response.end = function (chunk, encoding, cb) {
|
|
@@ -129,8 +130,12 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
129
130
|
"x-usage-key": self.apiKey,
|
|
130
131
|
"Content-Type": "application/json",
|
|
131
132
|
};
|
|
133
|
+
if (request.usageflow?.startTime) {
|
|
134
|
+
metadata.requestDuration =
|
|
135
|
+
Date.now() - request.usageflow.startTime;
|
|
136
|
+
}
|
|
132
137
|
const payload = {
|
|
133
|
-
alias:
|
|
138
|
+
alias: ledgerId,
|
|
134
139
|
amount: 1,
|
|
135
140
|
allocationId: request.usageflow?.eventId,
|
|
136
141
|
metadata,
|
|
@@ -142,7 +147,7 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
142
147
|
}).catch((error) => {
|
|
143
148
|
console.error("Error finalizing request:", error);
|
|
144
149
|
});
|
|
145
|
-
// Handle the different
|
|
150
|
+
// Handle the different overloadsx
|
|
146
151
|
if (typeof chunk === "function") {
|
|
147
152
|
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
148
153
|
}
|
|
@@ -163,6 +168,44 @@ class ExpressUsageFlowAPI extends core_1.UsageFlowAPI {
|
|
|
163
168
|
}
|
|
164
169
|
};
|
|
165
170
|
}
|
|
171
|
+
guessLedgerId(request) {
|
|
172
|
+
const method = request.method;
|
|
173
|
+
const url = request.route?.path || request.path;
|
|
174
|
+
const config = this.apiConfig;
|
|
175
|
+
if (!config?.identityFieldName || !config?.identityFieldLocation) {
|
|
176
|
+
return `${method} ${url}`;
|
|
177
|
+
}
|
|
178
|
+
const fieldName = config.identityFieldName;
|
|
179
|
+
const location = config.identityFieldLocation;
|
|
180
|
+
switch (location) {
|
|
181
|
+
case "path_params":
|
|
182
|
+
if (request.params?.[fieldName]) {
|
|
183
|
+
return `${method} ${url} ${request.params[fieldName]}`;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
case "query_params":
|
|
187
|
+
if (request.query?.[fieldName]) {
|
|
188
|
+
return `${method} ${url} ${request.query[fieldName]}`;
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
case "body":
|
|
192
|
+
if (request.body?.[fieldName]) {
|
|
193
|
+
return `${method} ${url} ${request.body[fieldName]}`;
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
case "bearer_token":
|
|
197
|
+
const authHeader = request.headers.authorization;
|
|
198
|
+
const token = this.extractBearerToken(authHeader);
|
|
199
|
+
if (token) {
|
|
200
|
+
const claims = this.decodeJwtUnverified(token);
|
|
201
|
+
if (claims?.[fieldName]) {
|
|
202
|
+
return `${method} ${url} ${claims[fieldName]}`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
return `${method} ${url}`;
|
|
208
|
+
}
|
|
166
209
|
}
|
|
167
210
|
exports.ExpressUsageFlowAPI = ExpressUsageFlowAPI;
|
|
168
211
|
//# sourceMappingURL=plugin.js.map
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":";;;AACA,0CAAuE;AAcvE,MAAa,mBAAoB,SAAQ,mBAAY;
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usageflow/express",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "UsageFlow plugin for Express applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"prepare": "npm run build"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@usageflow/core": "^0.1.
|
|
13
|
+
"@usageflow/core": "^0.1.3",
|
|
14
14
|
"express": "^4.18.0"
|
|
15
15
|
},
|
|
16
16
|
"homepage": "https://usageflow.io",
|
package/src/plugin.ts
CHANGED
|
@@ -2,217 +2,267 @@ import { Request, Response, NextFunction } from "express";
|
|
|
2
2
|
import { UsageFlowAPI, Route, RequestMetadata } from "@usageflow/core";
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
namespace Express {
|
|
6
|
+
interface Request {
|
|
7
|
+
usageflow?: {
|
|
8
|
+
startTime: number;
|
|
9
|
+
eventId?: string;
|
|
10
|
+
metadata?: RequestMetadata;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
12
13
|
}
|
|
13
|
-
}
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export class ExpressUsageFlowAPI extends UsageFlowAPI {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Get client IP, handling forwarded headers
|
|
25
|
-
let clientIP = request.ip;
|
|
26
|
-
const forwardedFor = request.headers["x-forwarded-for"];
|
|
27
|
-
if (forwardedFor && typeof forwardedFor === "string") {
|
|
28
|
-
clientIP = forwardedFor.split(",")[0].trim();
|
|
29
|
-
}
|
|
17
|
+
private async collectRequestMetadata(
|
|
18
|
+
request: Request,
|
|
19
|
+
): Promise<RequestMetadata> {
|
|
20
|
+
const headers = this.sanitizeHeaders(
|
|
21
|
+
request.headers as Record<string, string>,
|
|
22
|
+
);
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
timestamp: new Date().toISOString(),
|
|
38
|
-
headers,
|
|
39
|
-
queryParams: request.query as Record<string, any>,
|
|
40
|
-
pathParams: request.params,
|
|
41
|
-
body: request.body,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return metadata;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private async executeRequestWithMetadata(
|
|
48
|
-
ledgerId: string,
|
|
49
|
-
metadata: RequestMetadata,
|
|
50
|
-
request: Request,
|
|
51
|
-
response: Response,
|
|
52
|
-
): Promise<void> {
|
|
53
|
-
if (!this.apiKey) {
|
|
54
|
-
throw new Error("API key not initialized");
|
|
55
|
-
}
|
|
24
|
+
// Get client IP, handling forwarded headers
|
|
25
|
+
let clientIP = request.ip;
|
|
26
|
+
const forwardedFor = request.headers["x-forwarded-for"];
|
|
27
|
+
if (forwardedFor && typeof forwardedFor === "string") {
|
|
28
|
+
clientIP = forwardedFor.split(",")[0].trim();
|
|
29
|
+
}
|
|
56
30
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers,
|
|
74
|
-
body: JSON.stringify(payload),
|
|
75
|
-
},
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const data = await response.json();
|
|
79
|
-
|
|
80
|
-
if (response.status === 400) {
|
|
81
|
-
throw new Error(data.message);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (response.ok) {
|
|
85
|
-
request.usageflow!.eventId = data.eventId;
|
|
86
|
-
request.usageflow!.metadata = metadata;
|
|
87
|
-
} else {
|
|
88
|
-
throw new Error(data.message || "Unknown error occurred");
|
|
89
|
-
}
|
|
90
|
-
} catch (error: any) {
|
|
91
|
-
if (
|
|
92
|
-
error.message ==
|
|
93
|
-
"Failed to use resource after retries: Faile to preform operation"
|
|
94
|
-
) {
|
|
95
|
-
throw new Error("Failed to allocate resource");
|
|
96
|
-
}
|
|
97
|
-
throw error;
|
|
31
|
+
const metadata: RequestMetadata = {
|
|
32
|
+
method: request.method,
|
|
33
|
+
url: request.route?.path || request.path || request.url || "/",
|
|
34
|
+
rawUrl: request.originalUrl || "/",
|
|
35
|
+
clientIP: clientIP || "unknown",
|
|
36
|
+
userAgent: request.headers["user-agent"] as string,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
headers,
|
|
39
|
+
queryParams: Object.keys(request.query).length > 0 ? request.query : null,
|
|
40
|
+
pathParams: Object.keys(request.params).length > 0 ? request.params : null,
|
|
41
|
+
body: request.body,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return metadata;
|
|
98
45
|
}
|
|
99
|
-
}
|
|
100
46
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
47
|
+
private async executeRequestWithMetadata(
|
|
48
|
+
ledgerId: string,
|
|
49
|
+
metadata: RequestMetadata,
|
|
50
|
+
request: Request,
|
|
51
|
+
response: Response,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
if (!this.apiKey) {
|
|
54
|
+
throw new Error("API key not initialized");
|
|
55
|
+
}
|
|
105
56
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
57
|
+
const headers = {
|
|
58
|
+
"x-usage-key": this.apiKey,
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
};
|
|
109
61
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
62
|
+
const payload = {
|
|
63
|
+
alias: ledgerId,
|
|
64
|
+
amount: 1,
|
|
65
|
+
metadata,
|
|
66
|
+
};
|
|
113
67
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(
|
|
70
|
+
`${this.usageflowUrl}/api/v1/ledgers/measure/allocate`,
|
|
71
|
+
{
|
|
72
|
+
method: "POST",
|
|
73
|
+
headers,
|
|
74
|
+
body: JSON.stringify(payload),
|
|
75
|
+
},
|
|
76
|
+
);
|
|
117
77
|
|
|
118
|
-
|
|
119
|
-
return next();
|
|
120
|
-
}
|
|
78
|
+
const data = await response.json();
|
|
121
79
|
|
|
122
|
-
|
|
80
|
+
if (response.status === 400) {
|
|
81
|
+
throw new Error(data.message);
|
|
82
|
+
}
|
|
123
83
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
84
|
+
if (response.ok) {
|
|
85
|
+
request.usageflow!.eventId = data.eventId;
|
|
86
|
+
request.usageflow!.metadata = metadata;
|
|
87
|
+
} else {
|
|
88
|
+
throw new Error(data.message || "Unknown error occurred");
|
|
89
|
+
}
|
|
90
|
+
} catch (error: any) {
|
|
91
|
+
if (
|
|
92
|
+
error.message ==
|
|
93
|
+
"Failed to use resource after retries: Faile to preform operation"
|
|
94
|
+
) {
|
|
95
|
+
throw new Error("Failed to allocate resource");
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public createMiddleware(routes: Route[], whitelistRoutes: Route[] = []) {
|
|
102
|
+
const routesMap = this.createRoutesMap(routes);
|
|
103
|
+
const whitelistMap = this.createRoutesMap(whitelistRoutes);
|
|
104
|
+
const self = this;
|
|
105
|
+
|
|
106
|
+
return async (request: Request, response: Response, next: NextFunction) => {
|
|
107
|
+
const method = request.method;
|
|
108
|
+
const url = request.route?.path || request.path || request.url;
|
|
109
|
+
|
|
110
|
+
request.usageflow = {
|
|
111
|
+
startTime: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (this.shouldSkipRoute(method, url || "", whitelistMap)) {
|
|
115
|
+
return next();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!this.shouldMonitorRoute(method, url || "", routesMap)) {
|
|
119
|
+
return next();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const metadata = await this.collectRequestMetadata(request);
|
|
123
|
+
let ledgerId = this.guessLedgerId(request);
|
|
131
124
|
|
|
132
|
-
// Capture response data
|
|
133
|
-
const originalEnd = response.end;
|
|
134
|
-
response.end = function (
|
|
135
|
-
this: Response,
|
|
136
|
-
chunk?: any,
|
|
137
|
-
encoding?: BufferEncoding,
|
|
138
|
-
cb?: () => void,
|
|
139
|
-
) {
|
|
140
|
-
if (!request.usageflow?.eventId) {
|
|
141
|
-
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const metadata = request.usageflow?.metadata || {};
|
|
145
|
-
(metadata as any).responseStatusCode = response.statusCode;
|
|
146
|
-
|
|
147
|
-
// Add response body to metadata if it exists
|
|
148
|
-
if (chunk) {
|
|
149
125
|
try {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
126
|
+
await this.executeRequestWithMetadata(
|
|
127
|
+
ledgerId,
|
|
128
|
+
metadata,
|
|
129
|
+
request,
|
|
130
|
+
response,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Capture response data
|
|
134
|
+
const originalEnd = response.end;
|
|
135
|
+
response.end = function (
|
|
136
|
+
this: Response,
|
|
137
|
+
chunk?: any,
|
|
138
|
+
encoding?: BufferEncoding,
|
|
139
|
+
cb?: () => void,
|
|
140
|
+
) {
|
|
141
|
+
if (!request.usageflow?.eventId) {
|
|
142
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const metadata: Record<string, any> =
|
|
146
|
+
request.usageflow?.metadata || {};
|
|
147
|
+
metadata.responseStatusCode = response.statusCode;
|
|
148
|
+
|
|
149
|
+
// Add response body to metadata if it exists
|
|
150
|
+
if (chunk) {
|
|
151
|
+
try {
|
|
152
|
+
if (typeof chunk === "string") {
|
|
153
|
+
// Try to parse as JSON if it's a string
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(chunk);
|
|
156
|
+
(metadata as any).body = parsed;
|
|
157
|
+
} catch {
|
|
158
|
+
// If not valid JSON, use the string as is
|
|
159
|
+
(metadata as any).body = chunk;
|
|
160
|
+
}
|
|
161
|
+
} else if (Buffer.isBuffer(chunk)) {
|
|
162
|
+
const str = chunk.toString("utf8");
|
|
163
|
+
// Try to parse as JSON if it's a buffer
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(str);
|
|
166
|
+
(metadata as any).body = parsed;
|
|
167
|
+
} catch {
|
|
168
|
+
// If not valid JSON, use the string as is
|
|
169
|
+
(metadata as any).body = str;
|
|
170
|
+
}
|
|
171
|
+
} else if (typeof chunk === "object") {
|
|
172
|
+
(metadata as any).body = chunk;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error("Error parsing response body:", error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const headers = {
|
|
180
|
+
"x-usage-key": self.apiKey!,
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (request.usageflow?.startTime) {
|
|
185
|
+
metadata.requestDuration =
|
|
186
|
+
Date.now() - request.usageflow.startTime;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const payload = {
|
|
190
|
+
alias: ledgerId,
|
|
191
|
+
amount: 1,
|
|
192
|
+
allocationId: request.usageflow?.eventId,
|
|
193
|
+
metadata,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
fetch(`${self.usageflowUrl}/api/v1/ledgers/measure/allocate/use`, {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers,
|
|
199
|
+
body: JSON.stringify(payload),
|
|
200
|
+
}).catch((error) => {
|
|
201
|
+
console.error("Error finalizing request:", error);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Handle the different overloadsx
|
|
205
|
+
if (typeof chunk === "function") {
|
|
206
|
+
return originalEnd.call(this, undefined, "utf8", chunk);
|
|
207
|
+
}
|
|
208
|
+
if (typeof encoding === "function") {
|
|
209
|
+
return originalEnd.call(this, chunk, "utf8", encoding);
|
|
210
|
+
}
|
|
211
|
+
return originalEnd.call(this, chunk, encoding || "utf8", cb);
|
|
212
|
+
} as typeof response.end;
|
|
213
|
+
|
|
214
|
+
next();
|
|
215
|
+
} catch (error: any) {
|
|
216
|
+
console.error("Error executing request with metadata:", error);
|
|
217
|
+
response.status(400).json({
|
|
218
|
+
message: error.message,
|
|
219
|
+
blocked: true,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
174
222
|
}
|
|
175
|
-
|
|
223
|
+
};
|
|
224
|
+
}
|
|
176
225
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
226
|
+
protected guessLedgerId(request: Request): string {
|
|
227
|
+
const method = request.method;
|
|
228
|
+
const url = request.route?.path || request.path;
|
|
229
|
+
const config = this.apiConfig
|
|
181
230
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
allocationId: request.usageflow?.eventId,
|
|
186
|
-
metadata,
|
|
187
|
-
};
|
|
231
|
+
if (!config?.identityFieldName || !config?.identityFieldLocation) {
|
|
232
|
+
return `${method} ${url}`;
|
|
233
|
+
}
|
|
188
234
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
235
|
+
const fieldName = config.identityFieldName;
|
|
236
|
+
const location = config.identityFieldLocation;
|
|
237
|
+
|
|
238
|
+
switch (location) {
|
|
239
|
+
case "path_params":
|
|
240
|
+
if (request.params?.[fieldName]) {
|
|
241
|
+
return `${method} ${url} ${request.params[fieldName]}`;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case "query_params":
|
|
245
|
+
if (request.query?.[fieldName]) {
|
|
246
|
+
return `${method} ${url} ${request.query[fieldName]}`;
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case "body":
|
|
250
|
+
if (request.body?.[fieldName]) {
|
|
251
|
+
return `${method} ${url} ${request.body[fieldName]}`;
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
case "bearer_token":
|
|
255
|
+
const authHeader = request.headers.authorization;
|
|
256
|
+
const token = this.extractBearerToken(authHeader);
|
|
257
|
+
if (token) {
|
|
258
|
+
const claims = this.decodeJwtUnverified(token);
|
|
259
|
+
if (claims?.[fieldName]) {
|
|
260
|
+
return `${method} ${url} ${claims[fieldName]}`;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return `${method} ${url}`;
|
|
267
|
+
}
|
|
218
268
|
}
|