You've already forked nginx-proxy-manager
							
							
				mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 18:05:34 +03:00 
			
		
		
		
	Convert backend to ESM
- About 5 years overdue - Remove eslint, use bomejs instead
This commit is contained in:
		| @@ -4,28 +4,32 @@ | ||||
|  * "scope" in this file means "where did this token come from and what is using it", so 99% of the time | ||||
|  * the "scope" is going to be "user" because it would be a user token. This is not to be confused with | ||||
|  * the "role" which could be "user" or "admin". The scope in fact, could be "worker" or anything else. | ||||
|  * | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| const _              = require('lodash'); | ||||
| const logger         = require('../logger').access; | ||||
| const Ajv            = require('ajv/dist/2020'); | ||||
| const error          = require('./error'); | ||||
| const userModel      = require('../models/user'); | ||||
| const proxyHostModel = require('../models/proxy_host'); | ||||
| const TokenModel     = require('../models/token'); | ||||
| const roleSchema     = require('./access/roles.json'); | ||||
| const permsSchema    = require('./access/permissions.json'); | ||||
| import fs from "node:fs"; | ||||
| import { dirname } from "node:path"; | ||||
| import { fileURLToPath } from "node:url"; | ||||
| import Ajv from "ajv/dist/2020.js"; | ||||
| import _ from "lodash"; | ||||
| import { access as logger } from "../logger.js"; | ||||
| import proxyHostModel from "../models/proxy_host.js"; | ||||
| import TokenModel from "../models/token.js"; | ||||
| import userModel from "../models/user.js"; | ||||
| import permsSchema from "./access/permissions.json" with { type: "json" }; | ||||
| import roleSchema from "./access/roles.json" with { type: "json" }; | ||||
| import errs from "./error.js"; | ||||
|  | ||||
| module.exports = function (token_string) { | ||||
| 	let Token                 = new TokenModel(); | ||||
| 	let token_data            = null; | ||||
| 	let initialised           = false; | ||||
| 	let object_cache          = {}; | ||||
| const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = dirname(__filename); | ||||
|  | ||||
| export default function (token_string) { | ||||
| 	const Token = TokenModel(); | ||||
| 	let token_data = null; | ||||
| 	let initialised = false; | ||||
| 	const object_cache = {}; | ||||
| 	let allow_internal_access = false; | ||||
| 	let user_roles            = []; | ||||
| 	let permissions           = {}; | ||||
| 	let user_roles = []; | ||||
| 	let permissions = {}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Loads the Token object from the token string | ||||
| @@ -37,10 +41,10 @@ module.exports = function (token_string) { | ||||
| 			if (initialised) { | ||||
| 				resolve(); | ||||
| 			} else if (!token_string) { | ||||
| 				reject(new error.PermissionError('Permission Denied')); | ||||
| 				reject(new errs.PermissionError("Permission Denied")); | ||||
| 			} else { | ||||
| 				resolve(Token.load(token_string) | ||||
| 					.then((data) => { | ||||
| 				resolve( | ||||
| 					Token.load(token_string).then((data) => { | ||||
| 						token_data = data; | ||||
|  | ||||
| 						// At this point we need to load the user from the DB and make sure they: | ||||
| @@ -48,21 +52,25 @@ module.exports = function (token_string) { | ||||
| 						// - still have the appropriate scopes for this token | ||||
| 						// This is only required when the User ID is supplied or if the token scope has `user` | ||||
|  | ||||
| 						if (token_data.attrs.id || (typeof token_data.scope !== 'undefined' && _.indexOf(token_data.scope, 'user') !== -1)) { | ||||
| 						if ( | ||||
| 							token_data.attrs.id || | ||||
| 							(typeof token_data.scope !== "undefined" && | ||||
| 								_.indexOf(token_data.scope, "user") !== -1) | ||||
| 						) { | ||||
| 							// Has token user id or token user scope | ||||
| 							return userModel | ||||
| 								.query() | ||||
| 								.where('id', token_data.attrs.id) | ||||
| 								.andWhere('is_deleted', 0) | ||||
| 								.andWhere('is_disabled', 0) | ||||
| 								.allowGraph('[permissions]') | ||||
| 								.withGraphFetched('[permissions]') | ||||
| 								.where("id", token_data.attrs.id) | ||||
| 								.andWhere("is_deleted", 0) | ||||
| 								.andWhere("is_disabled", 0) | ||||
| 								.allowGraph("[permissions]") | ||||
| 								.withGraphFetched("[permissions]") | ||||
| 								.first() | ||||
| 								.then((user) => { | ||||
| 									if (user) { | ||||
| 										// make sure user has all scopes of the token | ||||
| 										// The `user` role is not added against the user row, so we have to just add it here to get past this check. | ||||
| 										user.roles.push('user'); | ||||
| 										user.roles.push("user"); | ||||
|  | ||||
| 										let is_ok = true; | ||||
| 										_.forEach(token_data.scope, (scope_item) => { | ||||
| @@ -72,21 +80,19 @@ module.exports = function (token_string) { | ||||
| 										}); | ||||
|  | ||||
| 										if (!is_ok) { | ||||
| 											throw new error.AuthError('Invalid token scope for User'); | ||||
| 										} else { | ||||
| 											initialised = true; | ||||
| 											user_roles  = user.roles; | ||||
| 											permissions = user.permissions; | ||||
| 											throw new errs.AuthError("Invalid token scope for User"); | ||||
| 										} | ||||
|  | ||||
| 										initialised = true; | ||||
| 										user_roles = user.roles; | ||||
| 										permissions = user.permissions; | ||||
| 									} else { | ||||
| 										throw new error.AuthError('User cannot be loaded for Token'); | ||||
| 										throw new errs.AuthError("User cannot be loaded for Token"); | ||||
| 									} | ||||
| 								}); | ||||
| 						} else { | ||||
| 							initialised = true; | ||||
| 						} | ||||
| 					})); | ||||
| 						initialised = true; | ||||
| 					}), | ||||
| 				); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
| @@ -101,53 +107,55 @@ module.exports = function (token_string) { | ||||
| 	 */ | ||||
| 	this.loadObjects = (object_type) => { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			if (Token.hasScope('user')) { | ||||
| 				if (typeof token_data.attrs.id === 'undefined' || !token_data.attrs.id) { | ||||
| 					reject(new error.AuthError('User Token supplied without a User ID')); | ||||
| 			if (Token.hasScope("user")) { | ||||
| 				if ( | ||||
| 					typeof token_data.attrs.id === "undefined" || | ||||
| 					!token_data.attrs.id | ||||
| 				) { | ||||
| 					reject(new errs.AuthError("User Token supplied without a User ID")); | ||||
| 				} else { | ||||
| 					let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; | ||||
| 					const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; | ||||
| 					let query; | ||||
|  | ||||
| 					if (typeof object_cache[object_type] === 'undefined') { | ||||
| 					if (typeof object_cache[object_type] === "undefined") { | ||||
| 						switch (object_type) { | ||||
|  | ||||
| 						// USERS - should only return yourself | ||||
| 						case 'users': | ||||
| 							resolve(token_user_id ? [token_user_id] : []); | ||||
| 							break; | ||||
| 							// USERS - should only return yourself | ||||
| 							case "users": | ||||
| 								resolve(token_user_id ? [token_user_id] : []); | ||||
| 								break; | ||||
|  | ||||
| 							// Proxy Hosts | ||||
| 						case 'proxy_hosts': | ||||
| 							query = proxyHostModel | ||||
| 								.query() | ||||
| 								.select('id') | ||||
| 								.andWhere('is_deleted', 0); | ||||
| 							case "proxy_hosts": | ||||
| 								query = proxyHostModel | ||||
| 									.query() | ||||
| 									.select("id") | ||||
| 									.andWhere("is_deleted", 0); | ||||
|  | ||||
| 							if (permissions.visibility === 'user') { | ||||
| 								query.andWhere('owner_user_id', token_user_id); | ||||
| 							} | ||||
| 								if (permissions.visibility === "user") { | ||||
| 									query.andWhere("owner_user_id", token_user_id); | ||||
| 								} | ||||
|  | ||||
| 							resolve(query | ||||
| 								.then((rows) => { | ||||
| 									let result = []; | ||||
| 									_.forEach(rows, (rule_row) => { | ||||
| 										result.push(rule_row.id); | ||||
| 									}); | ||||
| 								resolve( | ||||
| 									query.then((rows) => { | ||||
| 										const result = []; | ||||
| 										_.forEach(rows, (rule_row) => { | ||||
| 											result.push(rule_row.id); | ||||
| 										}); | ||||
|  | ||||
| 									// enum should not have less than 1 item | ||||
| 									if (!result.length) { | ||||
| 										result.push(0); | ||||
| 									} | ||||
| 										// enum should not have less than 1 item | ||||
| 										if (!result.length) { | ||||
| 											result.push(0); | ||||
| 										} | ||||
|  | ||||
| 									return result; | ||||
| 								}) | ||||
| 							); | ||||
| 							break; | ||||
| 										return result; | ||||
| 									}), | ||||
| 								); | ||||
| 								break; | ||||
|  | ||||
| 							// DEFAULT: null | ||||
| 						default: | ||||
| 							resolve(null); | ||||
| 							break; | ||||
| 							default: | ||||
| 								resolve(null); | ||||
| 								break; | ||||
| 						} | ||||
| 					} else { | ||||
| 						resolve(object_cache[object_type]); | ||||
| @@ -156,11 +164,10 @@ module.exports = function (token_string) { | ||||
| 			} else { | ||||
| 				resolve(null); | ||||
| 			} | ||||
| 		}) | ||||
| 			.then((objects) => { | ||||
| 				object_cache[object_type] = objects; | ||||
| 				return objects; | ||||
| 			}); | ||||
| 		}).then((objects) => { | ||||
| 			object_cache[object_type] = objects; | ||||
| 			return objects; | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	/** | ||||
| @@ -170,50 +177,48 @@ module.exports = function (token_string) { | ||||
| 	 * @returns {Object} | ||||
| 	 */ | ||||
| 	this.getObjectSchema = (permission_label) => { | ||||
| 		let base_object_type = permission_label.split(':').shift(); | ||||
| 		const base_object_type = permission_label.split(":").shift(); | ||||
|  | ||||
| 		let schema = { | ||||
| 			$id:                  'objects', | ||||
| 			description:          'Actor Properties', | ||||
| 			type:                 'object', | ||||
| 		const schema = { | ||||
| 			$id: "objects", | ||||
| 			description: "Actor Properties", | ||||
| 			type: "object", | ||||
| 			additionalProperties: false, | ||||
| 			properties:           { | ||||
| 			properties: { | ||||
| 				user_id: { | ||||
| 					anyOf: [ | ||||
| 						{ | ||||
| 							type: 'number', | ||||
| 							enum: [Token.get('attrs').id] | ||||
| 						} | ||||
| 					] | ||||
| 							type: "number", | ||||
| 							enum: [Token.get("attrs").id], | ||||
| 						}, | ||||
| 					], | ||||
| 				}, | ||||
| 				scope: { | ||||
| 					type:    'string', | ||||
| 					pattern: '^' + Token.get('scope') + '$' | ||||
| 				} | ||||
| 			} | ||||
| 					type: "string", | ||||
| 					pattern: `^${Token.get("scope")}$`, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}; | ||||
|  | ||||
| 		return this.loadObjects(base_object_type) | ||||
| 			.then((object_result) => { | ||||
| 				if (typeof object_result === 'object' && object_result !== null) { | ||||
| 					schema.properties[base_object_type] = { | ||||
| 						type:    'number', | ||||
| 						enum:    object_result, | ||||
| 						minimum: 1 | ||||
| 					}; | ||||
| 				} else { | ||||
| 					schema.properties[base_object_type] = { | ||||
| 						type:    'number', | ||||
| 						minimum: 1 | ||||
| 					}; | ||||
| 				} | ||||
| 		return this.loadObjects(base_object_type).then((object_result) => { | ||||
| 			if (typeof object_result === "object" && object_result !== null) { | ||||
| 				schema.properties[base_object_type] = { | ||||
| 					type: "number", | ||||
| 					enum: object_result, | ||||
| 					minimum: 1, | ||||
| 				}; | ||||
| 			} else { | ||||
| 				schema.properties[base_object_type] = { | ||||
| 					type: "number", | ||||
| 					minimum: 1, | ||||
| 				}; | ||||
| 			} | ||||
|  | ||||
| 				return schema; | ||||
| 			}); | ||||
| 			return schema; | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	return { | ||||
|  | ||||
| 		token: Token, | ||||
|  | ||||
| 		/** | ||||
| @@ -222,7 +227,7 @@ module.exports = function (token_string) { | ||||
| 		 * @returns {Promise} | ||||
| 		 */ | ||||
| 		load: (allow_internal) => { | ||||
| 			return new Promise(function (resolve/*, reject*/) { | ||||
| 			return new Promise((resolve /*, reject*/) => { | ||||
| 				if (token_string) { | ||||
| 					resolve(Token.load(token_string)); | ||||
| 				} else { | ||||
| @@ -240,68 +245,60 @@ module.exports = function (token_string) { | ||||
| 		 * @param {*}       [data] | ||||
| 		 * @returns {Promise} | ||||
| 		 */ | ||||
| 		can: (permission, data) => { | ||||
| 		can: async (permission, data) => { | ||||
| 			if (allow_internal_access === true) { | ||||
| 				return Promise.resolve(true); | ||||
| 				//return true; | ||||
| 			} else { | ||||
| 				return this.init() | ||||
| 					.then(() => { | ||||
| 						// Initialised, token decoded ok | ||||
| 						return this.getObjectSchema(permission) | ||||
| 							.then((objectSchema) => { | ||||
| 								const data_schema = { | ||||
| 									[permission]: { | ||||
| 										data:                         data, | ||||
| 										scope:                        Token.get('scope'), | ||||
| 										roles:                        user_roles, | ||||
| 										permission_visibility:        permissions.visibility, | ||||
| 										permission_proxy_hosts:       permissions.proxy_hosts, | ||||
| 										permission_redirection_hosts: permissions.redirection_hosts, | ||||
| 										permission_dead_hosts:        permissions.dead_hosts, | ||||
| 										permission_streams:           permissions.streams, | ||||
| 										permission_access_lists:      permissions.access_lists, | ||||
| 										permission_certificates:      permissions.certificates | ||||
| 									} | ||||
| 								}; | ||||
|  | ||||
| 								let permissionSchema = { | ||||
| 									$async:               true, | ||||
| 									$id:                  'permissions', | ||||
| 									type:                 'object', | ||||
| 									additionalProperties: false, | ||||
| 									properties:           {} | ||||
| 								}; | ||||
|  | ||||
| 								permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json'); | ||||
|  | ||||
| 								const ajv = new Ajv({ | ||||
| 									verbose:      true, | ||||
| 									allErrors:    true, | ||||
| 									breakOnError: true, | ||||
| 									coerceTypes:  true, | ||||
| 									schemas:      [ | ||||
| 										roleSchema, | ||||
| 										permsSchema, | ||||
| 										objectSchema, | ||||
| 										permissionSchema | ||||
| 									] | ||||
| 								}); | ||||
|  | ||||
| 								return ajv.validate('permissions', data_schema) | ||||
| 									.then(() => { | ||||
| 										return data_schema[permission]; | ||||
| 									}); | ||||
| 							}); | ||||
| 					}) | ||||
| 					.catch((err) => { | ||||
| 						err.permission      = permission; | ||||
| 						err.permission_data = data; | ||||
| 						logger.error(permission, data, err.message); | ||||
|  | ||||
| 						throw new error.PermissionError('Permission Denied', err); | ||||
| 					}); | ||||
| 				return true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 			try { | ||||
| 				await this.init(); | ||||
| 				const objectSchema = await this.getObjectSchema(permission); | ||||
|  | ||||
| 				const dataSchema = { | ||||
| 					[permission]: { | ||||
| 						data: data, | ||||
| 						scope: Token.get("scope"), | ||||
| 						roles: user_roles, | ||||
| 						permission_visibility: permissions.visibility, | ||||
| 						permission_proxy_hosts: permissions.proxy_hosts, | ||||
| 						permission_redirection_hosts: permissions.redirection_hosts, | ||||
| 						permission_dead_hosts: permissions.dead_hosts, | ||||
| 						permission_streams: permissions.streams, | ||||
| 						permission_access_lists: permissions.access_lists, | ||||
| 						permission_certificates: permissions.certificates, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| 				const permissionSchema = { | ||||
| 					$async: true, | ||||
| 					$id: "permissions", | ||||
| 					type: "object", | ||||
| 					additionalProperties: false, | ||||
| 					properties: {}, | ||||
| 				}; | ||||
|  | ||||
| 				const rawData = fs.readFileSync( | ||||
| 					`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, | ||||
| 					{ encoding: "utf8" }, | ||||
| 				); | ||||
| 				permissionSchema.properties[permission] = JSON.parse(rawData); | ||||
|  | ||||
| 				const ajv = new Ajv({ | ||||
| 					verbose: true, | ||||
| 					allErrors: true, | ||||
| 					breakOnError: true, | ||||
| 					coerceTypes: true, | ||||
| 					schemas: [roleSchema, permsSchema, objectSchema, permissionSchema], | ||||
| 				}); | ||||
|  | ||||
| 				const valid = ajv.validate("permissions", dataSchema); | ||||
| 				return valid && dataSchema[permission]; | ||||
| 			} catch (err) { | ||||
| 				err.permission = permission; | ||||
| 				err.permission_data = data; | ||||
| 				logger.error(permission, data, err.message); | ||||
| 				throw errs.PermissionError("Permission Denied", err); | ||||
| 			} | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user