Skip to content

Commit

Permalink
Add CAM token SSO support
Browse files Browse the repository at this point in the history
  • Loading branch information
skovati committed Dec 8, 2023
1 parent 136b211 commit abc0751
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"altair-express-middleware": "^5.2.11",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
Expand All @@ -32,6 +33,7 @@
"winston": "^3.9.0"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.6",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
Expand Down
8 changes: 8 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type Env = {
ALLOWED_ROLES_NO_AUTH: string[];
AUTH_TYPE: string;
AUTH_URL: string;
AUTH_UI_URL: string;
AUTH_SSO_TOKEN_NAME: string;
DEFAULT_ROLE: string;
DEFAULT_ROLE_NO_AUTH: string;
GQL_API_URL: string;
Expand All @@ -30,6 +32,8 @@ export const defaultEnv: Env = {
ALLOWED_ROLES_NO_AUTH: ['aerie_admin', 'user', 'viewer'],
AUTH_TYPE: 'cam',
AUTH_URL: 'https://atb-ocio-12b.jpl.nasa.gov:8443/cam-api',
AUTH_UI_URL: 'https://atb-ocio-12b.jpl.nasa.gov:8443/cam-ui/',
AUTH_SSO_TOKEN_NAME: 'iPlanetDirectoryPro',
DEFAULT_ROLE: 'user',
DEFAULT_ROLE_NO_AUTH: 'aerie_admin',
GQL_API_URL: 'http://localhost:8080/v1/graphql',
Expand Down Expand Up @@ -87,6 +91,8 @@ export function getEnv(): Env {
const ALLOWED_ROLES_NO_AUTH = parseArray(env['ALLOWED_ROLES_NO_AUTH'], defaultEnv.ALLOWED_ROLES_NO_AUTH);
const AUTH_TYPE = env['AUTH_TYPE'] ?? defaultEnv.AUTH_TYPE;
const AUTH_URL = env['AUTH_URL'] ?? defaultEnv.AUTH_URL;
const AUTH_UI_URL = env['AUTH_UI_URL'] ?? defaultEnv.AUTH_UI_URL;
const AUTH_SSO_TOKEN_NAME = env['AUTH_SSO_TOKEN_NAME'] ?? defaultEnv.AUTH_SSO_TOKEN_NAME;
const DEFAULT_ROLE = env['DEFAULT_ROLE'] ?? defaultEnv.DEFAULT_ROLE;
const DEFAULT_ROLE_NO_AUTH = env['DEFAULT_ROLE_NO_AUTH'] ?? defaultEnv.DEFAULT_ROLE_NO_AUTH;
const GQL_API_URL = env['GQL_API_URL'] ?? defaultEnv.GQL_API_URL;
Expand All @@ -111,6 +117,8 @@ export function getEnv(): Env {
ALLOWED_ROLES_NO_AUTH,
AUTH_TYPE,
AUTH_URL,
AUTH_UI_URL,
AUTH_SSO_TOKEN_NAME,
DEFAULT_ROLE,
DEFAULT_ROLE_NO_AUTH,
GQL_API_URL,
Expand Down
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DbMerlin } from './packages/db/db.js';
import initFileRoutes from './packages/files/files.js';
import initHealthRoutes from './packages/health/health.js';
import initSwaggerRoutes from './packages/swagger/swagger.js';
import cookieParser from 'cookie-parser';

async function main(): Promise<void> {
const logger = getLogger('main');
Expand All @@ -18,6 +19,7 @@ async function main(): Promise<void> {
app.use(helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false }));
app.use(cors());
app.use(express.json());
app.use(cookieParser());

await DbMerlin.init();

Expand Down
65 changes: 65 additions & 0 deletions src/packages/auth/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export async function getUserRoles(
[username],
);

// @ts-ignore
if (rowCount > 0) {
const [row] = rows;
const { hasura_allowed_roles, hasura_default_role } = row;
Expand Down Expand Up @@ -112,6 +113,70 @@ export function generateJwt(username: string, defaultRole: string, allowedRoles:
}
}

export async function validateSSOToken(ssoToken: string): Promise<SessionResponse> {
const { AUTH_URL, AUTH_UI_URL } = getEnv();

const body = JSON.stringify({ ssoToken });
const url = `${AUTH_URL}/ssoToken?action=validate`;
const response = await fetch(url, { body, method: 'POST' });
const json = await response.json();

// @ts-ignore
const { validated = false, errorCode = false } = json;

if (errorCode) {
return {
message: AUTH_UI_URL,
success: false
};
}

return {
message: "",
success: validated
};
}

export async function loginSSO(ssoToken: string): Promise<AuthResponse> {
const { AUTH_TYPE, AUTH_URL, DEFAULT_ROLE, ALLOWED_ROLES, DEFAULT_ROLE_NO_AUTH, ALLOWED_ROLES_NO_AUTH } = getEnv();

try {
const body = JSON.stringify({ ssoToken });
const url = `${AUTH_URL}/userProfile`;
const response = await fetch(url, { body, method: 'POST' });
const json = await response.json();
// @ts-ignore
const { userId = "", errorCode = false } = json;

if (errorCode) {
// @ts-ignore
const { errorMessage } = json;
return {
message: errorMessage,
success: false,
token: null,
};
}

const { allowed_roles, default_role } = AUTH_TYPE === "none"
? await getUserRoles(userId, DEFAULT_ROLE_NO_AUTH, ALLOWED_ROLES_NO_AUTH)
: await getUserRoles(userId, DEFAULT_ROLE, ALLOWED_ROLES);

return {
message: userId,
success: true,
token: generateJwt(userId, default_role, allowed_roles),
};
} catch (error) {
return {
message: 'An unexpected error occurred',
success: false,
token: null,
};
}

}

export async function login(username: string, password: string): Promise<AuthResponse> {
const { AUTH_TYPE, AUTH_URL, ALLOWED_ROLES, ALLOWED_ROLES_NO_AUTH, DEFAULT_ROLE, DEFAULT_ROLE_NO_AUTH } = getEnv();

Expand Down
61 changes: 60 additions & 1 deletion src/packages/auth/routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Express } from 'express';
import rateLimit from 'express-rate-limit';
import { getEnv } from '../../env.js';
import { login, session } from './functions.js';
import { login, loginSSO, session, validateSSOToken } from './functions.js';

export default (app: Express) => {
const { RATE_LIMITER_LOGIN_MAX } = getEnv();
Expand Down Expand Up @@ -47,6 +47,65 @@ export default (app: Express) => {
res.json(response);
});

/**
* @swagger
* /auth/loginSSO:
* get:
* parameters:
* - in: cookie
* name: AUTH_SSO_TOKEN_NAME
* schema:
* type: string
* description: SSO token cookie that is named according to the gateway environment variable
* produces:
* - application/json
* responses:
* 200:
* description: AuthResponse
* summary: Login to initiate a session
* tags:
* - Auth
*/
app.get('/auth/loginSSO', loginLimiter, async (req, res) => {
const { AUTH_SSO_TOKEN_NAME } = getEnv();
const ssoToken = req.cookies[AUTH_SSO_TOKEN_NAME];
// TODO, switch based on AUTH_TYPE to call different SSO provider adapters
const { token, success, message } = await loginSSO(ssoToken);
const resp = {
token,
success,
message
};
res.json(resp);
});

/**
* @swagger
* /auth/validateSSO:
* get:
* parameters:
* - in: cookie
* name: AUTH_SSO_TOKEN_NAME
* schema:
* type: string
* description: SSO token cookie that is named according to the gateway environment variable
* produces:
* - application/json
* responses:
* 200:
* description: AuthResponse
* summary: Validates a user's SSO token against external auth providers
* tags:
* - Auth
*/
app.get('/auth/validateSSO', loginLimiter, async (req, res) => {
const { AUTH_SSO_TOKEN_NAME } = getEnv();
const ssoToken = req.cookies[AUTH_SSO_TOKEN_NAME];
// TODO, switch based on AUTH_TYPE to call different SSO provider adapters
const response = await validateSSOToken(ssoToken);
res.json(response);
});

/**
* @swagger
* /auth/session:
Expand Down
2 changes: 2 additions & 0 deletions src/packages/files/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export default (app: Express) => {
[deleted_date, id],
);

// @ts-ignore
if (rowCount > 0) {
logger.info(`DELETE /file: Marked file as deleted in the database: ${id}`);
} else {
Expand Down Expand Up @@ -133,6 +134,7 @@ export default (app: Express) => {
const [row] = rows;
const id = row ? row.id : null;

// @ts-ignore
if (rowCount > 0) {
logger.info(`POST /file: Added file to the database: ${id}`);
} else {
Expand Down

0 comments on commit abc0751

Please sign in to comment.