Skip to content

Commit

Permalink
Merge pull request nasa#623 from nasa/harmony-1858
Browse files Browse the repository at this point in the history
Harmony 1858 - Update simple oauth lib
  • Loading branch information
vinnyinverso authored Sep 11, 2024
2 parents b0fe15d + 443feaa commit 554e407
Show file tree
Hide file tree
Showing 23 changed files with 1,268 additions and 1,298 deletions.
298 changes: 135 additions & 163 deletions services/giovanni-adapter/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions services/giovanni-adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"@types/node": "^16.18.59",
"@types/node-fetch": "^2.5.7",
"@types/shpjs": "^3.4.1",
"@types/sinon": "^10.0.6",
"@types/sinon": "^17.0.3",
"@types/superagent": "^4.1.13",
"@types/supertest": "^2.0.10",
"@types/tmp": "^0.2.2",
Expand All @@ -80,7 +80,7 @@
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"rimraf": "^5.0.1",
"sinon": "^12.0.1",
"sinon": "^18.0.0",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
},
Expand Down
24 changes: 22 additions & 2 deletions services/harmony/.nsprc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,29 @@
"notes": "Will fix in HARMONY-1729",
"expiry": "2024-11-01"
},
"1097346": {
"1099520": {
"active": true,
"notes": "ignored because it doesn't affect us and there is not current patch",
"notes": "Will fix in HARMONY-1868",
"expiry": "2024-11-01"
},
"1099519": {
"active": true,
"notes": "Will fix in HARMONY-1867",
"expiry": "2024-11-01"
},
"1099529": {
"active": true,
"notes": "Will fix in HARMONY-1869",
"expiry": "2024-11-01"
},
"1099525": {
"active": true,
"notes": "Will fix in HARMONY-1870",
"expiry": "2024-11-01"
},
"1099527": {
"active": true,
"notes": "Will fix in HARMONY-1871",
"expiry": "2024-11-01"
}
}
30 changes: 16 additions & 14 deletions services/harmony/app/middleware/earthdata-login-oauth-authorizer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import axios from 'axios';
import simpleOAuth2, { OAuthClient, Token } from 'simple-oauth2';
import { AuthorizationCode, Token, ModuleOptions } from 'simple-oauth2';
import { RequestHandler, NextFunction } from 'express';
import { cookieOptions, setCookiesForEdl } from '../util/cookies';
import { listToText } from '@harmony/util/string';
Expand All @@ -15,12 +15,15 @@ if (missingVars.length > 0) {
throw new Error(`Earthdata Login configuration error: You must set ${listToText(missingVars)} in the environment`);
}

export const oauthOptions = {
export const oauthOptions: ModuleOptions = {
client: {
id: process.env.OAUTH_CLIENT_ID,
secret: process.env.OAUTH_PASSWORD,
},
auth: { tokenHost: process.env.OAUTH_HOST },
options: {
credentialsEncodingMode: 'loose',
},
};

// Earthdata Login (OAuth2) tokens have the following structure:
Expand All @@ -43,7 +46,7 @@ export const oauthOptions = {
* @param res - The client response
* @param _next - The next function in the middleware chain
*/
async function handleCodeValidation(oauth2: OAuthClient, req, res, _next): Promise<void> {
async function handleCodeValidation(oauth2: AuthorizationCode, req, res, _next): Promise<void> {
const { state } = req.signedCookies;

if (state !== req.query.state) {
Expand All @@ -55,8 +58,7 @@ async function handleCodeValidation(oauth2: OAuthClient, req, res, _next): Promi
redirect_uri: process.env.OAUTH_REDIRECT_URI,
};

const oauthToken = await oauth2.authorizationCode.getToken(tokenConfig);
const { token } = oauth2.accessToken.create(oauthToken);
const { token } = await oauth2.getToken(tokenConfig);
res.cookie('token', token, cookieOptions);
res.clearCookie('redirect', cookieOptions);
res.redirect(307, req.signedCookies.redirect || '/');
Expand All @@ -71,12 +73,12 @@ async function handleCodeValidation(oauth2: OAuthClient, req, res, _next): Promi
* @param res - The client response
* @param _next - The next function in the middleware chain
*/
async function handleLogout(oauth2: OAuthClient, req, res, _next): Promise<void> {
async function handleLogout(oauth2: AuthorizationCode, req, res, _next): Promise<void> {
const { redirect } = req.query;

const { token } = req.signedCookies;
if (token) {
const oauthToken = oauth2.accessToken.create(token);
const oauthToken = oauth2.createToken(token);
await oauthToken.revokeAll();
res.clearCookie('token', cookieOptions);
}
Expand All @@ -92,10 +94,10 @@ async function handleLogout(oauth2: OAuthClient, req, res, _next): Promise<void>
* @param res - The client response
* @param _next - The next function in the middleware chain
*/
function handleNeedsAuthorized(oauth2: OAuthClient, req, res, _next): void {
function handleNeedsAuthorized(oauth2: AuthorizationCode, req, res, _next): void {
const state = setCookiesForEdl(req, res, cookieOptions);

const url = oauth2.authorizationCode.authorizeURL({
const url = oauth2.authorizeURL({
redirect_uri: process.env.OAUTH_REDIRECT_URI,
state,
});
Expand All @@ -110,7 +112,7 @@ function handleNeedsAuthorized(oauth2: OAuthClient, req, res, _next): void {
*/
async function validateUserToken(token: Token): Promise<void> {
await axios.post(
`${oauthOptions.auth.tokenHost}/oauth/tokens/user?token=${encodeURIComponent(token.access_token)}`,
`${oauthOptions.auth.tokenHost}/oauth/tokens/user?token=${encodeURIComponent(token.access_token as string)}`,
null,
{
auth: {
Expand All @@ -132,9 +134,9 @@ async function validateUserToken(token: Token): Promise<void> {
*
* @returns The result of calling the adapter's redirect method
*/
async function handleAuthorized(oauth2: OAuthClient, req, res, next: NextFunction): Promise<void> {
async function handleAuthorized(oauth2: AuthorizationCode, req, res, next: NextFunction): Promise<void> {
const { token } = req.signedCookies;
const oauthToken = oauth2.accessToken.create(token);
const oauthToken = oauth2.createToken(token);
req.accessToken = oauthToken.token.access_token;
try {
if (oauthToken.expired()) {
Expand All @@ -144,7 +146,7 @@ async function handleAuthorized(oauth2: OAuthClient, req, res, next: NextFunctio
} else {
await validateUserToken(oauthToken.token);
}
const user = oauthToken.token.endpoint.split('/').pop();
const user = (oauthToken.token.endpoint as string).split('/').pop();
req.context.logger = req.context.logger.child({ user });
req.user = user;
next();
Expand All @@ -169,7 +171,7 @@ async function handleAuthorized(oauth2: OAuthClient, req, res, next: NextFunctio
*/
export default function buildEdlAuthorizer(paths: Array<string | RegExp> = []): RequestHandler {
return async function earthdataLoginAuthorizer(req: HarmonyRequest, res, next): Promise<void> {
const oauth2 = simpleOAuth2.create(oauthOptions);
const oauth2 = new AuthorizationCode(oauthOptions);
const { token } = req.signedCookies;
const requiresAuth = paths.some((p) => req.path.match(p)) &&
!req.authorized &&
Expand Down
14 changes: 8 additions & 6 deletions services/harmony/app/util/edl-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import { Logger } from 'winston';
import env from './env';
import HarmonyRequest from '../models/harmony-request';
import { oauthOptions } from '../middleware/earthdata-login-oauth-authorizer';
import simpleOAuth2, { AccessToken } from 'simple-oauth2';
import { ClientCredentials, AccessToken } from 'simple-oauth2';

const edlUserRequestUrl = `${env.oauthHost}/oauth/tokens/user`;
const edlUserGroupsBaseUrl = `${env.oauthHost}/api/user_groups/groups_for_user`;
const edlVerifyUserEulaUrl = (username: string, eulaId: string): string =>
`${env.oauthHost}/api/users/${username}/verify_user_eula?eula_id=${eulaId}`;

const oauth2 = simpleOAuth2.create(oauthOptions);
let oauth2: ClientCredentials;
let harmonyClientToken: AccessToken; // valid for 30 days

/**
Expand All @@ -23,11 +23,13 @@ let harmonyClientToken: AccessToken; // valid for 30 days
*/
export async function getClientCredentialsToken(logger: Logger): Promise<string> {
try {
if (!harmonyClientToken || harmonyClientToken.expired()) {
const oauthToken = await oauth2.clientCredentials.getToken({});
harmonyClientToken = oauth2.accessToken.create(oauthToken);
if (oauth2 === undefined) {
oauth2 = new ClientCredentials(oauthOptions);
}
return harmonyClientToken.token.access_token;
if (harmonyClientToken === undefined || harmonyClientToken.expired()) {
harmonyClientToken = await oauth2.getToken({});
}
return harmonyClientToken.token.access_token as string;
} catch (e) {
logger.error('Failed to get client credentials for harmony user.');
logger.error(e);
Expand Down
Loading

0 comments on commit 554e407

Please sign in to comment.