-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathhandler.ts
More file actions
219 lines (202 loc) · 6.33 KB
/
handler.ts
File metadata and controls
219 lines (202 loc) · 6.33 KB
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
183
184
185
186
187
188
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
218
219
import { P2PCommandResponse } from '../../../@types/OceanNode.js'
import { OceanNode, RequestDataCheck, RequestLimiter } from '../../../OceanNode.js'
import {
Command,
ICommandHandler,
IValidateCommandHandler
} from '../../../@types/commands.js'
import {
// ValidateParams,
buildInvalidParametersResponse,
buildRateLimitReachedResponse,
ValidateParams
} from '../../httpRoutes/validateCommands.js'
import { getConfiguration } from '../../../utils/index.js'
import { CORE_LOGGER } from '../../../utils/logging/common.js'
import { ReadableString } from '../../P2P/handlers.js'
import { CONNECTION_HISTORY_DELETE_THRESHOLD } from '../../../utils/constants.js'
export abstract class BaseHandler implements ICommandHandler {
public nodeInstance: OceanNode
public constructor(oceanNode: OceanNode) {
this.nodeInstance = oceanNode
}
// abstract validate(command: Command): ValidateParams
abstract verifyParamsAndRateLimits(task: Command): Promise<P2PCommandResponse>
abstract handle(task: Command): Promise<P2PCommandResponse>
getOceanNode(): OceanNode {
return this.nodeInstance
}
// TODO LOG, implement all handlers
async checkRateLimit(caller: string | string[]): Promise<boolean> {
const requestMap = this.getOceanNode().getRequestMap()
const ratePerMinute = (await getConfiguration()).rateLimit
const requestTime = new Date().getTime()
let isOK = true
// If caller is not set, we cannot rate limit - allow the request
if (!caller) {
CORE_LOGGER.debug('No remote caller set, allowing request without rate limiting')
return true
}
// we have to clear this from time to time, so it does not grow forever
if (requestMap.size > CONNECTION_HISTORY_DELETE_THRESHOLD) {
CORE_LOGGER.info('Request history reached threeshold, cleaning cache...')
requestMap.clear()
}
const self = this
// common stuff
const updateRequestData = function (remoteCaller: string) {
const updatedRequestData = self.checkRequestData(
remoteCaller,
requestTime,
ratePerMinute
)
isOK = updatedRequestData.valid
requestMap.set(remoteCaller, updatedRequestData.updatedRequestData)
}
let data: RequestLimiter = null
if (Array.isArray(caller)) {
for (const remote of caller) {
if (!requestMap.has(remote)) {
data = {
requester: remote,
lastRequestTime: requestTime,
numRequests: 1
}
requestMap.set(remote, data)
} else {
updateRequestData(remote)
}
// do not proceed any further
if (!isOK) {
CORE_LOGGER.warn(
`Request denied (rate limit exceeded) for remote caller ${remote}. Current request map: ${JSON.stringify(
requestMap.get(remote)
)}`
)
return false
}
}
} else {
if (!requestMap.has(caller)) {
data = {
requester: caller,
lastRequestTime: requestTime,
numRequests: 1
}
requestMap.set(caller, data)
return true
} else {
updateRequestData(caller)
// log if request was denied
if (!isOK) {
CORE_LOGGER.warn(
`Request denied (rate limit exceeded) for remote caller ${caller}. Current request map: ${JSON.stringify(
requestMap.get(caller)
)}`
)
}
}
}
return isOK
}
/**
* Checks if the request is within the rate limit defined
* @param remote remote endpoint (ip or peer identifier)
* @param ratePerMinute number of calls per minute allowed (per ip or peer identifier)
* @returns updated request data
*/
checkRequestData(
remote: string,
currentTime: number,
ratePerMinute: number
): RequestDataCheck {
const requestMap = this.getOceanNode().getRequestMap()
const requestData: RequestLimiter = requestMap.get(remote)
const diffMinutes = ((currentTime - requestData.lastRequestTime) / 1000) * 60
// more than 1 minute difference means no problem
if (diffMinutes >= 1) {
// its fine
requestData.lastRequestTime = currentTime
requestData.numRequests = 1
return {
valid: true,
updatedRequestData: requestData
}
} else {
// requests in the same interval of 1 second
requestData.numRequests++
return {
valid: requestData.numRequests <= ratePerMinute,
updatedRequestData: requestData
}
}
}
shouldDenyTaskHandling(validationResponse: P2PCommandResponse): boolean {
return (
validationResponse.status.httpStatus !== 200 ||
validationResponse.status.error !== null
)
}
}
export abstract class CommandHandler
extends BaseHandler
implements IValidateCommandHandler
{
abstract validate(command: Command): ValidateParams
async verifyParamsAndRateLimits(task: Command): Promise<P2PCommandResponse> {
// first check rate limits, if any
if (!(await this.checkRateLimit(task.caller))) {
return buildRateLimitReachedResponse()
}
// then validate the command arguments
const validation = this.validate(task)
if (!validation.valid) {
return buildInvalidParametersResponse(validation)
}
// all good!
return {
stream: new ReadableString('OK'),
status: { httpStatus: 200, error: null }
}
}
async getAddressFromToken(authToken: string): Promise<string> {
const auth = this.getOceanNode().getAuth()
if (!auth) {
throw new Error('Auth not configured')
}
return (await auth.validateToken(authToken)).address
}
async validateTokenOrSignature(
authToken: string,
address: string,
nonce: string,
signature: string,
command: string
): Promise<P2PCommandResponse> {
const oceanNode = this.getOceanNode()
const auth = oceanNode.getAuth()
if (!auth) {
return {
stream: null,
status: { httpStatus: 401, error: 'Auth not configured' }
}
}
const isAuthRequestValid = await auth.validateAuthenticationOrToken({
token: authToken,
address,
nonce,
signature,
command
})
if (!isAuthRequestValid.valid) {
return {
stream: null,
status: { httpStatus: 401, error: isAuthRequestValid.error }
}
}
return {
stream: null,
status: { httpStatus: 200 }
}
}
}