mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-09-14 19:02:35 +00: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