UNPKG

6.63 kBJavaScriptView Raw
1"use strict"
2
3const path = require("path");
4const fs = require("fs");
5
6class Security{
7 constructor(mscp){
8 this.mscp = mscp
9 this.cachedIPResult = {}
10 this.cachedAccessKeyResult = {}
11 this.accessKeyPromptHTMLPage = fs.readFileSync(path.join(__dirname, '/www/accesskeyprompt.html'))
12 }
13
14 async init(){
15 this.setup = this.mscp.setupHandler.setup
16 }
17
18 async onRequest(req, res, next){
19 let ip = '';
20 if(this.setup.trustProxy === true)
21 ip = req.ip
22 else if(this.setup.useForwardedHeader === true)
23 ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
24 else if(this.setup.useRealIPHeader === true)
25 ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
26 else
27 ip = req.connection.remoteAddress;
28
29 let data = this.mscp.server.extend({}, req.body||{}, req.query||{});
30
31 let area = "static"
32 if(req.path.startsWith("/mscp")){
33 area = "manage"
34 } else if(req.path.startsWith("/api")){
35 area = "api"
36 }
37
38 let accessKey = data.accessKey
39
40 let accessKeyCookieName = area == "api" ? "mscpAccessKeyAPI"
41 : area == "manage" ? "mscpAccessKeyManage"
42 : "mscpAccessKeyStatic";
43 if(accessKey === undefined){
44 accessKey = req.cookies[accessKeyCookieName];
45 }
46
47 if(accessKey !== undefined){
48 if(this.setup.accessKeyExpirationDays){
49 let today = new Date();
50 res.cookie(accessKeyCookieName, accessKey, {expires: new Date(today.getFullYear(),today.getMonth(),today.getDate()+this.setup.accessKeyExpirationDays), httpOnly: false });
51 } else {
52 res.cookie(accessKeyCookieName, accessKey, {expires: new Date(Date.now() + 1500000000), httpOnly: false });
53 }
54 }
55
56 req.mscp = {ip: ip, accessKey: accessKey, area: area}
57
58 if(req.path.startsWith("/mscp/js/")
59 || req.path.startsWith("/mscp/libs/")
60 || req.path.startsWith("/mscp/apibrowser")
61 || req.path.startsWith("/mscpui/static/")
62 || req.path == "/api/browse" || req.path == "/api/browse/"
63 || req.path == "/api" || req.path == "/api/")
64 {
65 next(); //Always allowed
66 } else if(this.validate(req.path, data, area, ip, accessKey)){
67 next()
68 } else if(req.get("Accept") !== undefined && req.get("Accept").indexOf("text/html") >= 0){
69 res.writeHead(200, "text/html");
70 res.end(this.accessKeyPromptHTMLPage)
71 console.log("Denied request for " + req.path + " from IP " + ip + (accessKey !== undefined ? " and access key \"" + accessKey + "\"" : ""))
72 } else {
73 res.writeHead(403);
74 res.end("You do not have access to this content.")
75 console.log("Denied request for " + req.path + " from IP " + ip + (accessKey !== undefined ? " and access key \"" + accessKey + "\"" : ""))
76 }
77 }
78
79 validate(path, data, area, ip, accessKey){
80 let scheme = area == "api" ? this.setup.api_access_scheme
81 : area == "manage" ? this.setup.manage_access_scheme
82 : area == "static" ? this.setup.static_access_scheme
83 : "deny_all"
84
85 switch(scheme){
86 case "full_access":
87 return true
88 case "deny_all":
89 return false
90 case "localhost":
91 return ip == "127.0.0.1" || ip == "::ffff:127.0.0.1" || ip == "::1"
92 case "access_rule":
93 return this.validateAccess(path, data, area, accessKey, ip)
94 case undefined:
95 return true
96 }
97
98 return false;
99
100 }
101
102 validateAccess(path, data, area, accessKey, ip){
103 if(this.setup.accessRules === undefined)
104 return false
105
106 let matchesIP = false;
107 let matchesKey = false;
108 for(let f of this.setup.accessRules){
109 if(f.area != area && f.area != "all")
110 continue
111
112 matchesIP = false;
113 matchesKey = false;
114
115 if(f.ip === undefined || f.ip == null || f.ip === ""){
116 matchesIP = true;
117 } else {
118 try{
119 if(new RegExp(f.ip).test(ip)){
120 matchesIP = true;
121 }
122 } catch(err){
123 console.log("Error validating IP " + ip + " against regexp \"" + f.ip + "\"");
124 console.log(err)
125 }
126 }
127
128 if(!matchesIP)
129 continue;
130
131 if(f.require_access_key !== true) {
132 matchesKey = true;
133 } else if(accessKey && f.accessKeys !== undefined && f.accessKeys.findIndex((ak) => ak.key == accessKey) >= 0){
134 matchesKey = true;
135 }
136
137 if(matchesIP && matchesKey && this.validateSubRules(path, data, f)){
138 return true;
139 }
140 }
141 return false;
142 }
143
144 validateSubRules(path, data, accessRule){
145 let subRules = accessRule.subRules || []
146 for(let sr of subRules){
147
148 if(sr.path.endsWith('*')){
149 let p = sr.path.substring(0, sr.path.length - 1)
150 if(!path.startsWith(p))
151 continue;
152 } else {
153 if(path != sr.path && path != sr.path + "/")
154 continue;
155 }
156
157 if(accessRule.default_permission == "allow" && sr.permission == "allow")
158 continue;
159
160 if(accessRule.default_permission == "deny" && sr.permission == "deny")
161 continue;
162
163 if(typeof sr.parameters === "string" && sr.parameters != ""){
164 let parmsMatch = true;
165 let vars = sr.parameters.split("\n")
166 for(let v of vars){
167 if(v == "")
168 continue;
169
170 let vsplits = v.split("=")
171 if(vsplits.length == 1 && data[vsplits[0]] === undefined){
172 //Doesn't have required parameter
173 parmsMatch = false;
174 break;
175 }
176
177 let varName = vsplits[0]
178 let varVal = vsplits[1]
179
180 if(varVal == "null" && data[varName] !== null){
181 parmsMatch = false;
182 break;
183 }
184
185 if(varVal == "undefined" && data[varName] !== undefined){
186 parmsMatch = false;
187 break;
188 }
189
190 if(data[varName] === undefined){
191 parmsMatch = false;
192 break;
193 }
194
195 if(varVal.startsWith("$")){
196 try{
197 if(!new RegExp(varVal.substring(1)).test(data[varName])){
198 parmsMatch = false;
199 break;
200 }
201 } catch(err){
202 parmsMatch = false;
203 }
204 } else {
205 if(varVal != data[varName]){
206 parmsMatch = false;
207 break;
208 }
209 }
210 }
211
212 if(!parmsMatch){
213 continue;
214 }
215 }
216
217 return sr.permission == "allow" ? true : false;
218 }
219
220 return accessRule.default_permission == "allow" ? true : false;
221 }
222
223}
224
225module.exports = Security