Skip to content

Commit

Permalink
feat: add @microsoft/microsoft-graph-types dependency, update respons…
Browse files Browse the repository at this point in the history
…e handling, and improve type safety in graph request functions
  • Loading branch information
musale committed Nov 29, 2024
1 parent c5a8cea commit 4934e83
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 43 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"dependencies": {
"@augloop/types-core": "file:packages/types-core-2.16.189.tgz",
"@axe-core/webdriverjs": "4.10.0",
"@fluentui/react-components": "9.55.1",
"@fluentui/react-icons": "2.0.264",
"@azure/msal-browser": "3.26.1",
"@babel/core": "7.26.0",
"@babel/runtime": "7.26.0",
Expand All @@ -17,6 +15,7 @@
"@microsoft/applicationinsights-react-js": "17.3.4",
"@microsoft/applicationinsights-web": "3.3.3",
"@microsoft/microsoft-graph-client": "3.0.7",
"@microsoft/microsoft-graph-types": "2.40.0",
"@monaco-editor/react": "4.6.0",
"@ms-ofb/officebrowserfeedbacknpm": "file:packages/officebrowserfeedbacknpm-1.6.6.tgz",
"@reduxjs/toolkit": "2.2.7",
Expand Down
43 changes: 21 additions & 22 deletions src/app/services/actions/permissions-action-creator.util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { User } from '@microsoft/microsoft-graph-types';
import { componentNames, eventTypes, telemetry } from '../../../telemetry';
import { IOAuthGrantPayload, IPermissionGrant } from '../../../types/permissions';
import { IUser } from '../../../types/profile';
import { CustomBody, ResponseValue } from '../../../types/query-response';
import { IQuery } from '../../../types/query-runner';
import { RevokeScopesError } from '../../utils/error-utils/RevokeScopesError';
import { exponentialFetchRetry } from '../../utils/fetch-retry-handler';
import { GRAPH_URL } from '../graph-constants';
import { makeGraphRequest, parseResponse } from './query-action-creator-util';
import { parseResponse, makeGraphRequest as queryMakeGraphRequest } from './query-action-creator-util';

interface IPreliminaryChecksObject {
defaultUserScopes: string[];
Expand Down Expand Up @@ -107,7 +109,9 @@ export class RevokePermissionsUtil {
const tenantAdminQuery = { ...genericQuery };
tenantAdminQuery.sampleUrl = `${GRAPH_URL}/v1.0/me/memberOf`;
const response = await RevokePermissionsUtil.makeExponentialFetch([], tenantAdminQuery);
return response ? response.value.some((value: any) => value.displayName === 'Global Administrator') : false
const value = (response as CustomBody).value
const isAdmin = value ? value.some((v: Partial<User>)=>v?.displayName === 'Global Administrator') : false
return isAdmin
}

public async getUserPermissionChecks(preliminaryObject: PartialCheckObject): Promise<{
Expand Down Expand Up @@ -191,7 +195,7 @@ export class RevokePermissionsUtil {
genericQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants?$filter=clientId eq '${servicePrincipalAppId}'`;
genericQuery.sampleHeaders = [{ name: 'ConsistencyLevel', value: 'eventual' }];
const oAuthGrant = await RevokePermissionsUtil.makeExponentialFetch(scopes, genericQuery);
return oAuthGrant;
return oAuthGrant as IOAuthGrantPayload;
}

public permissionToRevokeInGrant(permissionsGrant: IPermissionGrant, allPrincipalGrant: IPermissionGrant,
Expand All @@ -207,7 +211,8 @@ export class RevokePermissionsUtil {
const currentAppId = process.env.REACT_APP_CLIENT_ID;
genericQuery.sampleUrl = `${GRAPH_URL}/v1.0/servicePrincipals?$filter=appId eq '${currentAppId}'`;
const response = await this.makeGraphRequest(scopes, genericQuery);
return response ? response.value[0].id : '';
const value = (response as CustomBody)?.value
return value ? value[0]?.id ?? '' : ''
}

private async revokePermission(permissionGrantId: string, newScopes: string): Promise<boolean> {
Expand All @@ -219,30 +224,24 @@ export class RevokePermissionsUtil {
patchQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants/${permissionGrantId}`;
genericQuery.sampleHeaders = [{ name: 'ConsistencyLevel', value: 'eventual' }];
patchQuery.selectedVerb = 'PATCH';
// eslint-disable-next-line no-useless-catch
try {
const response = await RevokePermissionsUtil.makeGraphRequest([], patchQuery);
const { error } = response;
if (error) {
return false;
}
return true;
}
catch (error: any) {
throw error;

const response = await RevokePermissionsUtil.makeGraphRequest([], patchQuery);
const error = (response as CustomBody).error;
if (error) {
return false;
}
return true;
}

private static async makeExponentialFetch(scopes: string[], query: IQuery, condition?:
(args?: any) => Promise<boolean>) {
const response = await exponentialFetchRetry(() => makeGraphRequest(scopes)(query),
8, 100, condition);
return parseResponse(response);
private static async makeExponentialFetch(
scopes: string[], query: IQuery, condition?: (args?: unknown) => Promise<boolean>) {
const response = await exponentialFetchRetry(() => queryMakeGraphRequest(scopes)(query), 8, 100, condition);
return parseResponse(response as Response);
}

private static async makeGraphRequest(scopes: string[], query: IQuery) {
const response = await makeGraphRequest(scopes)(query);
return parseResponse(response);
const response = await queryMakeGraphRequest(scopes)(query);
return parseResponse(response as Response);
}

private trackRevokeConsentEvent = (status: string, permissionObject: any) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/actions/profile-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export async function getProfileResponse(): Promise<IProfileResponse> {
const scopes = DEFAULT_USER_SCOPES.split(' ');

const response = await makeGraphRequest(scopes)(query);
const userInfo = await parseResponse(response);
const userInfo = await parseResponse(response as Response);
return {
userInfo,
response
Expand Down
14 changes: 7 additions & 7 deletions src/app/services/actions/query-action-creator-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,30 +107,30 @@ function createAuthenticatedRequest(

export function makeGraphRequest(scopes: string[]) {
return async (query: IQuery) => {
let response;
let response: ResponseBody;

const graphRequest: GraphRequest = createAuthenticatedRequest(scopes, query);

switch (query.selectedVerb) {
case 'GET':
response = await graphRequest.get();
response = await graphRequest.get() as ResponseBody;
break;
case 'POST':
response = await graphRequest.post(query.sampleBody);
response = await graphRequest.post(query.sampleBody) as ResponseBody;
break;
case 'PUT':
response = await graphRequest.put(query.sampleBody);
response = await graphRequest.put(query.sampleBody) as ResponseBody;
break;
case 'PATCH':
response = await graphRequest.patch(query.sampleBody);
response = await graphRequest.patch(query.sampleBody) as ResponseBody;
break;
case 'DELETE':
response = await graphRequest.delete();
response = await graphRequest.delete() as ResponseBody;
break;
default:
return;
}
return Promise.resolve(response);
return Promise.resolve(response) as ResponseBody;
};
}

Expand Down
13 changes: 6 additions & 7 deletions src/app/services/slices/graph-response.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,22 @@ export const runQuery = createAsyncThunk(
async (query: IQuery, { dispatch, getState, rejectWithValue }) => {
const state = getState() as ApplicationState;
const tokenPresent = !!state?.auth?.authToken?.token;
const respHeaders = {};
const createdAt = new Date().toISOString();

try {
const response: Response = tokenPresent
const response: ResponseBody = tokenPresent
? await authenticatedRequest(query)
: await anonymousRequest(query, getState);
const resp = response as Response;
const respHeaders = (resp).headers;

const result: Result = await processResponse(response, dispatch, query);
const result: Result = await processResponse(resp, dispatch, query);

const duration = new Date().getTime() - new Date(createdAt).getTime();
const status = generateStatus({ duration, response });
const status = generateStatus({ duration, response: resp });
dispatch(setQueryResponseStatus(status));

// TODO: fix this api args
const historyItem = generateHistoryItem(status, respHeaders,
query, createdAt, result, duration);
const historyItem = generateHistoryItem(status, respHeaders, query, createdAt, result, duration);
dispatch(addHistoryItem(historyItem));

return result;
Expand Down
6 changes: 3 additions & 3 deletions src/app/utils/fetch-retry-handler.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export async function exponentialFetchRetry<T>( fn: () => Promise<any>, retriesLeft: number,
export async function exponentialFetchRetry<T>( fn: () => Promise<T>, retriesLeft: number,
interval: number, condition?: (result: T, retriesLeft?: number) => Promise<boolean>
): Promise<T> {
try {
const result = await fn();
if (condition) {
const isConditionSatisfied = await condition(result, retriesLeft);
if(isConditionSatisfied){
throw new Error('An error occured during the execution of the request');
throw new Error('An error occurred during the execution of the request');
}
}
if (result && result.status && result.status >= 500){
if (result && result instanceof Response && result.status && result.status >= 500){
throw new Error('Encountered a server error during execution of the request');
}
return result;
Expand Down
9 changes: 8 additions & 1 deletion src/types/query-response.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Person, ResponseType, User } from '@microsoft/microsoft-graph-types';
import { ContentType, Mode } from './enums';
import { IQuery } from './query-runner';

Expand All @@ -23,9 +24,15 @@ export interface IGraphResponse {
}
}

export interface ResponseValue {
id: string
}

export interface CustomBody {
throwsCorsError: boolean,
contentDownloadUrl: string
contentDownloadUrl: string,
error: Error,
value: Partial<User & Person>[] | undefined

}
export type ResponseBody = Partial<CustomBody> | Response | string | object | null | undefined;

0 comments on commit 4934e83

Please sign in to comment.