Skip to content

Commit 611bd81

Browse files
author
Karthik Jeeyar
authored
detect GitHub SAML SSO session expiry and prompt users to re-authorize (#3253)
1 parent 03ef10a commit 611bd81

25 files changed

Lines changed: 366 additions & 10 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
3+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
4+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-api': patch
5+
'@red-hat-developer-hub/backstage-plugin-orchestrator': patch
6+
---
7+
8+
detect GitHub SAML SSO session expiry and prompt users to re-authorize

workspaces/orchestrator/plugins/orchestrator-form-api/report.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export type OrchestratorFormContextProps = {
5353
setIsChangedByUser: (id: string, isChangedByUser: boolean) => void;
5454
handleFetchStarted?: () => void;
5555
handleFetchEnded?: () => void;
56+
onSamlSsoError?: (error: Error) => void;
5657
};
5758

5859
// @public
@@ -90,7 +91,7 @@ export const useOrchestratorFormApiOrDefault: () => OrchestratorFormApi;
9091

9192
// Warnings were encountered during analysis:
9293
//
93-
// src/api.d.ts:131:22 - (ae-undocumented) Missing documentation for "useOrchestratorFormApiOrDefault".
94+
// src/api.d.ts:132:22 - (ae-undocumented) Missing documentation for "useOrchestratorFormApiOrDefault".
9495

9596
// (No @packageDocumentation comment for this package)
9697
```

workspaces/orchestrator/plugins/orchestrator-form-api/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export type OrchestratorFormContextProps = {
4646
setIsChangedByUser: (id: string, isChangedByUser: boolean) => void;
4747
handleFetchStarted?: () => void;
4848
handleFetchEnded?: () => void;
49+
onSamlSsoError?: (error: Error) => void;
4950
};
5051

5152
/**

workspaces/orchestrator/plugins/orchestrator-form-react/report.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export type OrchestratorFormProps = {
5858
schema: JSONSchema7;
5959
updateSchema: OrchestratorFormContextProps['updateSchema'];
6060
setAuthTokenDescriptors: OrchestratorFormContextProps['setAuthTokenDescriptors'];
61+
onSamlSsoError?: OrchestratorFormContextProps['onSamlSsoError'];
6162
isExecuting: boolean;
6263
handleExecute: (parameters: JsonObject) => Promise<void>;
6364
handleExecuteAsEvent?: (parameters: JsonObject) => Promise<void>;

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export type OrchestratorFormProps = {
5757
schema: JSONSchema7;
5858
updateSchema: OrchestratorFormContextProps['updateSchema'];
5959
setAuthTokenDescriptors: OrchestratorFormContextProps['setAuthTokenDescriptors'];
60+
onSamlSsoError?: OrchestratorFormContextProps['onSamlSsoError'];
6061
isExecuting: boolean;
6162
handleExecute: (parameters: JsonObject) => Promise<void>;
6263
handleExecuteAsEvent?: (parameters: JsonObject) => Promise<void>;
@@ -157,6 +158,7 @@ const OrchestratorForm = ({
157158
isExecuting,
158159
initialFormData,
159160
setAuthTokenDescriptors,
161+
onSamlSsoError,
160162
t,
161163
executeLabel,
162164
executeAsEventLabel,
@@ -270,6 +272,7 @@ const OrchestratorForm = ({
270272
formData={formData}
271273
setFormData={setFormData}
272274
setAuthTokenDescriptors={setAuthTokenDescriptors}
275+
onSamlSsoError={onSamlSsoError}
273276
getIsChangedByUser={getIsChangedByUser}
274277
setIsChangedByUser={setIsChangedByUser}
275278
>
@@ -284,6 +287,7 @@ const OrchestratorForm = ({
284287
formData={formData}
285288
setFormData={setFormData}
286289
setAuthTokenDescriptors={setAuthTokenDescriptors}
290+
onSamlSsoError={onSamlSsoError}
287291
getIsChangedByUser={getIsChangedByUser}
288292
setIsChangedByUser={setIsChangedByUser}
289293
/>

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/SingleStepForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type SingleStepFormProps = Pick<
3232
| 'formData'
3333
| 'setFormData'
3434
| 'setAuthTokenDescriptors'
35+
| 'onSamlSsoError'
3536
| 'getIsChangedByUser'
3637
| 'setIsChangedByUser'
3738
>;

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/utils/useFetch.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export const useFetch = (
4848
formData: JsonObject,
4949
uiProps: UiProps,
5050
retrigger: ReturnType<typeof useRetriggerEvaluate>,
51+
onSamlSsoError?: (error: Error) => void,
5152
) => {
5253
const fetchApi = useApi(fetchApiRef);
5354

@@ -173,7 +174,18 @@ export const useFetch = (
173174
() => fetchApi.fetch(evaluatedFetchUrl, evaluatedRequestInit),
174175
retryOptions,
175176
);
177+
176178
if (!response.ok) {
179+
const ssoHeader = response.headers.get('x-github-sso');
180+
if (response.status === 403 && ssoHeader) {
181+
const urlMatch = ssoHeader.match(/url=(\S+)/);
182+
const reauthorizeUrl = urlMatch?.[1];
183+
const samlError = new Error(
184+
`GitHub SAML SSO session expired.${reauthorizeUrl ? ` Re-authorize at: ${reauthorizeUrl}` : ''}`,
185+
);
186+
onSamlSsoError?.(samlError);
187+
return;
188+
}
177189
throw new Error(
178190
`Request ${evaluatedFetchUrl} returned status ${response.status}. Status text: ${response.statusText}.`,
179191
);

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveDropdown.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ export const ActiveDropdown: Widget<
101101
uiProps['fetch:retrigger'] as string[],
102102
);
103103

104-
const { data, error, loading } = useFetch(formData ?? {}, uiProps, retrigger);
104+
const { data, error, loading } = useFetch(
105+
formData ?? {},
106+
uiProps,
107+
retrigger,
108+
formContext?.onSamlSsoError,
109+
);
105110

106111
// Track the complete loading state (fetch + processing)
107112
const { completeLoading, wrapProcessing } = useProcessingState(

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveMultiSelect.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ export const ActiveMultiSelect: Widget<
142142
uiProps['fetch:retrigger'] as string[],
143143
);
144144

145-
const { data, error, loading } = useFetch(formData ?? {}, uiProps, retrigger);
145+
const { data, error, loading } = useFetch(
146+
formData ?? {},
147+
uiProps,
148+
retrigger,
149+
formContext?.onSamlSsoError,
150+
);
146151

147152
// Track the complete loading state (fetch + processing)
148153
const { completeLoading, wrapProcessing } = useProcessingState(

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveTextInput.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ export const ActiveTextInput: Widget<
9696
uiProps['fetch:retrigger'] as string[],
9797
);
9898

99-
const { data, error, loading } = useFetch(formData ?? {}, uiProps, retrigger);
99+
const { data, error, loading } = useFetch(
100+
formData ?? {},
101+
uiProps,
102+
retrigger,
103+
formContext?.onSamlSsoError,
104+
);
100105

101106
// Track the complete loading state (fetch + processing)
102107
const { completeLoading, wrapProcessing } = useProcessingState(

0 commit comments

Comments
 (0)