Skip to content

Commit

Permalink
feat(middleware): authorization
Browse files Browse the repository at this point in the history
  • Loading branch information
glebcha committed Nov 28, 2023
1 parent ea0154e commit 2844881
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 0 deletions.
58 changes: 58 additions & 0 deletions src/middleware/auth/authMiddleware.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { fetch } from 'cross-fetch';
import {
http,
HttpResponse
} from 'msw';
import { setupServer } from 'msw/node';
import { suite } from 'uvu';
import * as assert from 'uvu/assert';

import { createHttpClient } from '../../createHttpClient';
import {
authRefreshEndpoint,
endpoint,
getTokens,
queryInvalid
} from '../../mock';
import { BasicObject } from '../../types';

import { initAuthMiddleware } from './authMiddleware';

const clientSuite = suite('Authorization Middleware');

const server = setupServer(
http.post(endpoint, async ({ request }) => {
const body = await request.json().catch(() => '');
const headers = request.headers;
const isInvalid = String(body) === queryInvalid;

return HttpResponse.json(
body,
{ headers, status: isInvalid ? 401 : 200 },
);
}),
http.post(authRefreshEndpoint, ({ request }) => {
const headers = request.headers;

return HttpResponse.json(
{ accessToken: '4321', refreshToken: 'refresh4321' },
{ headers, status: 200 },
);
}),
);

global.fetch = fetch;

clientSuite.before(() => server.listen());
clientSuite.after.each(() => server.resetHandlers());
clientSuite.after(() => server.close());

clientSuite.only('should apply auth middleware', async () => {
const authParams = { url: authRefreshEndpoint, getTokens, setTokens: () => {} };
const { post } = createHttpClient({ middleware: { response: [initAuthMiddleware(authParams)] } });
const response = await post<BasicObject & { headers: Request['headers'] }>({ url: endpoint, query: queryInvalid });
// @ts-expect-error header assertion
assert.is(response.headers.Authorization, 'Bearer 4321');
});

clientSuite.run();
57 changes: 57 additions & 0 deletions src/middleware/auth/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { MiddlewareHandler } from '../../types';
import { getBody, is } from '../../utils';

import { AuthMiddlewareParams } from './types';

export function initAuthMiddleware(initParams: AuthMiddlewareParams) {
const {
url,
errorCodes = [401],
getTokens,
setTokens,
getHeaders,
handleAuthError,
} = initParams;

return async (...params: Parameters<MiddlewareHandler>) => {
const [options, meta] = params;
const currentSessionTokens = getTokens();
const headers =
typeof getHeaders === 'function' ?
getHeaders(meta) :
{ Authorization: `Bearer ${currentSessionTokens.accessToken}` };
const shouldProcessAuth = errorCodes.some(errorCode => errorCode === meta.status);

if (shouldProcessAuth) {
const body = getBody({ refreshToken: currentSessionTokens.refreshToken });
const sanitizedOptions = is.Object(options) ? options : {};
const sanitizedHeaders = sanitizedOptions?.headers ?? {};
const errorHandler =
typeof handleAuthError === 'function' ?
handleAuthError :
// eslint-disable-next-line no-console
() => console.warn('Failed to refresh authorization token');

return fetch(url, { method: 'post', body, headers })
.then(async (response) => {
const tokens = await response.json().catch(() => null) as ReturnType<AuthMiddlewareParams['getTokens']> | null;
const { accessToken } = getTokens(tokens);
const optionsWithAuth = {
...sanitizedOptions,
ok: response.ok,
headers: {
...sanitizedHeaders,
Authorization: `Bearer ${accessToken}`,
},
};

setTokens(tokens);

return optionsWithAuth;
})
.catch(errorHandler);
}

return { ...options ?? {}, headers };
};
}
1 change: 1 addition & 0 deletions src/middleware/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { initAuthMiddleware } from './authMiddleware';
8 changes: 8 additions & 0 deletions src/middleware/auth/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface AuthMiddlewareParams {
url: string;
errorCodes?: Response['status'][];
getTokens: (tokens?: unknown) => { accessToken: string; refreshToken: string };
setTokens: (tokens?: unknown) => void;
getHeaders?: (meta: Partial<Response>) => Response['headers'];
handleAuthError?: (error: unknown) => void;
}
1 change: 1 addition & 0 deletions src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { initAuthMiddleware } from './auth';

0 comments on commit 2844881

Please sign in to comment.