Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/backends/swift/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,76 @@ export interface OpenStackEndpoint {
url: string;
}

function normalizeLegacySwiftStorageUrl(
authUrl: string,
storageUrl: string,
): string {
const auth = new URL(authUrl);
const storage = new URL(storageUrl);
const localHosts = new Set(["localhost", "127.0.0.1", "::1"]);

if (localHosts.has(storage.hostname) && !localHosts.has(auth.hostname)) {
storage.protocol = auth.protocol;
storage.hostname = auth.hostname;
storage.port = auth.port;
}

return storage.toString().replace(/\/$/, "");
}

async function getLegacySwiftV1AuthMeta(config: SwiftConfig): Promise<{
storageUrl: string;
token: string;
}> {
const { auth_url, credentials } = config;
logger.info(`Fetching Authorization Token (Swift v1) from ${auth_url}`);

const response = await fetch(auth_url, {
method: "GET",
headers: {
"X-Auth-User": credentials.username,
"X-Auth-Key": credentials.password,
},
});

if (!response.ok) {
const msg = await response.text();
throw new HeraldError(response.status, { message: msg });
}

const token = response.headers.get("x-auth-token") ??
response.headers.get("x-storage-token");
const storageUrl = response.headers.get("x-storage-url");

if (token == null) {
throw new HeraldError(400, {
message:
"Error Authenticating to Swift Server: auth token header is null",
});
}

if (storageUrl == null) {
throw new HeraldError(404, {
message: "Storage URL not found in Swift auth response",
});
}

const normalizedStorageUrl = normalizeLegacySwiftStorageUrl(
auth_url,
storageUrl,
);
Comment on lines +62 to +82
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject blank or malformed legacy auth headers before normalizing them.

Line 62 only rejects null, so an empty X-Auth-Token is returned as a valid credential, and Line 79 can throw a raw TypeError if X-Storage-Url is present but blank or malformed. This is an upstream-response parsing path, so it should fail with a structured HeraldError instead of returning unusable auth state or leaking an uncaught URL parse error.

Proposed fix
-  const token = response.headers.get("x-auth-token") ??
-    response.headers.get("x-storage-token");
-  const storageUrl = response.headers.get("x-storage-url");
+  const token = (response.headers.get("x-auth-token") ??
+    response.headers.get("x-storage-token"))?.trim();
+  const storageUrl = response.headers.get("x-storage-url")?.trim();

-  if (token == null) {
+  if (!token) {
     throw new HeraldError(400, {
       message:
         "Error Authenticating to Swift Server: auth token header is null",
     });
   }

-  if (storageUrl == null) {
+  if (!storageUrl) {
     throw new HeraldError(404, {
       message: "Storage URL not found in Swift auth response",
     });
   }

-  const normalizedStorageUrl = normalizeLegacySwiftStorageUrl(
-    auth_url,
-    storageUrl,
-  );
+  let normalizedStorageUrl: string;
+  try {
+    normalizedStorageUrl = normalizeLegacySwiftStorageUrl(
+      auth_url,
+      storageUrl,
+    );
+  } catch {
+    throw new HeraldError(502, {
+      message: "Invalid storage URL returned by Swift auth response",
+    });
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backends/swift/auth.ts` around lines 62 - 82, The code currently accepts
empty or malformed Swift auth headers; update the auth parsing in the
token/storageUrl block to reject blank values and surface structured errors:
trim and validate the extracted token and storageUrl (treat "" or
whitespace-only as missing) and throw HeraldError with appropriate
status/messages instead of proceeding; wrap the call to
normalizeLegacySwiftStorageUrl(auth_url, storageUrl) in a try/catch and convert
any parsing/TypeError into a HeraldError (include context like "Invalid
X-Storage-Url" and the original error message) so
normalizeLegacySwiftStorageUrl, token, storageUrl and HeraldError are referenced
for locating the changes.

logger.info(
`Authorization Token and Storage URL retrieved Successfully (Swift v1): storageUrl=${normalizedStorageUrl}, tokenPresent=${
token.length > 0
}`,
);

return {
token,
storageUrl: normalizedStorageUrl,
};
}

export async function getAuthTokenWithTimeouts(config: SwiftConfig): Promise<
{
storageUrl: string;
Expand Down Expand Up @@ -81,6 +151,10 @@ export async function getAuthTokenWithTimeouts(config: SwiftConfig): Promise<

if (!response.ok) {
const msg = await response.text();
if (response.status === 404 || response.status === 405) {
logger.info("Falling back to legacy Swift v1 auth");
return await getLegacySwiftV1AuthMeta(config);
}
const errMessage = `Failed to authenticate with the auth service: ${msg}`;
logger.warn(errMessage);
throw new HeraldError(response.status, { message: msg });
Expand Down
4 changes: 4 additions & 0 deletions src/backends/swift/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,10 @@ export function convertSwiftCopyObjectToS3Response(
export function convertSwiftDeleteObjectToS3Response(
swiftResponse: Response,
): Result<Response, Error> {
if (swiftResponse.status === 404) {
return createOk(new Response(null, { status: 204 }));
}

if (!swiftResponse.ok) {
return createErr(
new HeraldError(swiftResponse.status, {
Expand Down
Loading
Loading