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 
			
		
		
		
	Introducing the Setup Wizard for creating the first user
- no longer setup a default - still able to do that with env vars however
This commit is contained in:
		| @@ -22,13 +22,13 @@ import errs from "./error.js"; | ||||
| const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = dirname(__filename); | ||||
|  | ||||
| export default function (token_string) { | ||||
| export default function (tokenString) { | ||||
| 	const Token = TokenModel(); | ||||
| 	let token_data = null; | ||||
| 	let tokenData = null; | ||||
| 	let initialised = false; | ||||
| 	const object_cache = {}; | ||||
| 	let allow_internal_access = false; | ||||
| 	let user_roles = []; | ||||
| 	const objectCache = {}; | ||||
| 	let allowInternalAccess = false; | ||||
| 	let userRoles = []; | ||||
| 	let permissions = {}; | ||||
|  | ||||
| 	/** | ||||
| @@ -36,65 +36,58 @@ export default function (token_string) { | ||||
| 	 * | ||||
| 	 * @returns {Promise} | ||||
| 	 */ | ||||
| 	this.init = () => { | ||||
| 		return new Promise((resolve, reject) => { | ||||
| 			if (initialised) { | ||||
| 				resolve(); | ||||
| 			} else if (!token_string) { | ||||
| 				reject(new errs.PermissionError("Permission Denied")); | ||||
| 	this.init = async () => { | ||||
| 		if (initialised) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		if (!tokenString) { | ||||
| 			throw new errs.PermissionError("Permission Denied"); | ||||
| 		} | ||||
|  | ||||
| 		tokenData = await Token.load(tokenString); | ||||
|  | ||||
| 		// At this point we need to load the user from the DB and make sure they: | ||||
| 		// - exist (and not soft deleted) | ||||
| 		// - 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 ( | ||||
| 			tokenData.attrs.id || | ||||
| 			(typeof tokenData.scope !== "undefined" && _.indexOf(tokenData.scope, "user") !== -1) | ||||
| 		) { | ||||
| 			// Has token user id or token user scope | ||||
| 			const user = await userModel | ||||
| 				.query() | ||||
| 				.where("id", tokenData.attrs.id) | ||||
| 				.andWhere("is_deleted", 0) | ||||
| 				.andWhere("is_disabled", 0) | ||||
| 				.allowGraph("[permissions]") | ||||
| 				.withGraphFetched("[permissions]") | ||||
| 				.first(); | ||||
|  | ||||
| 			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"); | ||||
|  | ||||
| 				let ok = true; | ||||
| 				_.forEach(tokenData.scope, (scope_item) => { | ||||
| 					if (_.indexOf(user.roles, scope_item) === -1) { | ||||
| 						ok = false; | ||||
| 					} | ||||
| 				}); | ||||
|  | ||||
| 				if (!ok) { | ||||
| 					throw new errs.AuthError("Invalid token scope for User"); | ||||
| 				} | ||||
| 				initialised = true; | ||||
| 				userRoles = user.roles; | ||||
| 				permissions = user.permissions; | ||||
| 			} else { | ||||
| 				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: | ||||
| 						// - exist (and not soft deleted) | ||||
| 						// - 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) | ||||
| 						) { | ||||
| 							// 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]") | ||||
| 								.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"); | ||||
|  | ||||
| 										let is_ok = true; | ||||
| 										_.forEach(token_data.scope, (scope_item) => { | ||||
| 											if (_.indexOf(user.roles, scope_item) === -1) { | ||||
| 												is_ok = false; | ||||
| 											} | ||||
| 										}); | ||||
|  | ||||
| 										if (!is_ok) { | ||||
| 											throw new errs.AuthError("Invalid token scope for User"); | ||||
| 										} | ||||
| 										initialised = true; | ||||
| 										user_roles = user.roles; | ||||
| 										permissions = user.permissions; | ||||
| 									} else { | ||||
| 										throw new errs.AuthError("User cannot be loaded for Token"); | ||||
| 									} | ||||
| 								}); | ||||
| 						} | ||||
| 						initialised = true; | ||||
| 					}), | ||||
| 				); | ||||
| 				throw new errs.AuthError("User cannot be loaded for Token"); | ||||
| 			} | ||||
| 		}); | ||||
| 		} | ||||
| 		initialised = true; | ||||
| 	}; | ||||
|  | ||||
| 	/** | ||||
| @@ -102,82 +95,64 @@ export default function (token_string) { | ||||
| 	 * This only applies to USER token scopes, as all other tokens are not really bound | ||||
| 	 * by object scopes | ||||
| 	 * | ||||
| 	 * @param   {String} object_type | ||||
| 	 * @param   {String} objectType | ||||
| 	 * @returns {Promise} | ||||
| 	 */ | ||||
| 	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 errs.AuthError("User Token supplied without a User ID")); | ||||
| 				} else { | ||||
| 					const token_user_id = token_data.attrs.id ? token_data.attrs.id : 0; | ||||
| 					let query; | ||||
| 	this.loadObjects = async (objectType) => { | ||||
| 		let objects = null; | ||||
|  | ||||
| 					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; | ||||
| 		if (Token.hasScope("user")) { | ||||
| 			if (typeof tokenData.attrs.id === "undefined" || !tokenData.attrs.id) { | ||||
| 				throw new errs.AuthError("User Token supplied without a User ID"); | ||||
| 			} | ||||
|  | ||||
| 							// Proxy Hosts | ||||
| 							case "proxy_hosts": | ||||
| 								query = proxyHostModel | ||||
| 									.query() | ||||
| 									.select("id") | ||||
| 									.andWhere("is_deleted", 0); | ||||
| 			const tokenUserId = tokenData.attrs.id ? tokenData.attrs.id : 0; | ||||
| 			let query; | ||||
|  | ||||
| 								if (permissions.visibility === "user") { | ||||
| 									query.andWhere("owner_user_id", token_user_id); | ||||
| 								} | ||||
| 			if (typeof objectCache[objectType] !== "undefined") { | ||||
| 				objects = objectCache[objectType]; | ||||
| 			} else { | ||||
| 				switch (objectType) { | ||||
| 					// USERS - should only return yourself | ||||
| 					case "users": | ||||
| 						objects = tokenUserId ? [tokenUserId] : []; | ||||
| 						break; | ||||
|  | ||||
| 								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); | ||||
| 										} | ||||
|  | ||||
| 										return result; | ||||
| 									}), | ||||
| 								); | ||||
| 								break; | ||||
|  | ||||
| 							// DEFAULT: null | ||||
| 							default: | ||||
| 								resolve(null); | ||||
| 								break; | ||||
| 					// Proxy Hosts | ||||
| 					case "proxy_hosts": { | ||||
| 						query = proxyHostModel.query().select("id").andWhere("is_deleted", 0); | ||||
| 						if (permissions.visibility === "user") { | ||||
| 							query.andWhere("owner_user_id", tokenUserId); | ||||
| 						} | ||||
| 					} else { | ||||
| 						resolve(object_cache[object_type]); | ||||
|  | ||||
| 						const rows = await query(); | ||||
| 						objects = []; | ||||
| 						_.forEach(rows, (ruleRow) => { | ||||
| 							result.push(ruleRow.id); | ||||
| 						}); | ||||
|  | ||||
| 						// enum should not have less than 1 item | ||||
| 						if (!objects.length) { | ||||
| 							objects.push(0); | ||||
| 						} | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				resolve(null); | ||||
| 				objectCache[objectType] = objects; | ||||
| 			} | ||||
| 		}).then((objects) => { | ||||
| 			object_cache[object_type] = objects; | ||||
| 			return objects; | ||||
| 		}); | ||||
| 		} | ||||
|  | ||||
| 		return objects; | ||||
| 	}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a schema object on the fly with the IDs and other values required to be checked against the permissionSchema | ||||
| 	 * | ||||
| 	 * @param   {String} permission_label | ||||
| 	 * @param   {String} permissionLabel | ||||
| 	 * @returns {Object} | ||||
| 	 */ | ||||
| 	this.getObjectSchema = (permission_label) => { | ||||
| 		const base_object_type = permission_label.split(":").shift(); | ||||
| 	this.getObjectSchema = async (permissionLabel) => { | ||||
| 		const baseObjectType = permissionLabel.split(":").shift(); | ||||
|  | ||||
| 		const schema = { | ||||
| 			$id: "objects", | ||||
| @@ -200,41 +175,39 @@ export default function (token_string) { | ||||
| 			}, | ||||
| 		}; | ||||
|  | ||||
| 		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, | ||||
| 				}; | ||||
| 			} | ||||
| 		const result = await this.loadObjects(baseObjectType); | ||||
| 		if (typeof result === "object" && result !== null) { | ||||
| 			schema.properties[baseObjectType] = { | ||||
| 				type: "number", | ||||
| 				enum: result, | ||||
| 				minimum: 1, | ||||
| 			}; | ||||
| 		} else { | ||||
| 			schema.properties[baseObjectType] = { | ||||
| 				type: "number", | ||||
| 				minimum: 1, | ||||
| 			}; | ||||
| 		} | ||||
|  | ||||
| 			return schema; | ||||
| 		}); | ||||
| 		return schema; | ||||
| 	}; | ||||
|  | ||||
| 	// here: | ||||
|  | ||||
| 	return { | ||||
| 		token: Token, | ||||
|  | ||||
| 		/** | ||||
| 		 * | ||||
| 		 * @param   {Boolean}  [allow_internal] | ||||
| 		 * @param   {Boolean}  [allowInternal] | ||||
| 		 * @returns {Promise} | ||||
| 		 */ | ||||
| 		load: (allow_internal) => { | ||||
| 			return new Promise((resolve /*, reject*/) => { | ||||
| 				if (token_string) { | ||||
| 					resolve(Token.load(token_string)); | ||||
| 				} else { | ||||
| 					allow_internal_access = allow_internal; | ||||
| 					resolve(allow_internal_access || null); | ||||
| 				} | ||||
| 			}); | ||||
| 		load: async (allowInternal) => { | ||||
| 			if (tokenString) { | ||||
| 				return await Token.load(tokenString); | ||||
| 			} | ||||
| 			allowInternalAccess = allowInternal; | ||||
| 			return allowInternal || null; | ||||
| 		}, | ||||
|  | ||||
| 		reloadObjects: this.loadObjects, | ||||
| @@ -246,7 +219,7 @@ export default function (token_string) { | ||||
| 		 * @returns {Promise} | ||||
| 		 */ | ||||
| 		can: async (permission, data) => { | ||||
| 			if (allow_internal_access === true) { | ||||
| 			if (allowInternalAccess === true) { | ||||
| 				return true; | ||||
| 			} | ||||
|  | ||||
| @@ -258,7 +231,7 @@ export default function (token_string) { | ||||
| 					[permission]: { | ||||
| 						data: data, | ||||
| 						scope: Token.get("scope"), | ||||
| 						roles: user_roles, | ||||
| 						roles: userRoles, | ||||
| 						permission_visibility: permissions.visibility, | ||||
| 						permission_proxy_hosts: permissions.proxy_hosts, | ||||
| 						permission_redirection_hosts: permissions.redirection_hosts, | ||||
| @@ -277,10 +250,9 @@ export default function (token_string) { | ||||
| 					properties: {}, | ||||
| 				}; | ||||
|  | ||||
| 				const rawData = fs.readFileSync( | ||||
| 					`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, | ||||
| 					{ encoding: "utf8" }, | ||||
| 				); | ||||
| 				const rawData = fs.readFileSync(`${__dirname}/access/${permission.replace(/:/gim, "-")}.json`, { | ||||
| 					encoding: "utf8", | ||||
| 				}); | ||||
| 				permissionSchema.properties[permission] = JSON.parse(rawData); | ||||
|  | ||||
| 				const ajv = new Ajv({ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user