Skip to content

Commit 989d55f

Browse files
committed
Release 1.0.6
1 parent bd8032c commit 989d55f

9 files changed

Lines changed: 765 additions & 31 deletions

File tree

caido.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default defineConfig({
1212
id,
1313
name: "Authify",
1414
description: "Plugin for seamless Authorization testing of user roles",
15-
version: "0.1.5",
15+
version: "0.1.6",
1616
author: {
1717
name: "Saltify",
1818
email: "saltify7@gmail.com",

packages/backend/src/index.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getFilterSettings, setFilterSettings, shouldFilterRequest, isPluginGene
33
import { saveAuthHeaders, getAuthHeaders, getStoredAuthHeaders, sendHeadersToAuthify, applyHeadersToReplay } from "./auth-headers";
44
import { setSelectedScope, getSelectedScope, getSelectedScopeInternal, setSelectedScopeInternal, isUrlInScope } from "./scopes";
55
import { getResponseContentLength, compareResponses } from "./utils";
6+
import { saveMatchReplaceRules, getMatchReplaceRules, applyMatchReplaceRules, hasEnabledMatchReplaceRules } from "./match-replace";
67

78
// Result type for safe error handling between backend and frontend
89
export type Result<T> =
@@ -559,9 +560,20 @@ const modifyAndResendRequest = async (sdk: SDK<API, BackendEvents>, originalReqR
559560
sdk.console.log(`Modified header: ${name}: ${value}`);
560561
}
561562

563+
// Extract body from original request
564+
const body = headerEndIndex < lines.length ? lines.slice(headerEndIndex + 1).join('\n') : '';
565+
566+
// Apply match & replace rules to the request body (if any rules are configured)
567+
let modifiedBody = body;
568+
if (hasEnabledMatchReplaceRules()) {
569+
modifiedBody = applyMatchReplaceRules(body);
570+
if (modifiedBody !== body) {
571+
sdk.console.log(`Applied match & replace rules to request body`);
572+
}
573+
}
574+
562575
// Reconstruct the modified request
563576
const requestLine = lines[0]; // GET /path HTTP/1.1
564-
const body = headerEndIndex < lines.length ? lines.slice(headerEndIndex + 1).join('\n') : '';
565577

566578
let modifiedReqRaw = requestLine + '\r\n';
567579

@@ -572,8 +584,8 @@ const modifyAndResendRequest = async (sdk: SDK<API, BackendEvents>, originalReqR
572584

573585
// Add empty line and body
574586
modifiedReqRaw += '\r\n';
575-
if (body) {
576-
modifiedReqRaw += body;
587+
if (modifiedBody) {
588+
modifiedReqRaw += modifiedBody;
577589
}
578590

579591
// Convert raw request to RequestSpec and send
@@ -615,9 +627,9 @@ const modifyAndResendRequest = async (sdk: SDK<API, BackendEvents>, originalReqR
615627
}
616628
}
617629

618-
// Set body if present
619-
if (body.trim()) {
620-
spec.setBody(body);
630+
// Set body if present (use modified body from match & replace)
631+
if (modifiedBody.trim()) {
632+
spec.setBody(modifiedBody);
621633
}
622634

623635
// Send the request
@@ -819,6 +831,8 @@ export type API = DefineAPI<{
819831
sendHeadersToAuthify: typeof sendHeadersToAuthify;
820832
applyHeadersToReplay: typeof applyHeadersToReplay;
821833
getCurrentProjectId: typeof getCurrentProjectId;
834+
saveMatchReplaceRules: typeof saveMatchReplaceRules;
835+
getMatchReplaceRules: typeof getMatchReplaceRules;
822836
}>;
823837

824838
export async function init(sdk: SDK<API, BackendEvents>) {
@@ -838,6 +852,8 @@ export async function init(sdk: SDK<API, BackendEvents>) {
838852
sdk.api.register("sendHeadersToAuthify", sendHeadersToAuthify);
839853
sdk.api.register("applyHeadersToReplay", applyHeadersToReplay);
840854
sdk.api.register("getCurrentProjectId", getCurrentProjectId);
855+
sdk.api.register("saveMatchReplaceRules", saveMatchReplaceRules);
856+
sdk.api.register("getMatchReplaceRules", getMatchReplaceRules);
841857

842858
// Register event listener for intercepted responses (event-driven approach)
843859
sdk.events.onInterceptResponse(async (sdk, request, response) => {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { DefineAPI, SDK } from "caido:plugin";
2+
3+
// Result type for safe error handling between backend and frontend
4+
export type Result<T> =
5+
| { kind: "Error"; error: string }
6+
| { kind: "Ok"; value: T };
7+
8+
// Match & Replace rule type
9+
export type MatchReplaceRule = {
10+
id: string;
11+
match: string;
12+
replace: string;
13+
enabled: boolean;
14+
};
15+
16+
// Match & Replace state
17+
let storedMatchReplaceRules: MatchReplaceRule[] = [];
18+
19+
// Save match & replace rules to memory
20+
export const saveMatchReplaceRules = (sdk: SDK, rules: MatchReplaceRule[]): Result<void> => {
21+
storedMatchReplaceRules = rules;
22+
sdk.console.log(`Match & replace rules saved (${rules.length} rules)`);
23+
return { kind: "Ok", value: undefined };
24+
};
25+
26+
// Get stored match & replace rules
27+
export const getMatchReplaceRules = (sdk: SDK): Result<MatchReplaceRule[]> => {
28+
return { kind: "Ok", value: storedMatchReplaceRules };
29+
};
30+
31+
// Get stored match & replace rules for internal use (without SDK parameter)
32+
export const getStoredMatchReplaceRules = (): MatchReplaceRule[] => {
33+
return storedMatchReplaceRules;
34+
};
35+
36+
// Apply match & replace rules to request body
37+
export const applyMatchReplaceRules = (body: string): string => {
38+
if (!body || body.trim() === '') {
39+
return body;
40+
}
41+
42+
let modifiedBody = body;
43+
const enabledRules = storedMatchReplaceRules.filter(rule => rule.enabled && rule.match.trim() !== '');
44+
45+
for (const rule of enabledRules) {
46+
try {
47+
// Use global replace to replace all occurrences
48+
const regex = new RegExp(escapeRegExp(rule.match), 'g');
49+
modifiedBody = modifiedBody.replace(regex, rule.replace);
50+
} catch (error) {
51+
// If regex creation fails, fall back to simple string replacement
52+
modifiedBody = modifiedBody.replace(new RegExp(rule.match.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), rule.replace);
53+
}
54+
}
55+
56+
return modifiedBody;
57+
};
58+
59+
// Helper function to escape special regex characters for literal string matching
60+
const escapeRegExp = (string: string): string => {
61+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
62+
};
63+
64+
// Check if any match & replace rules are configured and enabled
65+
export const hasEnabledMatchReplaceRules = (): boolean => {
66+
return storedMatchReplaceRules.some(rule => rule.enabled && rule.match.trim() !== '');
67+
};
68+
69+
// API type definition for match & replace functions
70+
export type MatchReplaceAPI = DefineAPI<{
71+
saveMatchReplaceRules: typeof saveMatchReplaceRules;
72+
getMatchReplaceRules: typeof getMatchReplaceRules;
73+
}>;
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { ref } from "vue";
2+
import { useSDK } from "@/plugins/sdk";
3+
import { StorageManager } from "@/utils/storage";
4+
5+
export class AuthConfigManager {
6+
private sdk: ReturnType<typeof useSDK>;
7+
private storage: StorageManager;
8+
9+
// Auth config state
10+
public authHeaders = ref<string>("");
11+
12+
constructor(sdk: ReturnType<typeof useSDK>, storage: StorageManager) {
13+
this.sdk = sdk;
14+
this.storage = storage;
15+
}
16+
17+
// Function to refresh auth config (can be called manually or on project change)
18+
async refreshAuthConfig() {
19+
console.log("Refreshing auth config");
20+
try {
21+
// Store the current auth headers before refreshing
22+
const currentAuthHeaders = this.authHeaders.value;
23+
24+
// Load project-specific auth config
25+
await this.loadProjectAuthConfig();
26+
27+
// Return a flag to indicate that traffic should be cleared if auth changed
28+
const authChanged = currentAuthHeaders !== this.authHeaders.value;
29+
return { shouldClearTraffic: authChanged };
30+
31+
} catch (error) {
32+
console.warn("Error refreshing auth config:", error);
33+
this.sdk.window.showToast("Failed to refresh auth config", { variant: "error" });
34+
return { shouldClearTraffic: false };
35+
}
36+
}
37+
38+
// Save auth headers to storage (project-specific)
39+
async saveAuthHeaders() {
40+
const result = await this.sdk.backend.getCurrentProjectId();
41+
if (result.kind === "Ok") {
42+
await this.storage.saveProjectAuthHeaders(this.authHeaders.value, result.value);
43+
} else {
44+
console.warn("Could not get project ID for auth headers storage:", result.error);
45+
}
46+
}
47+
48+
// Clear stored auth headers from storage (project-specific)
49+
async clearStoredAuthHeaders() {
50+
const result = await this.sdk.backend.getCurrentProjectId();
51+
if (result.kind === "Ok") {
52+
await this.storage.clearProjectAuthHeaders(result.value);
53+
} else {
54+
console.warn("Could not get project ID for auth headers clearing:", result.error);
55+
}
56+
}
57+
58+
// Function to load project-specific auth config
59+
async loadProjectAuthConfig() {
60+
try {
61+
// Get current project ID and load project-specific auth headers
62+
const result = await this.sdk.backend.getCurrentProjectId();
63+
if (result.kind === "Ok") {
64+
const storedAuthHeaders = await this.storage.loadProjectAuthHeaders(result.value);
65+
if (storedAuthHeaders !== null) {
66+
this.authHeaders.value = storedAuthHeaders;
67+
console.log("Restored previously saved auth headers for project:", result.value);
68+
69+
// Sync the loaded auth headers with the backend
70+
const backendResult = await this.sdk.backend.saveAuthHeaders(storedAuthHeaders);
71+
if (backendResult.kind === "Error") {
72+
console.warn("Failed to sync auth headers with backend:", backendResult.error);
73+
this.sdk.window.showToast("Failed to sync auth headers with backend", { variant: "warning" });
74+
} else {
75+
console.log("Successfully synced auth headers with backend");
76+
}
77+
} else {
78+
// No stored auth headers, default to empty and sync with backend
79+
this.authHeaders.value = "";
80+
console.log("No stored auth headers for project, defaulting to empty");
81+
82+
// Sync empty auth headers with backend
83+
const backendResult = await this.sdk.backend.saveAuthHeaders("");
84+
if (backendResult.kind === "Error") {
85+
console.warn("Failed to sync empty auth headers with backend:", backendResult.error);
86+
}
87+
}
88+
} else {
89+
console.warn("Could not get project ID for auth headers loading:", result.error);
90+
// No project ID available, default to empty
91+
this.authHeaders.value = "";
92+
}
93+
} catch (error) {
94+
console.warn('Could not load project auth config:', error);
95+
this.sdk.window.showToast('Could not load project auth config', { variant: "error" });
96+
// Fallback to empty
97+
this.authHeaders.value = "";
98+
}
99+
}
100+
101+
// Function to handle auth headers changes and notify backend
102+
async handleAuthHeadersChange(newAuthHeaders: string) {
103+
// Save to backend
104+
const result = await this.sdk.backend.saveAuthHeaders(newAuthHeaders);
105+
if (result.kind === "Error") {
106+
this.sdk.window.showToast(`Failed to save auth headers: ${result.error}`, { variant: "error" });
107+
} else {
108+
// Save the new auth headers to storage
109+
await this.saveAuthHeaders();
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)