Skip to content

Commit

Permalink
server side okta validation middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
pvighi committed Oct 17, 2023
1 parent bfec490 commit 5d25262
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 1 deletion.
81 changes: 81 additions & 0 deletions server/middleware/OktaServerSideAuthMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { joinUrl } from '@guardian/libs';
import type { NextFunction, Request, Response } from 'express';
import fetch from 'node-fetch';
import { getConfig as getOktaConfig } from '@/server/oktaConfig';
import { requiresSignin } from '../../shared/requiresSignin';

declare const CYPRESS: string;
//TODO remove all the extra console.log everywhere (not just on this file)
type UserInfo = {
legacy_identity_id: string;
email: string;
name: string;
};

export const withOktaSeverSideValidation = async (
req: Request,
res: Response,
next: NextFunction,
) => {
const signinRequired = () => requiresSignin(req.originalUrl);

//TODO I have no idea how this works and how safe it is
if (CYPRESS === 'SKIP_IDAPI') {
return next();
}
const locallyValidatedUserId = res.locals?.identity?.userId;

if (!locallyValidatedUserId) {
if (signinRequired()) {
console.log(
'error: could not find user id in request, expected local token validation to happen before server side check!',
);
res.send(500);
} else {
console.log(
' no credentials, but they were optional so server side validation is skipped!',
);
}
return {};
}

console.log(`MY USELESS MIDDLEWARE WAS CALLED with url ${req.url}`);
const authHeader = `Bearer ${req.signedCookies['GU_ACCESS_TOKEN']}`;
// maybe move the actual okta code to oauth.ts or maybe rename to something that makes sense?
const oktaConfig = await getOktaConfig();
const issuerUrl = joinUrl(
oktaConfig.orgUrl,
'/oauth2/',
oktaConfig.authServerId,
);

const oktaResponse = await fetch(`${issuerUrl}/v1/userinfo/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: authHeader,
},
});
console.log(`n=>> response from okta was status: ${oktaResponse.status}`);
if (oktaResponse.status != 200) {
console.log('invalid credentials! failing server side validation!');
res.send(401);
return {};
} else {
const userInfo = await oktaResponse.json<UserInfo>();

if (userInfo.legacy_identity_id != locallyValidatedUserId) {
console.log("userId in token doesn't match userInfo response!");
res.send(500);
return {};
}

res.locals.identity = {
signInStatus: 'signedInRecently',
userId: userInfo.legacy_identity_id,
name: userInfo.name,
email: userInfo.email,
};
return next();
}
};
9 changes: 8 additions & 1 deletion server/routes/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Sentry from '@sentry/node';
import { Router } from 'express';
import { withOktaSeverSideValidation } from '@/server/middleware/OktaServerSideAuthMiddleware';
import type { MembersDataApiResponse } from '../../shared/productResponse';
import { isProduct, MDA_TEST_USER_HEADER } from '../../shared/productResponse';
import {
Expand Down Expand Up @@ -209,16 +210,22 @@ router.get(
'subscriptionName',
]),
);
router.post('/holidays', holidayStopAPI('/hsr', 'HOLIDAY_STOP_CREATE'));
router.post(
'/holidays',
withOktaSeverSideValidation,
holidayStopAPI('/hsr', 'HOLIDAY_STOP_CREATE'),
);
router.patch(
'/holidays/:subscriptionName/:sfId',
withOktaSeverSideValidation,
holidayStopAPI('hsr/:subscriptionName/:sfId', 'HOLIDAY_STOP_AMEND', [
'subscriptionName',
'sfId',
]),
);
router.delete(
'/holidays/:subscriptionName/:sfId',
withOktaSeverSideValidation,
holidayStopAPI('hsr/:subscriptionName/:sfId', 'HOLIDAY_STOP_WITHDRAW', [
'subscriptionName',
'sfId',
Expand Down

0 comments on commit 5d25262

Please sign in to comment.