| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 | 
							- /**
 
-  * Some Notes: This is a friggin complicated piece of code.
 
-  *
 
-  * "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 validator      = require('ajv');
 
- 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');
 
- module.exports = function (token_string) {
 
- 	let Token                 = new TokenModel();
 
- 	let token_data            = null;
 
- 	let initialised           = false;
 
- 	let object_cache          = {};
 
- 	let allow_internal_access = false;
 
- 	let user_roles            = [];
 
- 	let permissions           = {};
 
- 	/**
 
- 	 * Loads the Token object from the token string
 
- 	 *
 
- 	 * @returns {Promise}
 
- 	 */
 
- 	this.init = () => {
 
- 		return new Promise((resolve, reject) => {
 
- 			if (initialised) {
 
- 				resolve();
 
- 			} else if (!token_string) {
 
- 				reject(new error.PermissionError('Permission Denied'));
 
- 			} 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)
 
- 								.allowEager('[permissions]')
 
- 								.eager('[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 error.AuthError('Invalid token scope for User');
 
- 										} else {
 
- 											initialised = true;
 
- 											user_roles  = user.roles;
 
- 											permissions = user.permissions;
 
- 										}
 
- 									} else {
 
- 										throw new error.AuthError('User cannot be loaded for Token');
 
- 									}
 
- 								});
 
- 						} else {
 
- 							initialised = true;
 
- 						}
 
- 					}));
 
- 			}
 
- 		});
 
- 	};
 
- 	/**
 
- 	 * Fetches the object ids from the database, only once per object type, for this token.
 
- 	 * This only applies to USER token scopes, as all other tokens are not really bound
 
- 	 * by object scopes
 
- 	 *
 
- 	 * @param   {String} object_type
 
- 	 * @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 error.AuthError('User Token supplied without a User ID'));
 
- 				} else {
 
- 					let token_user_id = token_data.attrs.id ? token_data.attrs.id : 0;
 
- 					let query;
 
- 					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;
 
- 							// Proxy Hosts
 
- 						case 'proxy_hosts':
 
- 							query = proxyHostModel
 
- 								.query()
 
- 								.select('id')
 
- 								.andWhere('is_deleted', 0);
 
- 							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);
 
- 									});
 
- 									// enum should not have less than 1 item
 
- 									if (!result.length) {
 
- 										result.push(0);
 
- 									}
 
- 									return result;
 
- 								})
 
- 							);
 
- 							break;
 
- 							// DEFAULT: null
 
- 						default:
 
- 							resolve(null);
 
- 							break;
 
- 						}
 
- 					} else {
 
- 						resolve(object_cache[object_type]);
 
- 					}
 
- 				}
 
- 			} else {
 
- 				resolve(null);
 
- 			}
 
- 		})
 
- 			.then((objects) => {
 
- 				object_cache[object_type] = 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
 
- 	 * @returns {Object}
 
- 	 */
 
- 	this.getObjectSchema = (permission_label) => {
 
- 		let base_object_type = permission_label.split(':').shift();
 
- 		let schema = {
 
- 			$id:                  'objects',
 
- 			$schema:              'http://json-schema.org/draft-07/schema#',
 
- 			description:          'Actor Properties',
 
- 			type:                 'object',
 
- 			additionalProperties: false,
 
- 			properties:           {
 
- 				user_id: {
 
- 					anyOf: [
 
- 						{
 
- 							type: 'number',
 
- 							enum: [Token.get('attrs').id]
 
- 						}
 
- 					]
 
- 				},
 
- 				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 schema;
 
- 			});
 
- 	};
 
- 	return {
 
- 		token: Token,
 
- 		/**
 
- 		 *
 
- 		 * @param   {Boolean}  [allow_internal]
 
- 		 * @returns {Promise}
 
- 		 */
 
- 		load: (allow_internal) => {
 
- 			return new Promise(function (resolve/*, reject*/) {
 
- 				if (token_string) {
 
- 					resolve(Token.load(token_string));
 
- 				} else {
 
- 					allow_internal_access = allow_internal;
 
- 					resolve(allow_internal_access || null);
 
- 				}
 
- 			});
 
- 		},
 
- 		reloadObjects: this.loadObjects,
 
- 		/**
 
- 		 *
 
- 		 * @param {String}  permission
 
- 		 * @param {*}       [data]
 
- 		 * @returns {Promise}
 
- 		 */
 
- 		can: (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) => {
 
- 								let 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 = {
 
- 									$schema:              'http://json-schema.org/draft-07/schema#',
 
- 									$async:               true,
 
- 									$id:                  'permissions',
 
- 									additionalProperties: false,
 
- 									properties:           {}
 
- 								};
 
- 								permissionSchema.properties[permission] = require('./access/' + permission.replace(/:/gim, '-') + '.json');
 
- 								// logger.info('objectSchema', JSON.stringify(objectSchema, null, 2));
 
- 								// logger.info('permissionSchema', JSON.stringify(permissionSchema, null, 2));
 
- 								// logger.info('data_schema', JSON.stringify(data_schema, null, 2));
 
- 								let ajv = validator({
 
- 									verbose:      true,
 
- 									allErrors:    true,
 
- 									format:       'full',
 
- 									missingRefs:  'fail',
 
- 									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);
 
- 					});
 
- 			}
 
- 		}
 
- 	};
 
- };
 
 
  |