0a659428dd
Same swap as sso-manager-node/jump-host: vendored app.util.actionMessage/ actionConfirm replaced by @simpleworkjs/frontend's app.messages.action/ confirm (real HTML-escaping, toast fallback); vendored val.js replaced by the package's app.validate.js. proxy's host/target/hostname validation rules (mirrored from the backend's utils/hostname_validate.js — wildcard DNS patterns, not something other apps need) move to public/js/app.js, registered via $.validateSettings, since they're proxy-specific and don't belong in the shared package's generic rule set (eq/user/password/ip). app.api/app.auth/app.pubsub/app.socket in app-base.js are untouched, same reasoning as sso-manager-node's PR. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
163 lines
4.3 KiB
JavaScript
Executable File
163 lines
4.3 KiB
JavaScript
Executable File
app.host = (function(app){
|
|
function list(callack){
|
|
app.api.get('host/?detail=true', function(error, data){
|
|
callack(error, data.hosts)
|
|
});
|
|
}
|
|
|
|
function get(host, callack){
|
|
app.api.get('host/' + host, function(error, data){
|
|
callack(error, data)
|
|
});
|
|
}
|
|
|
|
function add(args, callack){
|
|
app.api.post('host/', args, function(error, data){
|
|
callack(error, data);
|
|
});
|
|
}
|
|
|
|
function edit(args, callack){
|
|
app.api.put('host/' + args.edit_host, args, function(error, data){
|
|
callack(error, data);
|
|
});
|
|
}
|
|
|
|
function remove(args, callack){
|
|
app.api.delete('host/'+ args.host, function(error, data){
|
|
callack(error, data);
|
|
});
|
|
}
|
|
|
|
function getCert(args, callack){
|
|
app.api.get('cert/'+args.host, function(error, data){
|
|
callack(error, data);
|
|
});
|
|
}
|
|
|
|
function clearCache(callack){
|
|
app.api.delete('host/cache', function(error, data){
|
|
callack(error, data);
|
|
});
|
|
}
|
|
|
|
return {
|
|
getCert,
|
|
list: list,
|
|
get: get,
|
|
add: add,
|
|
edit: edit,
|
|
remove: remove,
|
|
clearCache: clearCache,
|
|
}
|
|
})(app);
|
|
|
|
app.apiToken = (function(app){
|
|
function list(callback){
|
|
app.api.get('api-token/', function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
function get(id, callback){
|
|
app.api.get('api-token/' + id, function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
function add(args, callback){
|
|
app.api.post('api-token/', args, function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
function update(args, callback){
|
|
app.api.put('api-token/' + args.id, args, function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
function remove(args, callback){
|
|
app.api.delete('api-token/' + args.id, function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
function rotate(args, callback){
|
|
app.api.post('api-token/' + args.id + '/rotate', {}, function(error, data){
|
|
callback(error, data);
|
|
});
|
|
}
|
|
|
|
return {list, get, add, update, remove, rotate};
|
|
})(app);
|
|
|
|
// Host / target validation, mirrored from the backend (utils/hostname_validate.js):
|
|
// a bare hostname or IPv4 address, no protocol / "/" / ":" / whitespace. The
|
|
// incoming host may be a wildcard ("*.example.com"); the target may not.
|
|
// Proxy-specific, so it's registered here (via @simpleworkjs/frontend's
|
|
// $.validateSettings) rather than in the shared package's generic rule set.
|
|
(function(){
|
|
var LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i;
|
|
// Either one bare label (Docker service names, /etc/hosts entries) or a
|
|
// dotted hostname with an alphabetic TLD.
|
|
var HOSTNAME = /^(?=.{1,253}$)(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)$/i;
|
|
var FORBIDDEN = /[\s/:]/;
|
|
|
|
function isIPv4( value ) {
|
|
var parts = value.split( '.' );
|
|
if ( parts.length !== 4 ) return false;
|
|
return parts.every( function( p ) {
|
|
return /^(0|[1-9]\d{0,2})$/.test( p ) && Number( p ) <= 255;
|
|
});
|
|
}
|
|
|
|
// Incoming-host pattern: labels may be normal, "*" (one fragment), or "**"
|
|
// (any number of fragments, incl. a bare "**" global catch-all).
|
|
function isHostPattern( value ) {
|
|
if ( value.length > 253 ) return false;
|
|
return value.split( '.' ).every( function( l ) {
|
|
return l === '*' || l === '**' || LABEL.test( l );
|
|
});
|
|
}
|
|
|
|
function forbidden( value ) {
|
|
return FORBIDDEN.test( value ) || value.includes( '://' );
|
|
}
|
|
|
|
// Incoming host: IPv4 or a wildcard host pattern.
|
|
function checkHost( value ) {
|
|
if ( typeof value !== 'string' || value.length === 0 ) return "Required";
|
|
if ( forbidden( value ) ) return 'No protocol, "/", or ":"';
|
|
if ( isIPv4( value ) || isHostPattern( value ) ) return;
|
|
return "Enter a valid host or wildcard (*, **)";
|
|
}
|
|
|
|
// Downstream target: IPv4 or a strict hostname, no wildcard.
|
|
function checkTarget( value ) {
|
|
if ( typeof value !== 'string' || value.length === 0 ) return "Required";
|
|
if ( forbidden( value ) ) return 'No protocol, "/", or ":"';
|
|
if ( isIPv4( value ) || HOSTNAME.test( value ) ) return;
|
|
return "Enter a valid hostname or IP";
|
|
}
|
|
|
|
$.validateSettings({
|
|
rule:{
|
|
// Incoming host name — hostname, IPv4, or wildcard pattern (*, **).
|
|
host: function( value ) {
|
|
return checkHost( value );
|
|
},
|
|
|
|
// Downstream target — hostname or IPv4, no wildcard.
|
|
target: function( value ) {
|
|
return checkTarget( value );
|
|
},
|
|
|
|
// Back-compat alias (no wildcard).
|
|
hostname: function( value ) {
|
|
return checkTarget( value );
|
|
},
|
|
}
|
|
});
|
|
})();
|