mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-05-02 20:12:29 +00:00
* add `oidc-config` setting allowing an admin user to configure parameters * modify login page to show another button when oidc is configured * add dependency `openid-client` `v5.4.0` * add backend route to process "OAuth2 Authorization Code" flow initialisation * add backend route to process callback of above flow * sign in the authenticated user with internal jwt token if internal user with email matching the one retrieved from oauth claims exists Note: Only Open ID Connect Discovery is supported which most modern Identity Providers offer. Tested with Authentik 2023.2.2 and Keycloak 18.0.2
760 lines
22 KiB
JavaScript
760 lines
22 KiB
JavaScript
const $ = require('jquery');
|
|
const _ = require('underscore');
|
|
const Tokens = require('./tokens');
|
|
|
|
/**
|
|
* @param {String} message
|
|
* @param {*} debug
|
|
* @param {Number} code
|
|
* @constructor
|
|
*/
|
|
const ApiError = function (message, debug, code) {
|
|
let temp = Error.call(this, message);
|
|
temp.name = this.name = 'ApiError';
|
|
this.stack = temp.stack;
|
|
this.message = temp.message;
|
|
this.debug = debug;
|
|
this.code = code;
|
|
};
|
|
|
|
ApiError.prototype = Object.create(Error.prototype, {
|
|
constructor: {
|
|
value: ApiError,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
|
|
/**
|
|
*
|
|
* @param {String} verb
|
|
* @param {String} path
|
|
* @param {Object} [data]
|
|
* @param {Object} [options]
|
|
* @returns {Promise}
|
|
*/
|
|
function fetch(verb, path, data, options) {
|
|
options = options || {};
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
let api_url = '/api/';
|
|
let url = api_url + path;
|
|
let token = Tokens.getTopToken();
|
|
|
|
if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') {
|
|
data = JSON.stringify(data);
|
|
}
|
|
|
|
$.ajax({
|
|
url: url,
|
|
data: typeof data === 'object' ? JSON.stringify(data) : data,
|
|
type: verb,
|
|
dataType: 'json',
|
|
contentType: options.contentType || 'application/json; charset=UTF-8',
|
|
processData: options.processData || true,
|
|
crossDomain: true,
|
|
timeout: options.timeout ? options.timeout : 180000,
|
|
xhrFields: {
|
|
withCredentials: true
|
|
},
|
|
|
|
beforeSend: function (xhr) {
|
|
// allow unauthenticated access to OIDC configuration
|
|
if (path === "settings/oidc-config") return;
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
|
},
|
|
|
|
success: function (data, textStatus, response) {
|
|
let total = response.getResponseHeader('X-Dataset-Total');
|
|
if (total !== null) {
|
|
resolve({
|
|
data: data,
|
|
pagination: {
|
|
total: parseInt(total, 10),
|
|
offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10),
|
|
limit: parseInt(response.getResponseHeader('X-Dataset-Limit'), 10)
|
|
}
|
|
});
|
|
} else {
|
|
resolve(response);
|
|
}
|
|
},
|
|
|
|
error: function (xhr, status, error_thrown) {
|
|
let code = 400;
|
|
|
|
if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
|
|
error_thrown = xhr.responseJSON.error.message;
|
|
code = xhr.responseJSON.error.code || 500;
|
|
}
|
|
|
|
reject(new ApiError(error_thrown, xhr.responseText, code));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {Array} expand
|
|
* @returns {String}
|
|
*/
|
|
function makeExpansionString(expand) {
|
|
let items = [];
|
|
_.forEach(expand, function (exp) {
|
|
items.push(encodeURIComponent(exp));
|
|
});
|
|
|
|
return items.join(',');
|
|
}
|
|
|
|
/**
|
|
* @param {String} path
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
function getAllObjects(path, expand, query) {
|
|
let params = [];
|
|
|
|
if (typeof expand === 'object' && expand !== null && expand.length) {
|
|
params.push('expand=' + makeExpansionString(expand));
|
|
}
|
|
|
|
if (typeof query === 'string') {
|
|
params.push('query=' + query);
|
|
}
|
|
|
|
return fetch('get', path + (params.length ? '?' + params.join('&') : ''));
|
|
}
|
|
|
|
function FileUpload(path, fd) {
|
|
return new Promise((resolve, reject) => {
|
|
let xhr = new XMLHttpRequest();
|
|
let token = Tokens.getTopToken();
|
|
|
|
xhr.open('POST', '/api/' + path);
|
|
xhr.overrideMimeType('text/plain');
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
|
xhr.send(fd);
|
|
|
|
xhr.onreadystatechange = function () {
|
|
if (this.readyState === XMLHttpRequest.DONE) {
|
|
if (xhr.status !== 200 && xhr.status !== 201) {
|
|
try {
|
|
reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message));
|
|
} catch (err) {
|
|
reject(new Error('Upload failed: ' + xhr.status));
|
|
}
|
|
} else {
|
|
resolve(xhr.responseText);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
//ref : https://codepen.io/chrisdpratt/pen/RKxJNo
|
|
function DownloadFile(verb, path, filename) {
|
|
return new Promise(function (resolve, reject) {
|
|
let api_url = '/api/';
|
|
let url = api_url + path;
|
|
let token = Tokens.getTopToken();
|
|
|
|
$.ajax({
|
|
url: url,
|
|
type: verb,
|
|
crossDomain: true,
|
|
xhrFields: {
|
|
withCredentials: true,
|
|
responseType: 'blob'
|
|
},
|
|
|
|
beforeSend: function (xhr) {
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
|
},
|
|
|
|
success: function (data) {
|
|
var a = document.createElement('a');
|
|
var url = window.URL.createObjectURL(data);
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.append(a);
|
|
a.click();
|
|
a.remove();
|
|
window.URL.revokeObjectURL(url);
|
|
},
|
|
|
|
error: function (xhr, status, error_thrown) {
|
|
let code = 400;
|
|
|
|
if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
|
|
error_thrown = xhr.responseJSON.error.message;
|
|
code = xhr.responseJSON.error.code || 500;
|
|
}
|
|
|
|
reject(new ApiError(error_thrown, xhr.responseText, code));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
status: function () {
|
|
return fetch('get', '');
|
|
},
|
|
|
|
Tokens: {
|
|
|
|
/**
|
|
* @param {String} identity
|
|
* @param {String} secret
|
|
* @param {Boolean} [wipe] Will wipe the stack before adding to it again if login was successful
|
|
* @returns {Promise}
|
|
*/
|
|
login: function (identity, secret, wipe) {
|
|
return fetch('post', 'tokens', {identity: identity, secret: secret})
|
|
.then(response => {
|
|
if (response.token) {
|
|
if (wipe) {
|
|
Tokens.clearTokens();
|
|
}
|
|
|
|
// Set storage token
|
|
Tokens.addToken(response.token);
|
|
return response.token;
|
|
} else {
|
|
Tokens.clearTokens();
|
|
throw(new Error('No token returned'));
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @returns {Promise}
|
|
*/
|
|
refresh: function () {
|
|
return fetch('get', 'tokens')
|
|
.then(response => {
|
|
if (response.token) {
|
|
Tokens.setCurrentToken(response.token);
|
|
return response.token;
|
|
} else {
|
|
Tokens.clearTokens();
|
|
throw(new Error('No token returned'));
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
Users: {
|
|
|
|
/**
|
|
* @param {Number|String} user_id
|
|
* @param {Array} [expand]
|
|
* @returns {Promise}
|
|
*/
|
|
getById: function (user_id, expand) {
|
|
return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : ''));
|
|
},
|
|
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('users', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @returns {Promise}
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'users', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'users/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'users/' + id);
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {Number} id
|
|
* @param {Object} auth
|
|
* @returns {Promise}
|
|
*/
|
|
setPassword: function (id, auth) {
|
|
return fetch('put', 'users/' + id + '/auth', auth);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
loginAs: function (id) {
|
|
return fetch('post', 'users/' + id + '/login');
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {Number} id
|
|
* @param {Object} perms
|
|
* @returns {Promise}
|
|
*/
|
|
setPermissions: function (id, perms) {
|
|
return fetch('put', 'users/' + id + '/permissions', perms);
|
|
}
|
|
},
|
|
|
|
Nginx: {
|
|
|
|
ProxyHosts: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/proxy-hosts', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'nginx/proxy-hosts', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/proxy-hosts/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/proxy-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
get: function (id) {
|
|
return fetch('get', 'nginx/proxy-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
enable: function (id) {
|
|
return fetch('post', 'nginx/proxy-hosts/' + id + '/enable');
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
disable: function (id) {
|
|
return fetch('post', 'nginx/proxy-hosts/' + id + '/disable');
|
|
}
|
|
},
|
|
|
|
RedirectionHosts: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/redirection-hosts', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'nginx/redirection-hosts', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/redirection-hosts/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/redirection-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
get: function (id) {
|
|
return fetch('get', 'nginx/redirection-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @param {FormData} form_data
|
|
* @params {Promise}
|
|
*/
|
|
setCerts: function (id, form_data) {
|
|
return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
enable: function (id) {
|
|
return fetch('post', 'nginx/redirection-hosts/' + id + '/enable');
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
disable: function (id) {
|
|
return fetch('post', 'nginx/redirection-hosts/' + id + '/disable');
|
|
}
|
|
},
|
|
|
|
Streams: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/streams', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'nginx/streams', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/streams/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/streams/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
get: function (id) {
|
|
return fetch('get', 'nginx/streams/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
enable: function (id) {
|
|
return fetch('post', 'nginx/streams/' + id + '/enable');
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
disable: function (id) {
|
|
return fetch('post', 'nginx/streams/' + id + '/disable');
|
|
}
|
|
},
|
|
|
|
DeadHosts: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/dead-hosts', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'nginx/dead-hosts', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/dead-hosts/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/dead-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
get: function (id) {
|
|
return fetch('get', 'nginx/dead-hosts/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @param {FormData} form_data
|
|
* @params {Promise}
|
|
*/
|
|
setCerts: function (id, form_data) {
|
|
return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
enable: function (id) {
|
|
return fetch('post', 'nginx/dead-hosts/' + id + '/enable');
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
disable: function (id) {
|
|
return fetch('post', 'nginx/dead-hosts/' + id + '/disable');
|
|
}
|
|
},
|
|
|
|
AccessLists: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/access-lists', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
return fetch('post', 'nginx/access-lists', data);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/access-lists/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/access-lists/' + id);
|
|
}
|
|
},
|
|
|
|
Certificates: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('nginx/certificates', expand, query);
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
*/
|
|
create: function (data) {
|
|
|
|
const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0);
|
|
return fetch('post', 'nginx/certificates', data, {timeout});
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'nginx/certificates/' + id, data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
delete: function (id) {
|
|
return fetch('delete', 'nginx/certificates/' + id);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @param {FormData} form_data
|
|
* @params {Promise}
|
|
*/
|
|
upload: function (id, form_data) {
|
|
return FileUpload('nginx/certificates/' + id + '/upload', form_data);
|
|
},
|
|
|
|
/**
|
|
* @param {FormData} form_data
|
|
* @params {Promise}
|
|
*/
|
|
validate: function (form_data) {
|
|
return FileUpload('nginx/certificates/validate', form_data);
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
renew: function (id, timeout = 180000) {
|
|
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
testHttpChallenge: function (domains) {
|
|
return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({
|
|
domains: JSON.stringify(domains),
|
|
}));
|
|
},
|
|
|
|
/**
|
|
* @param {Number} id
|
|
* @returns {Promise}
|
|
*/
|
|
download: function (id) {
|
|
return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip")
|
|
}
|
|
}
|
|
},
|
|
|
|
AuditLog: {
|
|
/**
|
|
* @param {Array} [expand]
|
|
* @param {String} [query]
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function (expand, query) {
|
|
return getAllObjects('audit-log', expand, query);
|
|
}
|
|
},
|
|
|
|
Reports: {
|
|
|
|
/**
|
|
* @returns {Promise}
|
|
*/
|
|
getHostStats: function () {
|
|
return fetch('get', 'reports/hosts');
|
|
}
|
|
},
|
|
|
|
Settings: {
|
|
|
|
/**
|
|
* @param {String} setting_id
|
|
* @returns {Promise}
|
|
*/
|
|
getById: function (setting_id) {
|
|
return fetch('get', 'settings/' + setting_id);
|
|
},
|
|
|
|
/**
|
|
* @returns {Promise}
|
|
*/
|
|
getAll: function () {
|
|
return getAllObjects('settings');
|
|
},
|
|
|
|
/**
|
|
* @param {Object} data
|
|
* @param {Number} data.id
|
|
* @returns {Promise}
|
|
*/
|
|
update: function (data) {
|
|
let id = data.id;
|
|
delete data.id;
|
|
return fetch('put', 'settings/' + id, data);
|
|
}
|
|
}
|
|
};
|