Skip to content

Commit 00d73e7

Browse files
authored
fix: enforce auth on addClientLog call (#1284)
1 parent 44e0654 commit 00d73e7

5 files changed

Lines changed: 65 additions & 29 deletions

File tree

apps/backend/src/mutations/AdminMutations.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,10 @@ describe('Test Admin Mutations', () => {
218218
})
219219
).resolves.toBe(true);
220220
});
221+
222+
test('An unauthenticated user can not add client log', () => {
223+
return expect(
224+
adminMutations.addClientLog(null, 'Some error')
225+
).resolves.toHaveProperty('reason', 'NOT_LOGGED_IN');
226+
});
221227
});

apps/backend/src/mutations/AdminMutations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ export default class AdminMutations {
112112
return await this.dataSource.deleteInstitution(id);
113113
}
114114

115-
async addClientLog(error: string) {
115+
@Authorized()
116+
async addClientLog(agent: UserWithRole | null, error: string) {
116117
logger.logError('Error received from client', { error });
117118

118119
return true;

apps/backend/src/resolvers/mutations/AddClientLogMutation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ export class AddClientLogMutation {
99
@Arg('error', () => String) error: string,
1010
@Ctx() context: ResolverContext
1111
) {
12-
return context.mutations.admin.addClientLog(error);
12+
return context.mutations.admin.addClientLog(context.user, error);
1313
}
1414
}

apps/frontend/src/components/App.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { FeatureContextProvider } from 'context/FeatureContextProvider';
1010
import { IdleContextPicker } from 'context/IdleContextProvider';
1111
import { SettingsContextProvider } from 'context/SettingsContextProvider';
1212
import { UserContextProvider } from 'context/UserContextProvider';
13-
import { getUnauthorizedApi } from 'hooks/common/useDataApi';
13+
import { sendClientLog } from 'hooks/common/useDataApi';
1414
import clearSession from 'utils/clearSession';
1515

1616
import AppRoutes from './AppRoutes';
@@ -72,19 +72,30 @@ function Root() {
7272

7373
const router = createBrowserRouter([{ path: '*', element: <Root /> }]);
7474

75-
class App extends React.Component {
76-
state = { errorUserInformation: '' };
77-
static getDerivedStateFromError() {
75+
type ErrorUserInformation = {
76+
id: string | number;
77+
currentRole: string | null;
78+
};
79+
80+
type AppState = {
81+
errorUserInformation: ErrorUserInformation | null;
82+
errorToken: string | null;
83+
};
84+
85+
class App extends React.Component<Record<string, never>, AppState> {
86+
state: AppState = { errorUserInformation: null, errorToken: null };
87+
static getDerivedStateFromError(): AppState {
7888
// Update state so the next render will show the fallback UI.
7989
const user = localStorage.getItem('user');
80-
const errorUserInformation = {
90+
const token = localStorage.getItem('token');
91+
const errorUserInformation: ErrorUserInformation = {
8192
id: user ? JSON.parse(user).id : 'Not logged in',
8293
currentRole: localStorage.getItem('currentRole'),
8394
};
8495

8596
clearSession();
8697

87-
return { errorUserInformation };
98+
return { errorUserInformation, errorToken: token };
8899
}
89100

90101
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
@@ -98,7 +109,7 @@ class App extends React.Component {
98109
} catch (e) {
99110
errorMessage = 'Exception while preparing error message';
100111
} finally {
101-
getUnauthorizedApi().addClientLog({ error: errorMessage });
112+
void sendClientLog(errorMessage, this.state.errorToken);
102113
}
103114
}
104115

apps/frontend/src/hooks/common/useDataApi.ts

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,29 @@ const endpoint = '/graphql';
1818
const clientNameHeader = 'apollographql-client-name';
1919
const clientName = 'UOP frontend';
2020

21+
export async function sendClientLog(
22+
errorPayload: string,
23+
token?: string | null
24+
) {
25+
if (!token) return;
26+
27+
try {
28+
await getSdk(
29+
new GraphQLClient(endpoint)
30+
.setHeader(clientNameHeader, clientName)
31+
.setHeader('authorization', `Bearer ${token}`)
32+
).addClientLog({ error: errorPayload });
33+
} catch (sendError) {
34+
console.error('Failed to send client log', sendError);
35+
}
36+
}
37+
2138
const notifyAndLog = async (
2239
enqueueSnackbar: WithSnackbarProps['enqueueSnackbar'],
2340
userMessage: string,
2441
error: ClientError | string,
25-
writeToServerLog = true
42+
writeToServerLog = true,
43+
token?: string
2644
) => {
2745
enqueueSnackbar(userMessage, {
2846
variant: 'error',
@@ -33,10 +51,7 @@ const notifyAndLog = async (
3351
console.error({ userMessage, error });
3452

3553
if (writeToServerLog) {
36-
await getSdk(
37-
// eslint-disable-next-line @typescript-eslint/no-use-before-define
38-
new UnauthorizedGraphQLClient(endpoint, enqueueSnackbar, true)
39-
).addClientLog({ error: JSON.stringify({ userMessage, error }) });
54+
await sendClientLog(JSON.stringify({ userMessage, error }), token);
4055
}
4156
};
4257

@@ -49,8 +64,7 @@ export function getUnauthorizedApi() {
4964
class UnauthorizedGraphQLClient extends GraphQLClient {
5065
constructor(
5166
private endpoint: string,
52-
private enqueueSnackbar: WithSnackbarProps['enqueueSnackbar'],
53-
private skipErrorReport?: boolean
67+
private enqueueSnackbar: WithSnackbarProps['enqueueSnackbar']
5468
) {
5569
super(endpoint);
5670
this.setHeader(clientNameHeader, clientName);
@@ -63,13 +77,6 @@ class UnauthorizedGraphQLClient extends GraphQLClient {
6377
return super
6478
.request<T, V>(query as RequestQuery<T, V>, ...variablesAndRequestHeaders)
6579
.catch((error) => {
66-
// if the `notificationWithClientLog` fails
67-
// and it fails while reporting an error, it can
68-
// easily cause an infinite loop
69-
if (this.skipErrorReport) {
70-
throw error;
71-
}
72-
7380
if (!error || !error.response) {
7481
notifyAndLog(
7582
this.enqueueSnackbar,
@@ -132,11 +139,16 @@ class AuthorizedGraphQLClient extends GraphQLClient {
132139
const newToken = data.token;
133140
this.setHeader('authorization', `Bearer ${newToken}`);
134141
this.tokenRenewed && this.tokenRenewed(newToken as string);
142+
this.token = newToken;
143+
this.renewalDate = this.getRenewalDate(this.token);
144+
this.externalToken = this.getExternalToken(this.token);
135145
} catch (error) {
136146
notifyAndLog(
137147
this.enqueueSnackbar,
138148
'Server rejected user credentials',
139-
JSON.stringify(error)
149+
JSON.stringify(error),
150+
true,
151+
this.token
140152
);
141153
this.onSessionExpired();
142154
}
@@ -154,7 +166,8 @@ class AuthorizedGraphQLClient extends GraphQLClient {
154166
this.enqueueSnackbar,
155167
'No response received from server',
156168
error,
157-
false
169+
false,
170+
this.token
158171
);
159172
} else if (
160173
error.response.error &&
@@ -164,7 +177,8 @@ class AuthorizedGraphQLClient extends GraphQLClient {
164177
this.enqueueSnackbar,
165178
'Connection problem!',
166179
error,
167-
false
180+
false,
181+
this.token
168182
);
169183
} else if (
170184
error.response.errors &&
@@ -175,15 +189,17 @@ class AuthorizedGraphQLClient extends GraphQLClient {
175189
this.enqueueSnackbar,
176190
'Your session has expired, you will need to log in again through the external homepage',
177191
error,
178-
false
192+
false,
193+
this.token
179194
);
180195
this.onSessionExpired();
181196
} else if ((jwtDecode(this.token) as any).exp < nowTimestampSeconds) {
182197
notifyAndLog(
183198
this.enqueueSnackbar,
184199
'Your session has expired, you will need to log in again.',
185200
error,
186-
false
201+
false,
202+
this.token
187203
);
188204
this.onSessionExpired();
189205
} else {
@@ -192,7 +208,9 @@ class AuthorizedGraphQLClient extends GraphQLClient {
192208
notifyAndLog(
193209
this.enqueueSnackbar,
194210
graphQLError?.message || 'Something went wrong!',
195-
error
211+
error,
212+
true,
213+
this.token
196214
);
197215
}
198216

0 commit comments

Comments
 (0)