fix(ui): correct “modified” indicator state across collection, folder, request, and presets/auth tabs (#3386) (#8027)

* fix: 3296 Folder-level No Auth inheritance is ignored; requests still use Collection Auth
This commit is contained in:
sharan-bruno
2026-06-08 16:57:18 +05:30
committed by GitHub
parent b9d8bdf2ec
commit 2d4d4e4037
41 changed files with 1182 additions and 356 deletions

View File

@@ -2,6 +2,7 @@ import { get } from 'lodash';
import {
getTreePathFromCollectionToItem
} from 'utils/collections/index';
import { AUTH_MODES } from 'utils/common/constants';
// Resolve inherited auth by traversing up the folder hierarchy
export const resolveInheritedAuth = (item, collection) => {
@@ -25,8 +26,9 @@ export const resolveInheritedAuth = (item, collection) => {
const collectionAuth = get(collectionRoot, 'request.auth', { mode: 'none' });
let effectiveAuth = collectionAuth;
// Check folders in reverse to find the closest auth configuration
for (let i of [...requestTreePath].reverse()) {
// Walk ancestor folders from deepest up; pick the first one with a concrete auth mode (skip 'none'/'inherit').
for (let idx = requestTreePath.length - 1; idx >= 0; idx--) {
const i = requestTreePath[idx];
if (i.type === 'folder') {
const folderAuth = i?.draft ? get(i, 'draft.request.auth') : get(i, 'root.request.auth');
if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') {
@@ -41,3 +43,51 @@ export const resolveInheritedAuth = (item, collection) => {
auth: effectiveAuth
};
};
export const getEffectiveAuthSource = (collection, item) => {
const authMode = item?.draft
? get(item, 'draft.request.auth.mode')
: (get(item, 'request.auth.mode') ?? get(item, 'root.request.auth.mode'));
if (authMode !== AUTH_MODES.INHERIT) return null;
const collectionRoot = collection?.draft?.root || collection?.root || {};
const collectionAuth = get(collectionRoot, 'request.auth');
let effectiveSource = {
type: 'collection',
name: 'Collection',
auth: collectionAuth
};
const requestTreePath = getTreePathFromCollectionToItem(collection, item);
for (let idx = requestTreePath.length - 1; idx >= 0; idx--) {
const i = requestTreePath[idx];
if (i?.uid === item?.uid) continue;
if (i?.type !== 'folder') continue;
const folderAuth = i?.draft ? get(i, 'draft.request.auth') : get(i, 'root.request.auth');
if (!folderAuth || !folderAuth.mode) continue;
if (folderAuth.mode === AUTH_MODES.INHERIT) continue;
effectiveSource = {
type: 'folder',
name: i.name,
auth: folderAuth
};
break;
}
return effectiveSource;
};
// Returns true when an item actually has auth applied — resolves `inherit` up
// the chain, then checks that the effective mode is set, not 'none', and (if a
// supportedModes list is passed) is one the protocol can apply.
export const hasEffectiveAuth = (collection, item, supportedModes) => {
const auth = item?.draft
? get(item, 'draft.request.auth')
: (get(item, 'request.auth') ?? get(item, 'root.request.auth'));
const mode = auth?.mode === AUTH_MODES.INHERIT
? getEffectiveAuthSource(collection, item)?.auth?.mode
: auth?.mode;
if (!mode || mode === AUTH_MODES.NONE) return false;
if (supportedModes && !supportedModes.includes(mode)) return false;
return true;
};

View File

@@ -1,13 +1,21 @@
import { resolveInheritedAuth } from './index';
import { getEffectiveAuthSource, resolveInheritedAuth } from './index';
jest.mock('utils/collections/index', () => ({
// General path finder: walks the collection.items tree until it finds the
// item with the matching uid and returns the full path to it.
getTreePathFromCollectionToItem: (collection, item) => {
const itemUid = item.uid;
if (itemUid === 'r1') {
return [collection.items[0], collection.items[0].items[0]];
}
return [];
const findPath = (items, targetUid, path = []) => {
for (const i of items || []) {
const next = [...path, i];
if (i.uid === targetUid) return next;
if (i.items) {
const found = findPath(i.items, targetUid, next);
if (found) return found;
}
}
return null;
};
return findPath(collection.items, item?.uid) || [];
}
}));
@@ -17,7 +25,7 @@ const buildCollection = () => {
uid: 'c1',
root: {
request: {
auth: { mode: 'bearer', bearer: { token: 'COLLECTION' } }
auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } }
}
},
items: [
@@ -64,7 +72,7 @@ describe('auth-utils.resolveInheritedAuth', () => {
const resolved = resolveInheritedAuth(item, collection);
expect(resolved.auth.mode).toBe('bearer');
expect(resolved.auth.bearer.token).toBe('COLLECTION');
expect(resolved.auth.bearer.token).toBe('COLLECTION_LEVEL_TOKEN');
});
it('should return original request when mode is not inherit', () => {
@@ -77,3 +85,211 @@ describe('auth-utils.resolveInheritedAuth', () => {
expect(resolved.auth.basic.username).toBe('override');
});
});
describe('auth-utils.getEffectiveAuthSource', () => {
it('returns null when the request mode is not inherit', () => {
const collection = buildCollection();
const item = collection.items[0].items[0]; // r1
item.request.auth = { mode: 'bearer', bearer: { token: 'MOCK_REQUEST_OWN_TOKEN_STRING' } };
expect(getEffectiveAuthSource(collection, item)).toBeNull();
});
it('returns null when the request has no auth configured', () => {
const collection = buildCollection();
const item = collection.items[0].items[0];
item.request.auth = undefined;
expect(getEffectiveAuthSource(collection, item)).toBeNull();
});
it('returns the nearest configured folder when request inherits and folder has auth', () => {
const collection = buildCollection();
const item = collection.items[0].items[0]; // r1, mode 'inherit'
const source = getEffectiveAuthSource(collection, item);
expect(source).toEqual({
type: 'folder',
name: 'Folder',
auth: { mode: 'basic', basic: { username: 'user', password: 'pass' } }
});
});
it('falls back to the collection when no ancestor folder has configured auth', () => {
const collection = buildCollection();
// make the folder also inherit so the walk falls through to the collection
collection.items[0].root.request.auth = { mode: 'inherit' };
const item = collection.items[0].items[0];
const source = getEffectiveAuthSource(collection, item);
expect(source).toEqual({
type: 'collection',
name: 'Collection',
auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } }
});
});
it('skips the item itself when the item is a folder in inherit mode', () => {
// Build a parent → child folder chain; child is the item under test.
const collection = {
uid: 'c1',
root: { request: { auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } } } },
items: [
{
uid: 'parent',
type: 'folder',
name: 'Parent',
root: { request: { auth: { mode: 'basic', basic: { username: 'p', password: 'p' } } } },
items: [
{
uid: 'child',
type: 'folder',
name: 'Child',
root: { request: { auth: { mode: 'inherit' } } },
items: []
}
]
}
]
};
const child = collection.items[0].items[0];
const source = getEffectiveAuthSource(collection, child);
expect(source).toEqual({
type: 'folder',
name: 'Parent',
auth: { mode: 'basic', basic: { username: 'p', password: 'p' } }
});
});
it('prefers the draft mode when item.draft exists', () => {
const collection = buildCollection();
const item = collection.items[0].items[0];
item.request.auth = { mode: 'bearer' }; // saved is not inherit
item.draft = { request: { auth: { mode: 'inherit' } } }; // draft is inherit
const source = getEffectiveAuthSource(collection, item);
expect(source).toEqual({
type: 'folder',
name: 'Folder',
auth: { mode: 'basic', basic: { username: 'user', password: 'pass' } }
});
});
it('resolves correctly when both draft and saved auth are inherit on a folder whose parent is also a folder', () => {
const collection = {
uid: 'c1',
root: { request: { auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } } } },
items: [
{
uid: 'parent',
type: 'folder',
name: 'Parent',
root: { request: { auth: { mode: 'basic', basic: { username: 'p', password: 'p' } } } },
items: [
{
uid: 'child',
type: 'folder',
name: 'Child',
root: { request: { auth: { mode: 'inherit' } } }, // saved: inherit
draft: { request: { auth: { mode: 'inherit' } } }, // draft: inherit
items: []
}
]
}
]
};
const child = collection.items[0].items[0];
const source = getEffectiveAuthSource(collection, child);
expect(source).toEqual({
type: 'folder',
name: 'Parent',
auth: { mode: 'basic', basic: { username: 'p', password: 'p' } }
});
});
it('handles a folder item without draft using its root.request.auth.mode', () => {
// The folder's mode is read from root.request.auth.mode when no draft exists.
const collection = {
uid: 'c1',
root: { request: { auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } } } },
items: [
{
uid: 'folder-inherit',
type: 'folder',
name: 'FolderInherit',
root: { request: { auth: { mode: 'inherit' } } },
items: []
}
]
};
const folder = collection.items[0];
const source = getEffectiveAuthSource(collection, folder);
expect(source).toEqual({
type: 'collection',
name: 'Collection',
auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } }
});
});
it('handles a folder item with draft by reading its draft.request.auth.mode (not the saved root mode)', () => {
// Saved mode is 'basic' (would return null since not inherit), but draft is 'inherit'
// so the walk should run and resolve to the collection.
const collection = {
uid: 'c1',
root: { request: { auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } } } },
items: [
{
uid: 'folder-draft-inherit',
type: 'folder',
name: 'FolderDraftInherit',
root: { request: { auth: { mode: 'basic', basic: { username: 'saved', password: 'saved' } } } },
draft: { request: { auth: { mode: 'inherit' } } },
items: []
}
]
};
const folder = collection.items[0];
const source = getEffectiveAuthSource(collection, folder);
expect(source).toEqual({
type: 'collection',
name: 'Collection',
auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } }
});
});
it('skips ancestor folders whose auth.mode is itself "inherit"', () => {
// Parent folder also inherits — walk should continue past it to collection.
const collection = {
uid: 'c1',
root: { request: { auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } } } },
items: [
{
uid: 'parent',
type: 'folder',
name: 'Parent',
root: { request: { auth: { mode: 'inherit' } } },
items: [
{
uid: 'r1',
type: 'request',
name: 'Request',
request: { auth: { mode: 'inherit' } }
}
]
}
]
};
const item = collection.items[0].items[0];
const source = getEffectiveAuthSource(collection, item);
expect(source).toEqual({
type: 'collection',
name: 'Collection',
auth: { mode: 'bearer', bearer: { token: 'COLLECTION_LEVEL_TOKEN' } }
});
});
});

View File

@@ -1,3 +1,47 @@
export const REQUEST_TYPES = ['http-request', 'graphql-request', 'grpc-request', 'ws-request'];
export const DEFAULT_COLLECTION_FORMAT = 'yml';
export const PRESET_REQUEST_TYPES = {
HTTP: 'http',
GRAPHQL: 'graphql',
GRPC: 'grpc',
WS: 'ws'
};
export const DEFAULT_PRESET_REQUEST_TYPE = PRESET_REQUEST_TYPES.HTTP;
export const AUTH_MODES = {
AWSV4: 'awsv4',
BASIC: 'basic',
BEARER: 'bearer',
DIGEST: 'digest',
NTLM: 'ntlm',
OAUTH1: 'oauth1',
OAUTH2: 'oauth2',
WSSE: 'wsse',
APIKEY: 'apikey',
NONE: 'none',
INHERIT: 'inherit'
};
// Auth modes supported by WS protocol.
export const AUTH_MODES_WS = [
AUTH_MODES.BASIC,
AUTH_MODES.BEARER,
AUTH_MODES.APIKEY,
AUTH_MODES.OAUTH2,
AUTH_MODES.NONE,
AUTH_MODES.INHERIT
];
// Auth modes supported by GRPC protocol
export const AUTH_MODES_GRPC = [
AUTH_MODES.BASIC,
AUTH_MODES.BEARER,
AUTH_MODES.APIKEY,
AUTH_MODES.OAUTH2,
AUTH_MODES.WSSE,
AUTH_MODES.NONE,
AUTH_MODES.INHERIT
];