mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-08-31 06:30:48 +02:00
Merge branch 'fix-change-access-directive' into develop
This commit is contained in:
@@ -2961,6 +2961,31 @@ export class API {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ipAllowList is the API for IP Allow List related operations.
|
||||
*/
|
||||
ipAllowList = {
|
||||
/**
|
||||
* Get IP allow list entries for a specific proxy configuration.
|
||||
*
|
||||
* @param {string} proxyConfigID
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
getForProxyConfig: async (proxyConfigID) => {
|
||||
return await getJSON(this.getPath(`/ip-allow-list/proxy-config/${proxyConfigID}`));
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all entries for a specific proxy configuration.
|
||||
*
|
||||
* @param {string} proxyConfigID
|
||||
* @returns {Promise<ApiResponse>}
|
||||
*/
|
||||
clearForProxyConfig: async (proxyConfigID) => {
|
||||
return await deleteJSON(this.getPath(`/ip-allow-list/clear-proxy-config/${proxyConfigID}`));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* import is for importing assets, landing pages and etc
|
||||
*/
|
||||
|
||||
@@ -96,16 +96,13 @@ export class ProxyYamlCompletionProvider {
|
||||
if (linePrefix.match(/\s*method:\s*$/)) {
|
||||
return this.getMethodSuggestions(range);
|
||||
}
|
||||
if (linePrefix.match(/\s*(with_session|without_session):\s*$/)) {
|
||||
return this.getActionSuggestions(range);
|
||||
if (linePrefix.match(/\s*on_deny:\s*$/)) {
|
||||
return this.getOnDenySuggestions(range);
|
||||
}
|
||||
|
||||
// Handle array items
|
||||
if (linePrefix.match(/^\s*-\s*$/)) {
|
||||
const context = this.findParentSection(linesAbove, currentIndent);
|
||||
if (context === 'paths') {
|
||||
return this.getPathPatternSuggestions(range);
|
||||
}
|
||||
if (context === 'capture') {
|
||||
return this.getNewCaptureSuggestions(range);
|
||||
}
|
||||
@@ -131,8 +128,6 @@ export class ProxyYamlCompletionProvider {
|
||||
return this.getDomainSuggestions(range);
|
||||
case 'access':
|
||||
return this.getAccessSuggestions(range);
|
||||
case 'on_deny':
|
||||
return this.getOnDenySuggestions(range);
|
||||
case 'capture':
|
||||
return this.getCaptureSuggestions(range);
|
||||
case 'rewrite':
|
||||
@@ -253,7 +248,7 @@ export class ProxyYamlCompletionProvider {
|
||||
label: 'access',
|
||||
kind: this.monaco.languages.CompletionItemKind.Module,
|
||||
insertText: 'access:',
|
||||
documentation: 'Domain access control',
|
||||
documentation: 'Domain access control (optional - defaults to secure private mode)',
|
||||
range
|
||||
},
|
||||
{
|
||||
@@ -285,22 +280,17 @@ export class ProxyYamlCompletionProvider {
|
||||
{
|
||||
label: 'mode',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'mode: "allow"',
|
||||
documentation: 'Access control mode: allow or deny',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'paths',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'paths:',
|
||||
documentation: 'Array of path patterns',
|
||||
insertText: 'mode: "private"',
|
||||
documentation:
|
||||
'Access control mode: public (allow all) or private (IP whitelist after lure). Default: private',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'on_deny',
|
||||
kind: this.monaco.languages.CompletionItemKind.Module,
|
||||
insertText: 'on_deny:',
|
||||
documentation: 'Response when access denied',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'on_deny: "404"',
|
||||
documentation:
|
||||
'Response for blocked requests in private mode (e.g., "404", "https://example.com")',
|
||||
range
|
||||
}
|
||||
];
|
||||
@@ -309,17 +299,24 @@ export class ProxyYamlCompletionProvider {
|
||||
getOnDenySuggestions(range) {
|
||||
return [
|
||||
{
|
||||
label: 'with_session',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'with_session: 403',
|
||||
documentation: 'Response for users with sessions',
|
||||
label: '"404"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"404"',
|
||||
documentation: 'Return 404 Not Found status',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: 'without_session',
|
||||
kind: this.monaco.languages.CompletionItemKind.Property,
|
||||
insertText: 'without_session: 404',
|
||||
documentation: 'Response for users without sessions',
|
||||
label: '"403"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"403"',
|
||||
documentation: 'Return 403 Forbidden status',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"https://example.com"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"https://example.com"',
|
||||
documentation: 'Redirect to specified URL (auto-detected)',
|
||||
range
|
||||
}
|
||||
];
|
||||
@@ -573,17 +570,17 @@ export class ProxyYamlCompletionProvider {
|
||||
getModeSuggestions(range) {
|
||||
return [
|
||||
{
|
||||
label: '"allow"',
|
||||
label: '"private"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"allow"',
|
||||
documentation: 'Allowlist mode - only specified paths allowed',
|
||||
insertText: '"private"',
|
||||
documentation: 'Private mode - IP-based whitelist after lure access (DEFAULT, secure)',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"deny"',
|
||||
label: '"public"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"deny"',
|
||||
documentation: 'Denylist mode - specified paths blocked',
|
||||
insertText: '"public"',
|
||||
documentation: 'Public mode - allow all traffic (traditional proxy behavior)',
|
||||
range
|
||||
}
|
||||
];
|
||||
@@ -782,86 +779,6 @@ export class ProxyYamlCompletionProvider {
|
||||
];
|
||||
}
|
||||
|
||||
getActionSuggestions(range) {
|
||||
return [
|
||||
{
|
||||
label: '"allow"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"allow"',
|
||||
documentation: 'Allow access (override deny)',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"redirect:https://example.com"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"redirect:https://example.com"',
|
||||
documentation: 'Redirect to URL',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '404',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '404',
|
||||
documentation: 'Return 404 Not Found',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '403',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '403',
|
||||
documentation: 'Return 403 Forbidden',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '503',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '503',
|
||||
documentation: 'Return 503 Service Unavailable',
|
||||
range
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getPathPatternSuggestions(range) {
|
||||
return [
|
||||
{
|
||||
label: '"^/admin/"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"^/admin/"',
|
||||
documentation: 'Admin panel paths',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"^/login"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"^/login"',
|
||||
documentation: 'Login page',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"^/api/"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"^/api/"',
|
||||
documentation: 'API endpoints',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"^/assets/"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"^/assets/"',
|
||||
documentation: 'Static assets',
|
||||
range
|
||||
},
|
||||
{
|
||||
label: '"^/\\.git/"',
|
||||
kind: this.monaco.languages.CompletionItemKind.Value,
|
||||
insertText: '"^/\\.git/"',
|
||||
documentation: 'Git repository',
|
||||
range
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
provideHover(model, position) {
|
||||
const word = model.getWordAtPosition(position);
|
||||
if (!word) return null;
|
||||
@@ -884,12 +801,10 @@ export class ProxyYamlCompletionProvider {
|
||||
const hoverData = {
|
||||
version: 'Configuration version. Currently supports "0.0"',
|
||||
global: 'Rules that apply to all domain mappings',
|
||||
access: 'Access control configuration - restricts which paths are accessible',
|
||||
mode: 'Access control mode: "allow" (allowlist) or "deny" (denylist)',
|
||||
paths: 'Array of regex patterns for path matching',
|
||||
on_deny: 'Response configuration when access is denied',
|
||||
with_session: 'Response for users with active proxy sessions (request with mitm cookie)',
|
||||
without_session: 'Response for requests without sessions',
|
||||
access: 'Access control configuration (optional - defaults to private mode for security)',
|
||||
mode: 'Access control mode: "public" (allow all traffic) or "private" (IP whitelist after lure access, DEFAULT)',
|
||||
on_deny:
|
||||
'Response when access is denied in private mode (e.g., "404", "https://example.com")',
|
||||
capture: 'Rules for capturing data from requests/responses',
|
||||
name: 'Unique identifier for the rule',
|
||||
method: 'HTTP method to match (GET, POST, PUT, DELETE, etc.)',
|
||||
|
||||
@@ -60,6 +60,11 @@
|
||||
name: null
|
||||
};
|
||||
|
||||
let isIPAllowListModalVisible = false;
|
||||
let ipAllowListEntries = [];
|
||||
let selectedProxyForIPList = null;
|
||||
let isLoadingIPAllowList = false;
|
||||
|
||||
const currentExample = `version: "0.0"
|
||||
proxy: "My Proxy Campaign"
|
||||
|
||||
@@ -363,6 +368,55 @@ portal.example.com:
|
||||
formValues.startURL = proxyItem.startURL;
|
||||
formValues.proxyConfig = proxyItem.proxyConfig;
|
||||
};
|
||||
|
||||
const openIPAllowListModal = async (proxy) => {
|
||||
selectedProxyForIPList = proxy;
|
||||
isLoadingIPAllowList = true;
|
||||
isIPAllowListModalVisible = true;
|
||||
|
||||
console.log('Opening IP allow list for proxy:', proxy.id);
|
||||
|
||||
try {
|
||||
const res = await api.ipAllowList.getForProxyConfig(proxy.id);
|
||||
console.log('API response:', res);
|
||||
if (res.success) {
|
||||
ipAllowListEntries = res.data || [];
|
||||
} else {
|
||||
console.error('API error:', res.error);
|
||||
addToast(`Failed to load IP allow list: ${res.error}`, 'Error');
|
||||
ipAllowListEntries = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Network error:', e);
|
||||
addToast('Failed to load IP allow list', 'Error');
|
||||
ipAllowListEntries = [];
|
||||
} finally {
|
||||
isLoadingIPAllowList = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeIPAllowListModal = () => {
|
||||
isIPAllowListModalVisible = false;
|
||||
selectedProxyForIPList = null;
|
||||
ipAllowListEntries = [];
|
||||
};
|
||||
|
||||
const clearIPAllowList = async () => {
|
||||
if (!selectedProxyForIPList) return;
|
||||
|
||||
try {
|
||||
const res = await api.ipAllowList.clearForProxyConfig(selectedProxyForIPList.id);
|
||||
if (res.success) {
|
||||
addToast('IP allow list cleared', 'Success');
|
||||
ipAllowListEntries = [];
|
||||
} else {
|
||||
addToast('Failed to clear IP allow list', 'Error');
|
||||
}
|
||||
} catch (e) {
|
||||
addToast('Failed to clear IP allow list', 'Error');
|
||||
console.error('failed to clear IP allow list', e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<HeadTitle title="Proxies" />
|
||||
@@ -424,6 +478,13 @@ portal.example.com:
|
||||
{...globalButtonDisabledAttributes(proxy, contextCompanyID)}
|
||||
/>
|
||||
<TableCopyButton title={'Copy'} on:click={() => openCopyModal(proxy.id)} />
|
||||
<button
|
||||
class="w-full px py-1 text-slate-600 dark:text-gray-200 hover:bg-highlight-blue dark:hover:bg-highlight-blue/50 hover:text-white cursor-pointer text-left transition-colors duration-200"
|
||||
on:click={() => openIPAllowListModal(proxy)}
|
||||
title="View IP Allow List"
|
||||
>
|
||||
<p class="ml-2 text-left">View IP Allow List</p>
|
||||
</button>
|
||||
<TableDeleteButton
|
||||
on:click={() => openDeleteAlert(proxy)}
|
||||
{...globalButtonDisabledAttributes(proxy, contextCompanyID)}
|
||||
@@ -506,4 +567,53 @@ portal.example.com:
|
||||
onClick={() => onClickDelete(deleteValues.id)}
|
||||
bind:isVisible={isDeleteAlertVisible}
|
||||
></DeleteAlert>
|
||||
|
||||
<!-- IP Allow List Modal -->
|
||||
<Modal
|
||||
headerText={`IP Allow List - ${selectedProxyForIPList?.name || ''}`}
|
||||
visible={isIPAllowListModalVisible}
|
||||
onClose={closeIPAllowListModal}
|
||||
isSubmitting={false}
|
||||
>
|
||||
<FormGrid>
|
||||
<div class="col-span-3 w-full overflow-y-auto px-6 py-4 space-y-6">
|
||||
<div class="flex justify-between items-center">
|
||||
{#if !isLoadingIPAllowList && ipAllowListEntries && ipAllowListEntries.length > 0}
|
||||
<BigButton on:click={clearIPAllowList}>Clear All</BigButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if isLoadingIPAllowList}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-400">Loading...</span>
|
||||
</div>
|
||||
{:else if !ipAllowListEntries || ipAllowListEntries.length === 0}
|
||||
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
No IP addresses are allow listed
|
||||
</div>
|
||||
{:else}
|
||||
<Table
|
||||
columns={[
|
||||
{ column: 'IP Address', size: 'medium' },
|
||||
{ column: 'Added At', size: 'medium' },
|
||||
{ column: 'Expires At', size: 'medium' }
|
||||
]}
|
||||
hasData={ipAllowListEntries.length > 0}
|
||||
plural="entries"
|
||||
>
|
||||
{#each ipAllowListEntries as entry}
|
||||
<TableRow>
|
||||
<TableCell>{entry.ip}</TableCell>
|
||||
<TableCell>{new Date(entry.createdAt).toLocaleString()}</TableCell>
|
||||
<TableCell>{new Date(entry.expiresAt).toLocaleString()}</TableCell>
|
||||
<TableCellEmpty />
|
||||
<TableCellEmpty />
|
||||
</TableRow>
|
||||
{/each}
|
||||
</Table>
|
||||
{/if}
|
||||
</div>
|
||||
</FormGrid>
|
||||
</Modal>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user