generated from pagopa/io-template-typescript
-
Notifications
You must be signed in to change notification settings - Fork 3
/
source_ip_check.ts
182 lines (169 loc) · 5.16 KB
/
source_ip_check.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import { isNone } from "fp-ts/lib/Option";
/*
* A request wrapper that checks whether the source IP is contained in the
* CIDRs allowed to make requests.
*/
import { IPString } from "@pagopa/ts-commons/lib/strings";
import { ITuple2, Tuple2 } from "@pagopa/ts-commons/lib/tuples";
import CIDRMatcher = require("cidr-matcher");
import {
IResponseErrorForbiddenNotAuthorized,
IResponseErrorInternal,
ResponseErrorForbiddenNotAuthorized,
ResponseErrorInternal
} from "@pagopa/ts-commons/lib/responses";
import { CIDR } from "../../generated/definitions/CIDR";
import { toAuthorizedCIDRs } from "../models/service";
import { ClientIp } from "./middlewares/client_ip_middleware";
import { IAzureUserAttributes } from "./middlewares/azure_user_attributes";
/**
* Whether IP is contained in the provided CIDRs
*/
const isContainedInCidrs = (
ip: IPString,
cidrs: ReadonlySet<string>
): boolean => {
const matcher = new CIDRMatcher();
cidrs.forEach(c => {
matcher.addNetworkClass(c);
});
return matcher.contains(ip);
};
export function checkSourceIpForHandler<P1, O>(
f: (p1: P1) => Promise<O>,
e: (p1: P1) => ITuple2<ClientIp, ReadonlySet<string>>
): (p1: P1) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
export function checkSourceIpForHandler<P1, P2, O>(
f: (p1: P1, p2: P2) => Promise<O>,
e: (p1: P1, p2: P2) => ITuple2<ClientIp, ReadonlySet<string>>
): (p1: P1, p2: P2) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
export function checkSourceIpForHandler<P1, P2, P3, O>(
f: (p1: P1, p2: P2, p3: P3) => Promise<O>,
e: (p1: P1, p2: P2, p3: P3) => ITuple2<ClientIp, ReadonlySet<string>>
): (
p1: P1,
p2: P2,
p3: P3
) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
export function checkSourceIpForHandler<P1, P2, P3, P4, O>(
f: (p1: P1, p2: P2, p3: P3, p4: P4) => Promise<O>,
e: (p1: P1, p2: P2, p3: P3, p4: P4) => ITuple2<ClientIp, ReadonlySet<string>>
): (
p1: P1,
p2: P2,
p3: P3,
p4: P4
) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
export function checkSourceIpForHandler<P1, P2, P3, P4, P5, O>(
f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => Promise<O>,
e: (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5
) => ITuple2<ClientIp, ReadonlySet<string>>
): (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5
) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
export function checkSourceIpForHandler<P1, P2, P3, P4, P5, P6, O>(
f: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) => Promise<O>,
e: (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5,
p6: P6
) => ITuple2<ClientIp, ReadonlySet<string>>
): (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5,
p6: P6
) => Promise<O | IResponseErrorForbiddenNotAuthorized>;
/**
* Whether the request is coming from an allowed IP.
*
* @param handler The handler to be wrapped
* @param extractor A Function that takes the parameters of f and extracts the
* X-Forwarded-For header from the request and the authorized
* CIDRs from the user attributes
*/
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
export function checkSourceIpForHandler<P1, P2, P3, P4, P5, P6, O>(
handler: (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) => Promise<O>,
extractor: (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5,
p6: P6
) => ITuple2<ClientIp, ReadonlySet<string>>
): (
p1: P1,
p2: P2,
p3: P3,
p4: P4,
p5: P5,
p6: P6
) => Promise<
O | IResponseErrorForbiddenNotAuthorized | IResponseErrorInternal
> {
// eslint-disable-next-line max-params, @typescript-eslint/explicit-function-return-type
return (p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) =>
new Promise(resolve => {
// extract the x-forwarded-for header and the allowed cidrs from the params
const x = extractor(p1, p2, p3, p4, p5, p6);
const maybeClientIp = x.e1;
const cidrs = x.e2;
if (isNone(maybeClientIp)) {
return resolve(
ResponseErrorInternal(
"IP address cannot be extracted from the request"
)
);
}
if (
// either allowed CIDRs is empty or client IP is contained in allowed CIDRs
cidrs.size === 0 ||
isContainedInCidrs(maybeClientIp.value, cidrs)
) {
// forward request to handler
return resolve(handler(p1, p2, p3, p4, p5, p6));
} else {
// respond with Not Authorized
return resolve(ResponseErrorForbiddenNotAuthorized);
}
});
}
/**
* A helper for building the Tuple2 needed by checkSourceIpForHandler.
*
* @param request The Express Request object
* @param userAttributes The IAzureUserAttributes object provided by
* AzureUserAttributesMiddleware.
*/
export const clientIPAndCidrTuple = (
clientIp: ClientIp,
userAttributes: IAzureUserAttributes
): ITuple2<ClientIp, ReadonlySet<string>> => {
/**
* Add the default /32 subnet to an IP without any subnet.
*/
const withDefaultSubnet = (ip: CIDR): CIDR =>
ip.indexOf("/") !== -1 ? ip : (`${ip}/32` as CIDR);
return Tuple2(
clientIp,
toAuthorizedCIDRs(
Array.from(userAttributes.service.authorizedCIDRs).map(withDefaultSubnet)
)
);
};