Merge pull request #1159 from prymitive/get-no-cors

fix(ui): use mode:no-cors for fetch GET requests
This commit is contained in:
Łukasz Mierzwa
2019-11-14 16:49:28 +00:00
committed by GitHub
13 changed files with 113 additions and 76 deletions

View File

@@ -1,9 +1,28 @@
import merge from "lodash.merge";
const FetchWithCredentials = async (uri, options) =>
const CommonOptions = {
credentials: "include",
redirect: "follow"
};
const FetchGet = async (uri, options) =>
await fetch(
uri,
merge({}, { credentials: "include", redirect: "follow" }, options)
merge(
{},
{
method: "GET",
mode: "no-cors"
},
CommonOptions,
options
)
);
export { FetchWithCredentials };
const FetchPost = async (uri, options) =>
await fetch(uri, merge({}, { method: "POST" }, CommonOptions, options));
const FetchDelete = async (uri, options) =>
await fetch(uri, merge({}, { method: "DELETE" }, CommonOptions, options));
export { CommonOptions, FetchGet, FetchPost, FetchDelete };

View File

@@ -1,4 +1,6 @@
import { FetchWithCredentials } from "./Fetch";
import { CommonOptions, FetchGet, FetchPost, FetchDelete } from "./Fetch";
import merge from "lodash.merge";
beforeEach(() => {
fetch.resetMocks();
@@ -8,37 +10,53 @@ afterEach(() => {
jest.restoreAllMocks();
});
describe("FetchWithCredentials", () => {
it("fetch passes '{credentials: include}' to all requests", async () => {
const request = FetchWithCredentials("http://example.com", {});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith("http://example.com", {
credentials: "include",
redirect: "follow"
});
});
describe("Fetch", () => {
const tests = {
FetchGet: FetchGet,
FetchPost: FetchPost,
FetchDelete: FetchDelete
};
it("custom keys are merged with defaults", async () => {
const request = FetchWithCredentials("http://example.com", {
foo: "bar"
});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith("http://example.com", {
credentials: "include",
redirect: "follow",
foo: "bar"
});
});
const methodOptions = {
FetchGet: { method: "GET", mode: "no-cors" },
FetchPost: { method: "POST" },
FetchDelete: { method: "DELETE" }
};
it("custom credentials are used when passed", async () => {
const request = FetchWithCredentials("http://example.com", {
credentials: "none",
redirect: "follow"
for (const [name, func] of Object.entries(tests)) {
it(`${name}: passes '{credentials: include}' to all requests`, async () => {
const request = func("http://example.com", {});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
"http://example.com",
merge({}, CommonOptions, methodOptions[name])
);
});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith("http://example.com", {
credentials: "none",
redirect: "follow"
it(`${name}: custom keys are merged with defaults`, async () => {
const request = func("http://example.com", {
foo: "bar"
});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
"http://example.com",
merge({}, CommonOptions, methodOptions[name], { foo: "bar" })
);
});
});
it(`${name}: custom credentials are used when passed`, async () => {
const request = func("http://example.com", {
credentials: "none",
redirect: "follow"
});
await expect(request).resolves.toBeUndefined();
expect(fetch).toHaveBeenCalledWith(
"http://example.com",
merge({}, CommonOptions, methodOptions[name], {
credentials: "none",
redirect: "follow"
})
);
});
}
});

View File

@@ -21,7 +21,7 @@ import {
MatchersFromGroup,
GenerateAlertmanagerSilenceData
} from "Stores/SilenceFormStore";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchPost } from "Common/Fetch";
import { TooltipWrapper } from "Components/TooltipWrapper";
const SubmitState = Object.freeze({
@@ -148,19 +148,15 @@ const AlertAck = observer(
? `${am.uri}/api/v2/silences`
: `${am.uri}/api/v1/silences`;
this.submitState.silencesByCluster[cluster].fetch = FetchWithCredentials(
uri,
{
method: "POST",
body: JSON.stringify(
this.submitState.silencesByCluster[cluster].payload
),
headers: {
"Content-Type": "application/json",
...am.headers
}
this.submitState.silencesByCluster[cluster].fetch = FetchPost(uri, {
body: JSON.stringify(
this.submitState.silencesByCluster[cluster].payload
),
headers: {
"Content-Type": "application/json",
...am.headers
}
)
})
.then(result => {
if (isOpenAPI) {
if (result.ok) {

View File

@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import Creatable from "react-select/creatable";
import { StaticLabels } from "Common/Query";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
import { FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { ReactSelectStyles } from "Components/MultiSelect";
@@ -34,7 +34,7 @@ const SortLabelName = observer(
});
populateNameSuggestions = action(() => {
this.nameSuggestionsFetch = FetchWithCredentials(
this.nameSuggestionsFetch = FetchGet(
FormatBackendURI(`labelNames.json`),
{}
)

View File

@@ -16,7 +16,7 @@ import { APISilence } from "Models/API";
import { AlertStore, FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet, FetchDelete } from "Common/Fetch";
import { Modal } from "Components/Modal";
import {
LabelSetList,
@@ -144,7 +144,7 @@ const DeleteSilenceModalContent = observer(
FormatQuery(StaticLabels.SilenceID, QueryOperators.Equal, silence.id)
]);
this.previewState.fetch = FetchWithCredentials(alertsURI, {})
this.previewState.fetch = FetchGet(alertsURI, {})
.then(result => result.json())
.then(result => {
this.previewState.groupsToUniqueLabels(Object.values(result.groups));
@@ -175,8 +175,7 @@ const DeleteSilenceModalContent = observer(
? `${alertmanager.uri}/api/v2/silence/${silence.id}`
: `${alertmanager.uri}/api/v1/silence/${silence.id}`;
this.deleteState.fetch = FetchWithCredentials(uri, {
method: "DELETE",
this.deleteState.fetch = FetchDelete(uri, {
headers: alertmanager.headers
})
.then(result => {

View File

@@ -15,7 +15,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons/faSearch";
import { AlertStore, FormatBackendURI } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { IsMobile } from "Common/Device";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
import { FilterInputLabel } from "Components/Labels/FilterInputLabel";
import { AutosuggestTheme } from "./Constants";
import { History } from "./History";
@@ -73,7 +73,7 @@ const FilterInput = observer(
onSuggestionsFetchRequested = debounce(
action(({ value }) => {
if (value !== "") {
this.inputStore.suggestionsFetch = FetchWithCredentials(
this.inputStore.suggestionsFetch = FetchGet(
FormatBackendURI(`autocomplete.json?term=${value}`),
{}
)

View File

@@ -21,7 +21,7 @@ import { faAngleDoubleRight } from "@fortawesome/free-solid-svg-icons/faAngleDou
import { AlertStore, FormatBackendURI } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { Settings } from "Stores/Settings";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
import { MountFade } from "Components/Animations/MountFade";
import { ManagedSilence } from "Components/ManagedSilence";
@@ -102,7 +102,7 @@ const Browser = observer(
}`
);
this.dataSource.fetch = FetchWithCredentials(uri, {})
this.dataSource.fetch = FetchGet(uri, {})
.then(result => {
return result.json();
})

View File

@@ -8,7 +8,7 @@ import { SilenceFormMatcher } from "Models/SilenceForm";
import { MultiSelect } from "Components/MultiSelect";
import { ValidationError } from "Components/MultiSelect/ValidationError";
import { FormatBackendURI } from "Stores/AlertStore";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
const LabelNameInput = observer(
class LabelNameInput extends MultiSelect {
@@ -20,7 +20,7 @@ const LabelNameInput = observer(
populateNameSuggestions = action(() => {
const { matcher } = this.props;
this.nameSuggestionsFetch = FetchWithCredentials(
this.nameSuggestionsFetch = FetchGet(
FormatBackendURI(`labelNames.json`),
{}
)
@@ -45,7 +45,7 @@ const LabelNameInput = observer(
populateValueSuggestions = action(() => {
const { matcher } = this.props;
this.valueSuggestionsFetch = FetchWithCredentials(
this.valueSuggestionsFetch = FetchGet(
FormatBackendURI(`labelValues.json?name=${matcher.name}`),
{}
)

View File

@@ -14,7 +14,7 @@ import { FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { SilenceFormMatcher } from "Models/SilenceForm";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
import { MatcherToFilter, AlertManagersToFilter } from "../Matchers";
const MatchCounter = observer(
@@ -55,7 +55,7 @@ const MatchCounter = observer(
const alertsURI =
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters);
this.matchedAlerts.fetch = FetchWithCredentials(alertsURI, {})
this.matchedAlerts.fetch = FetchGet(alertsURI, {})
.then(result => {
return result.json();
})

View File

@@ -16,7 +16,7 @@ import {
LabelSetList,
GroupListToUniqueLabelsList
} from "Components/LabelSetList";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
import { MatcherToFilter, AlertManagersToFilter } from "../Matchers";
const FetchError = ({ message }) => (
@@ -82,7 +82,7 @@ const SilencePreview = observer(
const alertsURI =
FormatBackendURI("alerts.json?") + FormatAlertsQ(filters);
this.matchedAlerts.fetch = FetchWithCredentials(alertsURI, {})
this.matchedAlerts.fetch = FetchGet(alertsURI, {})
.then(result => {
return result.json();
})

View File

@@ -85,11 +85,14 @@ describe("<SilencePreview />", () => {
const tree = MountedSilencePreview();
await expect(tree.instance().matchedAlerts.fetch).resolves.toBeUndefined();
expect(
fetch
).toHaveBeenCalledWith(
expect(fetch).toHaveBeenCalledWith(
"./alerts.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28amValue%29%24",
{ credentials: "include", redirect: "follow" }
{
method: "GET",
mode: "no-cors",
credentials: "include",
redirect: "follow"
}
);
});
@@ -101,11 +104,14 @@ describe("<SilencePreview />", () => {
const tree = MountedSilencePreview();
await expect(tree.instance().matchedAlerts.fetch).resolves.toBeUndefined();
expect(
fetch
).toHaveBeenCalledWith(
expect(fetch).toHaveBeenCalledWith(
"./alerts.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%7Cam2%29%24",
{ credentials: "include", redirect: "follow" }
{
method: "GET",
mode: "no-cors",
credentials: "include",
redirect: "follow"
}
);
});

View File

@@ -13,7 +13,7 @@ import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclama
import { APISilenceMatcher } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchPost } from "Common/Fetch";
const SubmitState = Object.freeze({
InProgress: "InProgress",
@@ -109,8 +109,7 @@ const SilenceSubmitProgress = observer(
? `${am.uri}/api/v2/silences`
: `${am.uri}/api/v1/silences`;
this.submitState.fetch = FetchWithCredentials(uri, {
method: "POST",
this.submitState.fetch = FetchPost(uri, {
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",

View File

@@ -8,7 +8,7 @@ import qs from "qs";
import moment from "moment";
import { FetchWithCredentials } from "Common/Fetch";
import { FetchGet } from "Common/Fetch";
const QueryStringEncodeOptions = {
encodeValuesOnly: true, // don't encode q[]
@@ -268,7 +268,7 @@ class AlertStore {
`alerts.json?sortOrder=${sortOrder}&sortLabel=${sortLabel}&sortReverse=${sortReverse}&`
) + FormatAPIFilterQuery(this.filters.values.map(f => f.raw));
return FetchWithCredentials(alertsURI, {})
return FetchGet(alertsURI, {})
.then(result => {
this.status.setProcessing();
return result.json();