From 027a8f6757f18e031d3e4962e8d6352a66a2c78c Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 11:35:25 -0600 Subject: [PATCH 01/11] feat: initial implementation of Ping Federate --- README.md | 34 +++++++--- cspell.json | 1 + packages/api/.env.sample | 34 ++++++---- packages/api/src/api/auth/callback/index.ts | 10 ++- .../api/src/api/auth/callback/pingfed/get.ts | 54 +++++++++++++++ .../src/api/auth/callback/pingfed/index.ts | 6 ++ .../api/src/api/auth/callback/slack/get.ts | 12 ++-- packages/api/src/api/auth/get.ts | 22 +++--- .../authUrlFormatters/formatPingfedAuthUrl.ts | 18 +++++ .../authUrlFormatters/formatSlackAuthUrl.ts | 19 ++++++ .../api/auth/utils/authUrlFormatters/index.ts | 4 ++ .../api/auth/utils/authUrlFormatters/types.ts | 1 + .../{ => api/auth}/utils/authenticateUser.ts | 2 +- .../src/api/auth/utils/formatRedirectUri.ts | 25 +++++++ packages/api/src/env/auth/index.ts | 2 + packages/api/src/env/auth/pingfedAuth.env.ts | 10 +++ .../src/{env.ts => env/auth/slackAuth.env.ts} | 9 +-- packages/api/src/env/env.ts | 13 ++++ packages/api/src/env/index.ts | 13 ++++ .../api/src/slack/formatSlackRedirectUri.ts | 17 ----- .../tests/api/auth/callback/slack/get.test.ts | 4 +- packages/api/tests/api/auth/get.test.ts | 10 +-- .../auth}/utils/authenticateUser.test.ts | 8 +-- .../api/auth/utils/formatRedirectUri.test.ts | 36 ++++++++++ .../slack/formatSlackRedirectUri.test.ts | 36 ---------- packages/shared/src/config/auth.ts | 11 +++ packages/shared/src/config/index.ts | 1 + .../PingfedContent/PingfedContent.tsx | 35 ++++++++++ .../PingfedContent/index.ts | 1 + .../RedirectToAuthModal.tsx | 68 ++++--------------- .../SlackContent/SlackContent.tsx | 52 ++++++++++++++ .../RedirectToAuthModal/SlackContent/index.ts | 1 + 32 files changed, 405 insertions(+), 164 deletions(-) create mode 100644 packages/api/src/api/auth/callback/pingfed/get.ts create mode 100644 packages/api/src/api/auth/callback/pingfed/index.ts create mode 100644 packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts create mode 100644 packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts create mode 100644 packages/api/src/api/auth/utils/authUrlFormatters/index.ts create mode 100644 packages/api/src/api/auth/utils/authUrlFormatters/types.ts rename packages/api/src/{ => api/auth}/utils/authenticateUser.ts (96%) create mode 100644 packages/api/src/api/auth/utils/formatRedirectUri.ts create mode 100644 packages/api/src/env/auth/index.ts create mode 100644 packages/api/src/env/auth/pingfedAuth.env.ts rename packages/api/src/{env.ts => env/auth/slackAuth.env.ts} (60%) create mode 100644 packages/api/src/env/env.ts create mode 100644 packages/api/src/env/index.ts delete mode 100644 packages/api/src/slack/formatSlackRedirectUri.ts rename packages/api/tests/{ => api/auth}/utils/authenticateUser.test.ts (90%) create mode 100644 packages/api/tests/api/auth/utils/formatRedirectUri.test.ts delete mode 100644 packages/api/tests/slack/formatSlackRedirectUri.test.ts create mode 100644 packages/shared/src/config/auth.ts create mode 100644 packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/PingfedContent.tsx create mode 100644 packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/index.ts create mode 100644 packages/web/src/components/layout/RedirectToAuthModal/SlackContent/SlackContent.tsx create mode 100644 packages/web/src/components/layout/RedirectToAuthModal/SlackContent/index.ts diff --git a/README.md b/README.md index 9a0e963b0..d59ea2cf4 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ Hangar is a hackathon management platform that can help with everything from pro ## Table of Contents - [What is Hangar?](#what-is-hangar) - - [Features](#features) + - [Features](#features) - [Table of Contents](#table-of-contents) - [Using Hangar](#using-hangar) - - [Deployment](#deployment) - - [Authentication](#authentication) - - [Feature Utilization](#feature-utilization) - - [Customization](#customization) + - [Deployment](#deployment) + - [Authentication](#authentication) + - [Feature Utilization](#feature-utilization) + - [Customization](#customization) - [Development](#development) - [Prerequisites](#prerequisites) - [Setup](#setup) @@ -52,7 +52,10 @@ Hangar is a hackathon management platform that can help with everything from pro - [Updating Mikro-ORM](#updating-mikro-orm) - [Restoring a Production DB locally](#restoring-a-production-db-locally) - [Restarting Table Sequences](#restarting-table-sequences) - - [Slack](#slack) + - [SSO](#sso) + - [Ping Federate](#ping-federate) + - [Slack](#slack) + - [Starting the App](#starting-the-app) - [Containerization](#containerization) - [Building and Running Docker Locally](#building-and-running-docker-locally) - [Environment Variables](#environment-variables) @@ -71,7 +74,7 @@ Hangar is containerized via Docker; simply build the docker image and deploy to #### Authentication -Authentication uses OAuth via Slack but it can be easily modified to use a new callback from a different OAuth provider. See the [Slack](#slack) section below for full details on how to setup and configure your Slack app. +Authentication uses OAuth via Ping Federate OR Slack but it can be easily modified to use a new callback from a different OAuth provider. See the [Slack](#slack) or [Ping Federate](#ping-federate) section below for full details on how to setup and configure your Slack app. #### Feature Utilization @@ -210,9 +213,20 @@ In order to modify the content of the homepage and to make other modifications t - ### Slack + ### SSO - To configure Slack notifications for certain events, create a Slack app use the following manifest after selecting `From an app manifest` (NOTE: watch out for whitespace issues when copying): + #### Ping Federate + + Ping Federate is enabled by default for SSO. To configure it, simply add the following environment variables to your `.env.local`: + + - `PINGFED_CLIENT_ID`: Your client ID + - `PINGFED_CLIENT_SECRET`: Your client secret + - `PINGFED_AUTH_BASE_URL`: The URL which starts the authentication flow + - `PINGFED_TOKEN_BASE_URL`: The URL from which the token containing user data can be retrieved + + #### Slack + + To configure the app to use Slack for SSO Authentication, modify the `packages/shared/src/config/auth.ts` and set `slack` as the auth method. Next, create a Slack app using the following manifest after selecting `From an app manifest` (NOTE: watch out for whitespace issues when copying): ```yml display_information: @@ -248,7 +262,7 @@ In order to modify the content of the homepage and to make other modifications t For Slack OAuth to work, your bot needs to be configured with your redirect URL. Go to `OAuth & Permissions` if you need to update your Redirect URL (e.g., `[YOUR_NGROK_URL]/api/auth/callback/slack`). - Channel IDs can be obtained by right clicking a channel in the sidebar and removing the last path value from the URL. +### Starting the App 1. Start the development server diff --git a/cspell.json b/cspell.json index d59bc6478..a648b26ed 100644 --- a/cspell.json +++ b/cspell.json @@ -30,6 +30,7 @@ "mikro", "mrkdwn", "msapplication", + "pingfed", "seedrandom", "SSRF", "tablist", diff --git a/packages/api/.env.sample b/packages/api/.env.sample index a6aa92a89..b5046ab48 100644 --- a/packages/api/.env.sample +++ b/packages/api/.env.sample @@ -1,16 +1,14 @@ # cSpell:disable +# NOTE: All commented out variables below are optional but some are required when running docker locally + #### Generic options #### NODE_ENV=development PORT=3000 -# No ending slash -NEXT_PUBLIC_BASE_URL=localhost:3000 -NEXT_PUBLIC_SLACK_WORKSPACE_NAME=your-slack-workspace -# NEXT_PUBLIC_SLACK_INVITE_URL=https://join.slack.com/XXX SESSION_SECRET=XXXXX -# For images when testing locally -# NOTE: All commented out variables below are optional but some are required when running docker locally +# No ending slash +NEXT_PUBLIC_BASE_URL="http://localhost:3000" #### Database #### DATABASE_URL=postgresql://localhost:5432/hangar @@ -18,10 +16,22 @@ DB_LOGGING_ENABLED=true DISABLE_DATABASE_SSL=true # REQUIRED FOR PC DEVS AND DOCKER, macOS DEVS MAY NEED USER: # DATABASE_PASS= -# DATABASE_USER=aa0000000 +# DATABASE_USER= + +#### Ping Federate Auth #### +PINGFED_CLIENT_ID="XXXXXXXXXXXX" +PINGFED_CLIENT_SECRET="XXXXXXXX" +PINGFED_AUTH_BASE_URL="XXXXXXXX" +PINGFED_TOKEN_BASE_URL="XXXXXXX" + +#### Slack Workspace Invite Button #### +# Set if you'd like a "Join Slack" button in the UI +# NEXT_PUBLIC_SLACK_INVITE_URL=https://join.slack.com/XXX + +#### Slack Auth - See README #### +# NEXT_PUBLIC_SLACK_WORKSPACE_NAME=your-slack-workspace +# SLACK_BOT_TOKEN="xoxb-XXXXXXXX" +# SLACK_SIGNING_SECRET="XXXXX" +# SLACK_CLIENT_ID="XXXXX" +# SLACK_CLIENT_SECRET="XXXXXX" -#### Slack API #### -SLACK_BOT_TOKEN="xoxb-XXXXXXXX" -SLACK_SIGNING_SECRET="XXXXX" -SLACK_CLIENT_ID="XXXXX" -SLACK_CLIENT_SECRET="XXXXXX" diff --git a/packages/api/src/api/auth/callback/index.ts b/packages/api/src/api/auth/callback/index.ts index eafb2885e..3869e7324 100644 --- a/packages/api/src/api/auth/callback/index.ts +++ b/packages/api/src/api/auth/callback/index.ts @@ -1,6 +1,14 @@ +import { Config } from '@hangar/shared'; import { Router } from 'express'; import { slack } from './slack'; +import { pingfed } from './pingfed'; export const callback = Router(); -callback.use('/slack', slack); +// Register the appropriate callback route based on the auth method +const { method: authMethod } = Config.Auth; +if (authMethod === 'slack') { + callback.use('/slack', slack); +} else if (authMethod === 'pingfed') { + callback.use('/pingfed', pingfed); +} diff --git a/packages/api/src/api/auth/callback/pingfed/get.ts b/packages/api/src/api/auth/callback/pingfed/get.ts new file mode 100644 index 000000000..578d657d7 --- /dev/null +++ b/packages/api/src/api/auth/callback/pingfed/get.ts @@ -0,0 +1,54 @@ +import { Config } from '@hangar/shared'; +import axios from 'axios'; +import jwt_decode from 'jwt-decode'; +import { Request, Response } from 'express'; +import { pingfedAuth } from '../../../../env/auth'; +import { formatRedirectUri } from '../../utils/formatRedirectUri'; +import { authenticateUser } from '../../utils/authenticateUser'; + +type TokenResponse = { + access_token: string; +}; +type TokenValues = { + first_name: string; + last_name: string; + Email: string; +}; + +export const get = async (req: Request, res: Response) => { + const returnTo = req.query[Config.global.authReturnUriParamName] as string | undefined; + const { code } = req.query; + + if (!code) { + res.redirect(`/error?description=${encodeURIComponent('Bad Auth Callback')}`); + return; + } + + try { + const body = new URLSearchParams({ + client_id: pingfedAuth.clientId, + client_secret: pingfedAuth.clientSecret, + grant_type: 'authorization_code', + redirect_uri: formatRedirectUri({ returnTo }), + code: code as string, + }).toString(); + + const response = await axios.post(pingfedAuth.tokenBaseUrl, body, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': 'hangar-server', // Cannot be `axios/*` or PingFed will reject with 403 + }, + }); + const { access_token: token } = response.data as TokenResponse; + + const { + first_name: firstName, + last_name: lastName, + Email: email, + } = jwt_decode(token as string); + + void authenticateUser({ req, res, data: { firstName, lastName, email, returnTo } }); + } catch (error) { + res.redirect(`/error?description=${encodeURIComponent('Failed to get auth token')}`); + } +}; diff --git a/packages/api/src/api/auth/callback/pingfed/index.ts b/packages/api/src/api/auth/callback/pingfed/index.ts new file mode 100644 index 000000000..8d425a0b3 --- /dev/null +++ b/packages/api/src/api/auth/callback/pingfed/index.ts @@ -0,0 +1,6 @@ +import { Router } from 'express'; +import { get } from './get'; + +export const pingfed = Router(); + +pingfed.get('', get); diff --git a/packages/api/src/api/auth/callback/slack/get.ts b/packages/api/src/api/auth/callback/slack/get.ts index 0e164d24a..a9b1d36e7 100644 --- a/packages/api/src/api/auth/callback/slack/get.ts +++ b/packages/api/src/api/auth/callback/slack/get.ts @@ -2,10 +2,10 @@ import { WebClient } from '@slack/web-api'; import { Request, Response } from 'express'; import jwt_decode from 'jwt-decode'; import { Config } from '@hangar/shared'; -import { env } from '../../../../env'; -import { authenticateUser } from '../../../../utils/authenticateUser'; +import { authenticateUser } from '../../utils/authenticateUser'; import { logger } from '../../../../utils/logger'; -import { formatSlackRedirectUri } from '../../../../slack/formatSlackRedirectUri'; +import { slackAuth } from '../../../../env/auth'; +import { formatRedirectUri } from '../../utils/formatRedirectUri'; const codeQueryParam = 'code'; export type SlackTokenData = { @@ -16,17 +16,17 @@ export type SlackTokenData = { export const get = async (req: Request, res: Response) => { const myCode: string = req.query[codeQueryParam] as string; - const { slackClientID, slackClientSecret } = env; + const { slackClientID, slackClientSecret, slackBotToken } = slackAuth; const returnTo = req.query[Config.global.authReturnUriParamName] as string | undefined; - const client = new WebClient(env.slackBotToken); + const client = new WebClient(slackBotToken); try { const fullToken = await client.openid.connect.token({ code: myCode, client_id: slackClientID, client_secret: slackClientSecret, - redirect_uri: formatSlackRedirectUri({ returnTo }), + redirect_uri: formatRedirectUri({ returnTo }), }); const { diff --git a/packages/api/src/api/auth/get.ts b/packages/api/src/api/auth/get.ts index 4cbd5be8b..7d36a6e96 100644 --- a/packages/api/src/api/auth/get.ts +++ b/packages/api/src/api/auth/get.ts @@ -1,18 +1,22 @@ import { Request, Response } from 'express'; import { Config } from '@hangar/shared'; -import { env } from '../../env'; -import { formatSlackRedirectUri } from '../../slack/formatSlackRedirectUri'; +import { formatSlackAuthUrl, formatPingfedAuthUrl } from './utils/authUrlFormatters'; -const slackAuthBaseUrl: string = - 'https://slack.com/openid/connect/authorize?scope=openid%20email%20profile&response_type=code&'; +const { method: authMethod } = Config.Auth; +/** + * Express handler that redirects to the appropriate auth url based on the auth method + */ export const get = async (req: Request, res: Response) => { const { [Config.global.authReturnUriParamName]: returnTo } = req.query as Record; - const queryArgs = new URLSearchParams({ - redirect_uri: formatSlackRedirectUri({ returnTo }), - client_id: env.slackClientID, - }).toString(); + let authUrl: string; + if (authMethod === 'slack') { + authUrl = formatSlackAuthUrl({ returnTo }); + } else { + // Pingfed + authUrl = formatPingfedAuthUrl({ returnTo }); + } - res.redirect(`${slackAuthBaseUrl}${queryArgs}`); + res.redirect(authUrl); }; diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts b/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts new file mode 100644 index 000000000..1def365c1 --- /dev/null +++ b/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts @@ -0,0 +1,18 @@ +import { pingfedAuth } from '../../../../env/auth'; +import { formatRedirectUri } from '../formatRedirectUri'; +import { AuthUrlFormatter } from './types'; + +/** + * + * @returns {string} The URL to redirect to for the initial step of the auth flow + */ +export const formatPingfedAuthUrl: AuthUrlFormatter = ({ returnTo }) => { + const queryArgs = new URLSearchParams({ + redirect_uri: formatRedirectUri({ returnTo }), + client_id: pingfedAuth.clientId, + client_password: pingfedAuth.clientSecret, + response_type: 'code', + }).toString(); + + return `${pingfedAuth.authBaseUrl}?${queryArgs}`; +}; diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts new file mode 100644 index 000000000..7583fa8e6 --- /dev/null +++ b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts @@ -0,0 +1,19 @@ +import { slackAuth } from '../../../../env/auth'; +import { formatRedirectUri } from '../formatRedirectUri'; +import { AuthUrlFormatter } from './types'; + +/** + * + * @returns {string} The URL to redirect to for the initial step of the auth flow + */ +export const formatSlackAuthUrl: AuthUrlFormatter = ({ returnTo }) => { + const slackAuthBaseUrl: string = + 'https://slack.com/openid/connect/authorize?scope=openid%20email%20profile&response_type=code&'; + + const queryArgs = new URLSearchParams({ + redirect_uri: formatRedirectUri({ returnTo }), + client_id: slackAuth.slackClientID, + }).toString(); + + return `${slackAuthBaseUrl}${queryArgs}`; +}; diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/index.ts b/packages/api/src/api/auth/utils/authUrlFormatters/index.ts new file mode 100644 index 000000000..c29b4a92d --- /dev/null +++ b/packages/api/src/api/auth/utils/authUrlFormatters/index.ts @@ -0,0 +1,4 @@ +export * from './formatPingfedAuthUrl'; +export * from './formatSlackAuthUrl'; + +// This directory houses the formatters that create URLs for redirection that triggers the initial step of the auth flow diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/types.ts b/packages/api/src/api/auth/utils/authUrlFormatters/types.ts new file mode 100644 index 000000000..9f9b25c2e --- /dev/null +++ b/packages/api/src/api/auth/utils/authUrlFormatters/types.ts @@ -0,0 +1 @@ +export type AuthUrlFormatter = (args: { returnTo?: string }) => string; diff --git a/packages/api/src/utils/authenticateUser.ts b/packages/api/src/api/auth/utils/authenticateUser.ts similarity index 96% rename from packages/api/src/utils/authenticateUser.ts rename to packages/api/src/api/auth/utils/authenticateUser.ts index 391571e06..f3f40c32d 100644 --- a/packages/api/src/utils/authenticateUser.ts +++ b/packages/api/src/api/auth/utils/authenticateUser.ts @@ -1,6 +1,6 @@ import { User } from '@hangar/database'; import { Response, Request } from 'express'; -import { logger } from './logger'; +import { logger } from '../../../utils/logger'; export type OAuthUserData = { email: string; diff --git a/packages/api/src/api/auth/utils/formatRedirectUri.ts b/packages/api/src/api/auth/utils/formatRedirectUri.ts new file mode 100644 index 000000000..4fab0b435 --- /dev/null +++ b/packages/api/src/api/auth/utils/formatRedirectUri.ts @@ -0,0 +1,25 @@ +import { Config } from '@hangar/shared'; +import { env } from '../../../env'; + +type FormatSlackRedirectUriArgs = { + returnTo?: string; +}; + +const { method } = Config.Auth; + +/** + * A method to format a URL encoded redirect URI based on the configured auth method + * @param {string} args.returnTo the uri to return the user to post-auth + * @returns + */ +export const formatRedirectUri = ({ returnTo }: FormatSlackRedirectUriArgs = {}) => { + const params = new URLSearchParams(); + if (returnTo) { + params.append(Config.global.authReturnUriParamName, returnTo); + } + + const paramsString = params.toString(); + const returnToQuery = paramsString ? `?${paramsString}` : ''; + console.log(`${env.baseUrl ?? ''}/api/auth/callback/${method}/${returnToQuery}`); + return `${env.baseUrl ?? ''}/api/auth/callback/${method}/${returnToQuery}`; +}; diff --git a/packages/api/src/env/auth/index.ts b/packages/api/src/env/auth/index.ts new file mode 100644 index 000000000..7e5c81e1a --- /dev/null +++ b/packages/api/src/env/auth/index.ts @@ -0,0 +1,2 @@ +export * from './pingfedAuth.env'; +export * from './slackAuth.env'; diff --git a/packages/api/src/env/auth/pingfedAuth.env.ts b/packages/api/src/env/auth/pingfedAuth.env.ts new file mode 100644 index 000000000..ca82ca26d --- /dev/null +++ b/packages/api/src/env/auth/pingfedAuth.env.ts @@ -0,0 +1,10 @@ +import setEnv from '@americanairlines/simple-env'; + +export const pingfedAuth = setEnv({ + required: { + clientId: 'PINGFED_CLIENT_ID', + clientSecret: 'PINGFED_CLIENT_SECRET', + authBaseUrl: 'PINGFED_AUTH_BASE_URL', + tokenBaseUrl: 'PINGFED_TOKEN_BASE_URL', + }, +}); diff --git a/packages/api/src/env.ts b/packages/api/src/env/auth/slackAuth.env.ts similarity index 60% rename from packages/api/src/env.ts rename to packages/api/src/env/auth/slackAuth.env.ts index afcbfef2b..9a63098fc 100644 --- a/packages/api/src/env.ts +++ b/packages/api/src/env/auth/slackAuth.env.ts @@ -1,14 +1,7 @@ -import { config } from 'dotenv-flow'; import setEnv from '@americanairlines/simple-env'; -config(); - -export const env = setEnv({ +export const slackAuth = setEnv({ required: { - nodeEnv: 'NODE_ENV', - port: 'PORT', - baseUrl: 'NEXT_PUBLIC_BASE_URL', - sessionSecret: 'SESSION_SECRET', slackBotToken: 'SLACK_BOT_TOKEN', slackSigningSecret: 'SLACK_SIGNING_SECRET', slackClientID: 'SLACK_CLIENT_ID', diff --git a/packages/api/src/env/env.ts b/packages/api/src/env/env.ts new file mode 100644 index 000000000..380444a3b --- /dev/null +++ b/packages/api/src/env/env.ts @@ -0,0 +1,13 @@ +import setEnv from '@americanairlines/simple-env'; + +export const env = setEnv({ + required: { + nodeEnv: 'NODE_ENV', + port: 'PORT', + baseUrl: 'NEXT_PUBLIC_BASE_URL', + sessionSecret: 'SESSION_SECRET', + }, + optional: { + // Add optional env vars here + }, +}); diff --git a/packages/api/src/env/index.ts b/packages/api/src/env/index.ts new file mode 100644 index 000000000..a473c6631 --- /dev/null +++ b/packages/api/src/env/index.ts @@ -0,0 +1,13 @@ +import { config } from 'dotenv-flow'; + +config(); // Must be called before exports + +/** + * All core environment variables + */ +export * from './env'; + +/** + * All auth environment variables + */ +export * as Auth from './auth'; diff --git a/packages/api/src/slack/formatSlackRedirectUri.ts b/packages/api/src/slack/formatSlackRedirectUri.ts deleted file mode 100644 index 407e1059d..000000000 --- a/packages/api/src/slack/formatSlackRedirectUri.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Config } from '@hangar/shared'; -import { env } from '../env'; - -type FormatSlackRedirectUriArgs = { - returnTo?: string; -}; - -export const formatSlackRedirectUri = ({ returnTo }: FormatSlackRedirectUriArgs = {}) => { - const params = new URLSearchParams(); - if (returnTo) { - params.append(Config.global.authReturnUriParamName, returnTo); - } - - const paramsString = params.toString(); - const returnToQuery = paramsString ? `?${paramsString}` : ''; - return `${env.baseUrl ?? ''}/api/auth/callback/slack${returnToQuery}`; -}; diff --git a/packages/api/tests/api/auth/callback/slack/get.test.ts b/packages/api/tests/api/auth/callback/slack/get.test.ts index 8a1d087c0..73a36118d 100644 --- a/packages/api/tests/api/auth/callback/slack/get.test.ts +++ b/packages/api/tests/api/auth/callback/slack/get.test.ts @@ -3,10 +3,10 @@ import { Config } from '@hangar/shared'; import jwt_decode from 'jwt-decode'; import { SlackTokenData, get } from '../../../../../src/api/auth/callback/slack/get'; import { getMock } from '../../../../testUtils/getMock'; -import { authenticateUser } from '../../../../../src/utils/authenticateUser'; +import { authenticateUser } from '../../../../../src/api/auth/utils/authenticateUser'; import { createMockRequest } from '../../../../testUtils/expressHelpers/createMockRequest'; import { createMockResponse } from '../../../../testUtils/expressHelpers/createMockResponse'; -import { formatSlackRedirectUri } from '../../../../../src/slack/formatSlackRedirectUri'; +import { formatSlackRedirectUri } from '../../../../../src/api/auth/utils/formatRedirectUri'; jest.mock('@slack/web-api'); jest.mock('jwt-decode'); diff --git a/packages/api/tests/api/auth/get.test.ts b/packages/api/tests/api/auth/get.test.ts index b3f0af500..87a47402e 100644 --- a/packages/api/tests/api/auth/get.test.ts +++ b/packages/api/tests/api/auth/get.test.ts @@ -2,7 +2,7 @@ import { Config } from '@hangar/shared'; import { get } from '../../../src/api/auth/get'; import { createMockRequest } from '../../testUtils/expressHelpers/createMockRequest'; import { createMockResponse } from '../../testUtils/expressHelpers/createMockResponse'; -import { formatSlackRedirectUri } from '../../../src/slack/formatSlackRedirectUri'; +import { formatRedirectUri } from '../../../src/api/auth/utils/formatRedirectUri'; import { getMock } from '../../testUtils/getMock'; const slackAuthBaseUrl: string = @@ -10,13 +10,13 @@ const slackAuthBaseUrl: string = const returnTo = '/api/expoJudgingSession'; -jest.mock('../../../src/slack/formatSlackRedirectUri'); -const formatSlackRedirectUriMock = getMock(formatSlackRedirectUri); +jest.mock('../../../src/utils/auth/formatRedirectUri'); +const formatRedirectUriMock = getMock(formatRedirectUri); describe('auth SLACK', () => { it('redirects to correct url for happy path', async () => { const redirectUri = 'waffles'; - formatSlackRedirectUriMock.mockReturnValueOnce(redirectUri); + formatRedirectUriMock.mockReturnValueOnce(redirectUri); const fullLink = `${slackAuthBaseUrl}redirect_uri=${redirectUri}&client_id=undefined`; const mockReq = createMockRequest({ @@ -28,7 +28,7 @@ describe('auth SLACK', () => { await get(mockReq as any, mockRes as any); - expect(formatSlackRedirectUriMock).toBeCalledWith(expect.objectContaining({ returnTo })); + expect(formatRedirectUriMock).toBeCalledWith(expect.objectContaining({ returnTo })); expect(mockRes.redirect).toHaveBeenCalledTimes(1); expect(mockRes.redirect).toHaveBeenCalledWith(fullLink); }); diff --git a/packages/api/tests/utils/authenticateUser.test.ts b/packages/api/tests/api/auth/utils/authenticateUser.test.ts similarity index 90% rename from packages/api/tests/utils/authenticateUser.test.ts rename to packages/api/tests/api/auth/utils/authenticateUser.test.ts index 1eff4bbb3..26c2da039 100644 --- a/packages/api/tests/utils/authenticateUser.test.ts +++ b/packages/api/tests/api/auth/utils/authenticateUser.test.ts @@ -1,8 +1,8 @@ import { User } from '@hangar/database'; -import { authenticateUser, OAuthUserData } from '../../src/utils/authenticateUser'; -import { logger } from '../../src/utils/logger'; -import { createMockRequest } from '../testUtils/expressHelpers/createMockRequest'; -import { createMockResponse } from '../testUtils/expressHelpers/createMockResponse'; +import { authenticateUser, OAuthUserData } from '../../../../src/api/auth/utils/authenticateUser'; +import { logger } from '../../../../src/utils/logger'; +import { createMockRequest } from '../../../testUtils/expressHelpers/createMockRequest'; +import { createMockResponse } from '../../../testUtils/expressHelpers/createMockResponse'; const loggerErrorSpy = jest.spyOn(logger, 'error').mockImplementation(); diff --git a/packages/api/tests/api/auth/utils/formatRedirectUri.test.ts b/packages/api/tests/api/auth/utils/formatRedirectUri.test.ts new file mode 100644 index 000000000..b92ffee86 --- /dev/null +++ b/packages/api/tests/api/auth/utils/formatRedirectUri.test.ts @@ -0,0 +1,36 @@ +import { Config } from '@hangar/shared'; +import { mockEnv } from '../../../testUtils/mockEnv'; +import { formatRedirectUri } from '../../../../src/api/auth/utils/formatRedirectUri'; + +describe('formatRedirectUri', () => { + it('uses a returnTo if provided', () => { + const baseUrl = 'abc'; + mockEnv({ baseUrl }); + const returnTo = '/api/health'; + const redirectUri = formatRedirectUri({ returnTo }); + const encodedQueryValue = encodeURIComponent(returnTo); + expect(redirectUri).toEqual( + `${baseUrl}/api/auth/callback/${Config.Auth.method}/?${Config.global.authReturnUriParamName}=${encodedQueryValue}`, + ); + }); + + it('skips the returnTo if omitted', () => { + const baseUrl = 'abc'; + mockEnv({ baseUrl }); + const redirectUri = formatRedirectUri(); + expect(redirectUri).toEqual(`${baseUrl}/api/auth/callback/${Config.Auth.method}/`); + }); + + it('skips the returnTo if empty', () => { + const baseUrl = 'abc'; + mockEnv({ baseUrl }); + const redirectUri = formatRedirectUri({ returnTo: '' }); + expect(redirectUri).toEqual(`${baseUrl}/api/auth/callback/${Config.Auth.method}/`); + }); + + it('excludes a base url if it is not set', () => { + mockEnv({ baseUrl: undefined }); + const redirectUri = formatRedirectUri({ returnTo: '' }); + expect(redirectUri).toEqual(`/api/auth/callback/${Config.Auth.method}/`); + }); +}); diff --git a/packages/api/tests/slack/formatSlackRedirectUri.test.ts b/packages/api/tests/slack/formatSlackRedirectUri.test.ts deleted file mode 100644 index f7a95c93c..000000000 --- a/packages/api/tests/slack/formatSlackRedirectUri.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Config } from '@hangar/shared'; -import { formatSlackRedirectUri } from '../../src/slack/formatSlackRedirectUri'; -import { mockEnv } from '../testUtils/mockEnv'; - -describe('formatSlackRedirectUri', () => { - it('uses a returnTo if provided', () => { - const baseUrl = 'abc'; - mockEnv({ baseUrl }); - const returnTo = '/api/health'; - const redirectUri = formatSlackRedirectUri({ returnTo }); - const encodedQueryValue = encodeURIComponent(returnTo); - expect(redirectUri).toEqual( - `${baseUrl}/api/auth/callback/slack?${Config.global.authReturnUriParamName}=${encodedQueryValue}`, - ); - }); - - it('skips the returnTo if omitted', () => { - const baseUrl = 'abc'; - mockEnv({ baseUrl }); - const redirectUri = formatSlackRedirectUri(); - expect(redirectUri).toEqual(`${baseUrl}/api/auth/callback/slack`); - }); - - it('skips the returnTo if empty', () => { - const baseUrl = 'abc'; - mockEnv({ baseUrl }); - const redirectUri = formatSlackRedirectUri({ returnTo: '' }); - expect(redirectUri).toEqual(`${baseUrl}/api/auth/callback/slack`); - }); - - it('excludes a base url if it is not set', () => { - mockEnv({ baseUrl: undefined }); - const redirectUri = formatSlackRedirectUri({ returnTo: '' }); - expect(redirectUri).toEqual(`/api/auth/callback/slack`); - }); -}); diff --git a/packages/shared/src/config/auth.ts b/packages/shared/src/config/auth.ts new file mode 100644 index 000000000..8fe246b14 --- /dev/null +++ b/packages/shared/src/config/auth.ts @@ -0,0 +1,11 @@ +type AuthMethod = 'slack' | 'pingfed'; // To support new methods, add them here +type AuthData = { + method: AuthMethod; +}; + +/** + * Configuration for authentication throughout the app + */ +export const Auth: AuthData = { + method: 'slack', // Change this value to drive auth throughout the app +}; diff --git a/packages/shared/src/config/index.ts b/packages/shared/src/config/index.ts index b62ea006e..f70412452 100644 --- a/packages/shared/src/config/index.ts +++ b/packages/shared/src/config/index.ts @@ -1,3 +1,4 @@ +export * from './auth'; export * from './global'; export * from './homepage'; export * from './project'; diff --git a/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/PingfedContent.tsx b/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/PingfedContent.tsx new file mode 100644 index 000000000..82e54c48e --- /dev/null +++ b/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/PingfedContent.tsx @@ -0,0 +1,35 @@ +import { Flex, Heading, Button } from '@chakra-ui/react'; + +type PingfedContentProps = { + secondsRemaining?: number; + onContinue: () => void; +}; + +export const PingfedContent: React.FC = ({ secondsRemaining, onContinue }) => ( + + + {secondsRemaining !== undefined + ? `Redirecting to login in ${secondsRemaining} seconds...` + : 'Redirecting...'} + + + +); diff --git a/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/index.ts b/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/index.ts new file mode 100644 index 000000000..63da4bae5 --- /dev/null +++ b/packages/web/src/components/layout/RedirectToAuthModal/PingfedContent/index.ts @@ -0,0 +1 @@ +export * from './PingfedContent'; diff --git a/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx b/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx index 803bf82d2..d969a5db5 100644 --- a/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx +++ b/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx @@ -1,28 +1,15 @@ /* eslint-disable max-lines */ import React from 'react'; -import { - Button, - Code, - Flex, - Heading, - Modal, - ModalBody, - ModalCloseButton, - ModalContent, - ModalOverlay, - Text, - useClipboard, -} from '@chakra-ui/react'; -import { wait } from '@hangar/shared'; +import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalOverlay } from '@chakra-ui/react'; +import { Config, wait } from '@hangar/shared'; import { useRedirectToAuth } from './useRedirectToAuth'; -import { env } from '../../../env'; -import { JoinSlackButton } from '../../JoinSlackButton'; +import { SlackContent } from './SlackContent'; +import { PingfedContent } from './PingfedContent'; const countdownDurationSeconds = 15; export const RedirectToAuthModal: React.FC = () => { const { redirect } = useRedirectToAuth(); - const { onCopy } = useClipboard(env.slackWorkspaceName ?? ''); const { isOpen, closeModal } = useRedirectToAuth(); const [secondsRemaining, setSecondsRemaining] = React.useState(); @@ -53,49 +40,24 @@ export const RedirectToAuthModal: React.FC = () => { }; }, [isOpen, redirect]); + const onContinue = () => { + redirect(); + setSecondsRemaining(undefined); + }; + return ( - - - {secondsRemaining !== undefined - ? `Redirecting to login in ${secondsRemaining} seconds...` - : 'Redirecting...'} - - - The next screen will ask for a Slack Workspace Name - - - Workspace Name: - {env.slackWorkspaceName} - - + {Config.Auth.method === 'slack' && ( + + )} - - + {Config.Auth.method === 'pingfed' && ( + + )} diff --git a/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/SlackContent.tsx b/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/SlackContent.tsx new file mode 100644 index 000000000..e24257625 --- /dev/null +++ b/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/SlackContent.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Flex, Heading, Code, Button, Text, useClipboard } from '@chakra-ui/react'; +import { env } from '../../../../env'; +import { JoinSlackButton } from '../../../JoinSlackButton'; + +type SlackContentProps = { + secondsRemaining?: number; + onContinue: () => void; +}; + +export const SlackContent: React.FC = ({ secondsRemaining, onContinue }) => { + const { onCopy } = useClipboard(env.slackWorkspaceName ?? ''); + + return ( + + + {secondsRemaining !== undefined + ? `Redirecting to login in ${secondsRemaining} seconds...` + : 'Redirecting...'} + + + The next screen will ask for a Slack Workspace Name + + + Workspace Name: + {env.slackWorkspaceName} + + + + + + ); +}; diff --git a/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/index.ts b/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/index.ts new file mode 100644 index 000000000..dfbd310dc --- /dev/null +++ b/packages/web/src/components/layout/RedirectToAuthModal/SlackContent/index.ts @@ -0,0 +1 @@ +export * from './SlackContent'; From 4a5f009bee5629f8adfbe87ef3aa7e36e28afe5f Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 14:26:45 -0600 Subject: [PATCH 02/11] fixing tests --- .../src/api/auth/utils/formatRedirectUri.ts | 2 +- .../tests/api/auth/callback/slack/get.test.ts | 21 +++-- packages/api/tests/api/auth/get.test.ts | 80 ++++++++++++++----- 3 files changed, 77 insertions(+), 26 deletions(-) diff --git a/packages/api/src/api/auth/utils/formatRedirectUri.ts b/packages/api/src/api/auth/utils/formatRedirectUri.ts index 4fab0b435..f2b41da0e 100644 --- a/packages/api/src/api/auth/utils/formatRedirectUri.ts +++ b/packages/api/src/api/auth/utils/formatRedirectUri.ts @@ -20,6 +20,6 @@ export const formatRedirectUri = ({ returnTo }: FormatSlackRedirectUriArgs = {}) const paramsString = params.toString(); const returnToQuery = paramsString ? `?${paramsString}` : ''; - console.log(`${env.baseUrl ?? ''}/api/auth/callback/${method}/${returnToQuery}`); + return `${env.baseUrl ?? ''}/api/auth/callback/${method}/${returnToQuery}`; }; diff --git a/packages/api/tests/api/auth/callback/slack/get.test.ts b/packages/api/tests/api/auth/callback/slack/get.test.ts index 73a36118d..a9331f0c1 100644 --- a/packages/api/tests/api/auth/callback/slack/get.test.ts +++ b/packages/api/tests/api/auth/callback/slack/get.test.ts @@ -6,12 +6,20 @@ import { getMock } from '../../../../testUtils/getMock'; import { authenticateUser } from '../../../../../src/api/auth/utils/authenticateUser'; import { createMockRequest } from '../../../../testUtils/expressHelpers/createMockRequest'; import { createMockResponse } from '../../../../testUtils/expressHelpers/createMockResponse'; -import { formatSlackRedirectUri } from '../../../../../src/api/auth/utils/formatRedirectUri'; +import { formatRedirectUri } from '../../../../../src/api/auth/utils/formatRedirectUri'; +import { slackAuth } from '../../../../../src/env/auth'; jest.mock('@slack/web-api'); jest.mock('jwt-decode'); -jest.mock('../../../../../src/utils/authenticateUser'); -jest.mock('../../../../../src/slack/formatSlackRedirectUri'); +jest.mock('../../../../../src/api/auth/utils/authenticateUser'); +jest.mock('../../../../../src/api/auth/utils/formatRedirectUri'); +jest.mock('../../../../../src/env/auth'); + +(slackAuth as Partial) = { + slackClientID: 'mockClientID', + slackBotToken: 'mockBotToken', + slackClientSecret: 'mockClientSecret', +}; const mockToken = { ok: true, @@ -22,7 +30,7 @@ const mockToken = { const jwtDecodeMock = getMock(jwt_decode); const webClientSpy = jest.spyOn(Slack, 'WebClient'); const authenticateUserMock = getMock(authenticateUser); -const formatSlackRedirectUriMock = getMock(formatSlackRedirectUri); +const formatRedirectUriMock = getMock(formatRedirectUri); describe('Slack auth callback', () => { describe('handler', () => { @@ -41,13 +49,16 @@ describe('Slack auth callback', () => { query: { code: 'mockCode', [Config.global.authReturnUriParamName]: returnToMock }, }); const mockRedirectUri = 'pancakes'; - formatSlackRedirectUriMock.mockReturnValueOnce(mockRedirectUri); + formatRedirectUriMock.mockReturnValueOnce(mockRedirectUri); await get(mockReq as any, {} as any); + expect(webClientSpy).toBeCalledWith(slackAuth.slackBotToken); expect(mockTokenMethod).toHaveBeenCalledTimes(1); expect(mockTokenMethod).toHaveBeenCalledWith( expect.objectContaining({ + client_id: slackAuth.slackClientID, + client_secret: slackAuth.slackClientSecret, code: mockReq.query.code, redirect_uri: mockRedirectUri, }), diff --git a/packages/api/tests/api/auth/get.test.ts b/packages/api/tests/api/auth/get.test.ts index 87a47402e..a9d7498c6 100644 --- a/packages/api/tests/api/auth/get.test.ts +++ b/packages/api/tests/api/auth/get.test.ts @@ -1,35 +1,75 @@ import { Config } from '@hangar/shared'; -import { get } from '../../../src/api/auth/get'; import { createMockRequest } from '../../testUtils/expressHelpers/createMockRequest'; import { createMockResponse } from '../../testUtils/expressHelpers/createMockResponse'; import { formatRedirectUri } from '../../../src/api/auth/utils/formatRedirectUri'; import { getMock } from '../../testUtils/getMock'; - -const slackAuthBaseUrl: string = - 'https://slack.com/openid/connect/authorize?scope=openid%20email%20profile&response_type=code&'; +import { + formatPingfedAuthUrl, + formatSlackAuthUrl, +} from '../../../src/api/auth/utils/authUrlFormatters'; const returnTo = '/api/expoJudgingSession'; -jest.mock('../../../src/utils/auth/formatRedirectUri'); -const formatRedirectUriMock = getMock(formatRedirectUri); +jest.mock('@hangar/shared'); +jest.mock('../../../src/api/auth/utils/authUrlFormatters'); +jest.mock('../../../src/env/auth', () => ({ slackAuth: {}, pingfedAuth: {} })); +const formatSlackAuthUrlMock = getMock(formatSlackAuthUrl); +const formatPingfedAuthUrlMock = getMock(formatPingfedAuthUrl); + +describe('auth login redirect', () => { + describe('slack redirect', () => { + beforeEach(() => { + Config.Auth.method = 'slack'; + }); + + it('redirects to correct url for happy path', async () => { + await jest.isolateModulesAsync(async () => { + const { get } = await import('../../../src/api/auth/get'); + const redirectUri = 'waffles'; + formatSlackAuthUrlMock.mockReturnValueOnce(redirectUri); + + const mockReq = createMockRequest({ + query: { + [Config.global.authReturnUriParamName]: returnTo, + }, + }); + const mockRes = createMockResponse(); -describe('auth SLACK', () => { - it('redirects to correct url for happy path', async () => { - const redirectUri = 'waffles'; - formatRedirectUriMock.mockReturnValueOnce(redirectUri); - const fullLink = `${slackAuthBaseUrl}redirect_uri=${redirectUri}&client_id=undefined`; + await get(mockReq as any, mockRes as any); - const mockReq = createMockRequest({ - query: { - [Config.global.authReturnUriParamName]: returnTo, - }, + expect(formatSlackAuthUrlMock).toBeCalledTimes(1); + expect(formatSlackAuthUrlMock).toBeCalledWith(expect.objectContaining({ returnTo })); + expect(mockRes.redirect).toHaveBeenCalledTimes(1); + expect(mockRes.redirect).toHaveBeenCalledWith(redirectUri); + }); }); - const mockRes = createMockResponse(); + }); - await get(mockReq as any, mockRes as any); + describe('pingfed redirect', () => { + beforeEach(() => { + Config.Auth.method = 'pingfed'; + }); + + it('redirects to correct url for happy path', async () => { + await jest.isolateModulesAsync(async () => { + const { get } = await import('../../../src/api/auth/get'); + const redirectUri = 'pancakes'; + formatPingfedAuthUrlMock.mockReturnValueOnce(redirectUri); + + const mockReq = createMockRequest({ + query: { + [Config.global.authReturnUriParamName]: returnTo, + }, + }); + const mockRes = createMockResponse(); - expect(formatRedirectUriMock).toBeCalledWith(expect.objectContaining({ returnTo })); - expect(mockRes.redirect).toHaveBeenCalledTimes(1); - expect(mockRes.redirect).toHaveBeenCalledWith(fullLink); + await get(mockReq as any, mockRes as any); + + expect(formatPingfedAuthUrlMock).toBeCalledTimes(1); + expect(formatPingfedAuthUrlMock).toBeCalledWith(expect.objectContaining({ returnTo })); + expect(mockRes.redirect).toHaveBeenCalledTimes(1); + expect(mockRes.redirect).toHaveBeenCalledWith(redirectUri); + }); + }); }); }); From b1cf16cf3b4569f2bd1a0f5d971706e874bb90f7 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:43:07 -0600 Subject: [PATCH 03/11] adding 100% test coverage --- .../authUrlFormatters/formatPingfedAuthUrl.ts | 2 +- .../authUrlFormatters/formatSlackAuthUrl.ts | 6 +- packages/api/src/env/auth/index.ts | 4 +- .../{pingfedAuth.env.ts => pingfedAuth.ts} | 0 .../auth/{slackAuth.env.ts => slackAuth.ts} | 0 .../api/tests/api/auth/callback/index.test.ts | 43 ++++++++++++ .../api/auth/callback/pingfed/get.test.ts | 69 +++++++++++++++++++ .../formatPingfedAuthUrl.test.ts | 31 +++++++++ .../formatSlackAuthUrl.test.ts | 32 +++++++++ packages/api/tests/api/auth/get.test.ts | 1 - 10 files changed, 181 insertions(+), 7 deletions(-) rename packages/api/src/env/auth/{pingfedAuth.env.ts => pingfedAuth.ts} (100%) rename packages/api/src/env/auth/{slackAuth.env.ts => slackAuth.ts} (100%) create mode 100644 packages/api/tests/api/auth/callback/index.test.ts create mode 100644 packages/api/tests/api/auth/callback/pingfed/get.test.ts create mode 100644 packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatPingfedAuthUrl.test.ts create mode 100644 packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts b/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts index 1def365c1..bae665a58 100644 --- a/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts +++ b/packages/api/src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl.ts @@ -4,7 +4,7 @@ import { AuthUrlFormatter } from './types'; /** * - * @returns {string} The URL to redirect to for the initial step of the auth flow + * @returns The URL to redirect to for the initial step of the auth flow */ export const formatPingfedAuthUrl: AuthUrlFormatter = ({ returnTo }) => { const queryArgs = new URLSearchParams({ diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts index 7583fa8e6..273499232 100644 --- a/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts +++ b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts @@ -2,14 +2,14 @@ import { slackAuth } from '../../../../env/auth'; import { formatRedirectUri } from '../formatRedirectUri'; import { AuthUrlFormatter } from './types'; +export const slackAuthBaseUrl: string = + 'https://slack.com/openid/connect/authorize?scope=openid%20email%20profile&response_type=code&'; + /** * * @returns {string} The URL to redirect to for the initial step of the auth flow */ export const formatSlackAuthUrl: AuthUrlFormatter = ({ returnTo }) => { - const slackAuthBaseUrl: string = - 'https://slack.com/openid/connect/authorize?scope=openid%20email%20profile&response_type=code&'; - const queryArgs = new URLSearchParams({ redirect_uri: formatRedirectUri({ returnTo }), client_id: slackAuth.slackClientID, diff --git a/packages/api/src/env/auth/index.ts b/packages/api/src/env/auth/index.ts index 7e5c81e1a..e8477b251 100644 --- a/packages/api/src/env/auth/index.ts +++ b/packages/api/src/env/auth/index.ts @@ -1,2 +1,2 @@ -export * from './pingfedAuth.env'; -export * from './slackAuth.env'; +export * from './pingfedAuth'; +export * from './slackAuth'; diff --git a/packages/api/src/env/auth/pingfedAuth.env.ts b/packages/api/src/env/auth/pingfedAuth.ts similarity index 100% rename from packages/api/src/env/auth/pingfedAuth.env.ts rename to packages/api/src/env/auth/pingfedAuth.ts diff --git a/packages/api/src/env/auth/slackAuth.env.ts b/packages/api/src/env/auth/slackAuth.ts similarity index 100% rename from packages/api/src/env/auth/slackAuth.env.ts rename to packages/api/src/env/auth/slackAuth.ts diff --git a/packages/api/tests/api/auth/callback/index.test.ts b/packages/api/tests/api/auth/callback/index.test.ts new file mode 100644 index 000000000..464fdd38a --- /dev/null +++ b/packages/api/tests/api/auth/callback/index.test.ts @@ -0,0 +1,43 @@ +import { Config } from '@hangar/shared'; +import { Router } from 'express'; +import { slack } from '../../../../src/api/auth/callback/slack'; +import { pingfed } from '../../../../src/api/auth/callback/pingfed'; + +jest.mock('../../../../src/api/auth/callback/slack', () => ({})); // TODO: Investigate why this requires a blank factory +jest.mock('../../../../src/api/auth/callback/pingfed', () => ({})); +jest.mock('@hangar/shared'); +jest.mock('express'); + +describe('callback router registrations', () => { + describe('slack router', () => { + beforeEach(() => { + Config.Auth.method = 'slack'; + }); + + it('registers the slack callback router', async () => { + const mockRouter = { use: jest.fn() }; + Router.prototype.constructor.mockReturnValueOnce(mockRouter); + + await jest.isolateModulesAsync(async () => { + await import('../../../../src/api/auth/callback'); + expect(mockRouter.use).toHaveBeenCalledWith('/slack', slack); + }); + }); + }); + + describe('pingfed router', () => { + beforeEach(() => { + Config.Auth.method = 'pingfed'; + }); + + it('registers the pingfed callback router', async () => { + const mockRouter = { use: jest.fn() }; + Router.prototype.constructor.mockReturnValueOnce(mockRouter); + + await jest.isolateModulesAsync(async () => { + await import('../../../../src/api/auth/callback'); + expect(mockRouter.use).toHaveBeenCalledWith('/pingfed', pingfed); + }); + }); + }); +}); diff --git a/packages/api/tests/api/auth/callback/pingfed/get.test.ts b/packages/api/tests/api/auth/callback/pingfed/get.test.ts new file mode 100644 index 000000000..a4def33ff --- /dev/null +++ b/packages/api/tests/api/auth/callback/pingfed/get.test.ts @@ -0,0 +1,69 @@ +import axios from 'axios'; +import jwt_decode from 'jwt-decode'; +import { get } from '../../../../../src/api/auth/callback/pingfed/get'; +import { createMockRequest } from '../../../../testUtils/expressHelpers/createMockRequest'; +import { createMockResponse } from '../../../../testUtils/expressHelpers/createMockResponse'; +import { getMock } from '../../../../testUtils/getMock'; +import { authenticateUser } from '../../../../../src/api/auth/utils/authenticateUser'; + +jest.mock('axios', () => ({ + __esModule: true, + default: { post: jest.fn() }, +})); +jest.mock('jwt-decode'); +jest.mock('../../../../../src/api/auth/utils/authenticateUser'); +const jwtDecodeMock = getMock(jwt_decode); + +describe('Pingfed auth callback', () => { + it('parses the code correctly, fetches a token, and decodes it into user properties', async () => { + const mockCode = 'mockCode'; + const mockReq = createMockRequest({ + query: { code: mockCode }, + }); + const mockRes = createMockResponse(); + + const mockToken = 'mockToken'; + (axios.post as jest.Mock).mockResolvedValueOnce({ data: { access_token: mockToken } }); + const mockTokenValues = { + first_name: 'mockFirstName', + last_name: 'mockLastName', + Email: 'mockEmail', + }; + jwtDecodeMock.mockReturnValueOnce(mockTokenValues); + + await get(mockReq as any, mockRes as any); + + expect(jwtDecodeMock).toHaveBeenCalledWith(mockToken); + expect(authenticateUser).toHaveBeenCalledWith({ + req: mockReq, + res: mockRes, + data: expect.objectContaining({ + firstName: mockTokenValues.first_name, + lastName: mockTokenValues.last_name, + email: mockTokenValues.Email, + }), + }); + }); + + it('redirects to an error page if a code is not present', async () => { + const mockReq = createMockRequest(); + const mockRes = createMockResponse(); + + await get(mockReq as any, mockRes as any); + + expect(mockRes.redirect).toBeCalledWith(`/error?description=Bad%20Auth%20Callback`); + }); + + it('redirects to an error page if the token could not be fetched', async () => { + const mockReq = createMockRequest({ + query: { code: 'mockCode' }, + }); + const mockRes = createMockResponse(); + + (axios.post as jest.Mock).mockRejectedValueOnce(new Error('mockError')); + + await get(mockReq as any, mockRes as any); + + expect(mockRes.redirect).toBeCalledWith(`/error?description=Failed%20to%20get%20auth%20token`); + }); +}); diff --git a/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatPingfedAuthUrl.test.ts b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatPingfedAuthUrl.test.ts new file mode 100644 index 000000000..67f65cb1e --- /dev/null +++ b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatPingfedAuthUrl.test.ts @@ -0,0 +1,31 @@ +import { formatPingfedAuthUrl } from '../../../../../../../src/api/auth/utils/authUrlFormatters/formatPingfedAuthUrl'; +import { formatRedirectUri } from '../../../../../../../src/api/auth/utils/formatRedirectUri'; +import { pingfedAuth } from '../../../../../../../src/env/auth/pingfedAuth'; +import { getMock } from '../../../../../../testUtils/getMock'; + +jest.mock('../../../../../../../src/env/auth/pingfedAuth'); +jest.mock('../../../../../../../src/api/auth/utils/formatRedirectUri'); +const formatRedirectUriMock = getMock(formatRedirectUri); + +(pingfedAuth as Partial) = { + authBaseUrl: 'https://mock.com', + clientId: 'mockClientId', + clientSecret: 'mockClientSecret', +}; + +describe('formatPingfedAuthUrl', () => { + it('formats the pingfed auth url correctly', () => { + const returnTo = '/api/health'; + const mockRedirectUri = 'mockRedirectUri'; + formatRedirectUriMock.mockReturnValueOnce(mockRedirectUri); + + const redirectUri = formatPingfedAuthUrl({ returnTo }); + + expect(formatRedirectUriMock).toBeCalledTimes(1); + expect(formatRedirectUriMock).toBeCalledWith({ returnTo }); + + expect(redirectUri).toEqual( + `${pingfedAuth.authBaseUrl}?redirect_uri=${mockRedirectUri}&client_id=${pingfedAuth.clientId}&client_password=${pingfedAuth.clientSecret}&response_type=code`, + ); + }); +}); diff --git a/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts new file mode 100644 index 000000000..0747d6623 --- /dev/null +++ b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts @@ -0,0 +1,32 @@ +import { + formatSlackAuthUrl, + slackAuthBaseUrl, +} from '../../../../../../../src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl'; +import { formatRedirectUri } from '../../../../../../../src/api/auth/utils/formatRedirectUri'; +import { slackAuth } from '../../../../../../../src/env/auth/slackAuth'; +import { getMock } from '../../../../../../testUtils/getMock'; + +jest.mock('../../../../../../../src/env/auth/slackAuth'); +jest.mock('../../../../../../../src/api/auth/utils/formatRedirectUri'); +const formatRedirectUriMock = getMock(formatRedirectUri); + +(slackAuth as Partial) = { + slackClientID: 'mockClientId', +}; + +describe('formatSlackAuthUrl', () => { + it('formats the slack auth url correctly', () => { + const returnTo = '/api/health'; + const mockRedirectUri = 'mockRedirectUri'; + formatRedirectUriMock.mockReturnValueOnce(mockRedirectUri); + + const redirectUri = formatSlackAuthUrl({ returnTo }); + + expect(formatRedirectUriMock).toBeCalledTimes(1); + expect(formatRedirectUriMock).toBeCalledWith({ returnTo }); + + expect(redirectUri).toEqual( + `${slackAuthBaseUrl}redirect_uri=${mockRedirectUri}&client_id=${slackAuth.slackClientID}`, + ); + }); +}); diff --git a/packages/api/tests/api/auth/get.test.ts b/packages/api/tests/api/auth/get.test.ts index a9d7498c6..d3087783d 100644 --- a/packages/api/tests/api/auth/get.test.ts +++ b/packages/api/tests/api/auth/get.test.ts @@ -1,7 +1,6 @@ import { Config } from '@hangar/shared'; import { createMockRequest } from '../../testUtils/expressHelpers/createMockRequest'; import { createMockResponse } from '../../testUtils/expressHelpers/createMockResponse'; -import { formatRedirectUri } from '../../../src/api/auth/utils/formatRedirectUri'; import { getMock } from '../../testUtils/getMock'; import { formatPingfedAuthUrl, From 9927ceb537d12e6de82591f0e8d50675b52355b4 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:43:48 -0600 Subject: [PATCH 04/11] cleanup --- packages/api/tests/api/auth/callback/index.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/api/tests/api/auth/callback/index.test.ts b/packages/api/tests/api/auth/callback/index.test.ts index 464fdd38a..c62f2d65d 100644 --- a/packages/api/tests/api/auth/callback/index.test.ts +++ b/packages/api/tests/api/auth/callback/index.test.ts @@ -10,11 +10,8 @@ jest.mock('express'); describe('callback router registrations', () => { describe('slack router', () => { - beforeEach(() => { - Config.Auth.method = 'slack'; - }); - it('registers the slack callback router', async () => { + Config.Auth.method = 'slack'; const mockRouter = { use: jest.fn() }; Router.prototype.constructor.mockReturnValueOnce(mockRouter); @@ -26,11 +23,8 @@ describe('callback router registrations', () => { }); describe('pingfed router', () => { - beforeEach(() => { - Config.Auth.method = 'pingfed'; - }); - it('registers the pingfed callback router', async () => { + Config.Auth.method = 'pingfed'; const mockRouter = { use: jest.fn() }; Router.prototype.constructor.mockReturnValueOnce(mockRouter); From b35c37a958fddf8707792ba18638d5dccf830f23 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:45:58 -0600 Subject: [PATCH 05/11] prettier --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d59ea2cf4..bd2e2dc8c 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ Hangar is a hackathon management platform that can help with everything from pro ## Table of Contents - [What is Hangar?](#what-is-hangar) - - [Features](#features) + - [Features](#features) - [Table of Contents](#table-of-contents) - [Using Hangar](#using-hangar) - - [Deployment](#deployment) - - [Authentication](#authentication) - - [Feature Utilization](#feature-utilization) - - [Customization](#customization) + - [Deployment](#deployment) + - [Authentication](#authentication) + - [Feature Utilization](#feature-utilization) + - [Customization](#customization) - [Development](#development) - [Prerequisites](#prerequisites) - [Setup](#setup) From 8ce01208ea8eaf1308b909ea5d912d34ca317674 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:47:36 -0600 Subject: [PATCH 06/11] Fixing a README typo --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd2e2dc8c..fe6fd10cf 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ Hangar is a hackathon management platform that can help with everything from pro ## Table of Contents - [What is Hangar?](#what-is-hangar) - - [Features](#features) + - [Features](#features) - [Table of Contents](#table-of-contents) - [Using Hangar](#using-hangar) - - [Deployment](#deployment) - - [Authentication](#authentication) - - [Feature Utilization](#feature-utilization) - - [Customization](#customization) + - [Deployment](#deployment) + - [Authentication](#authentication) + - [Feature Utilization](#feature-utilization) + - [Customization](#customization) - [Development](#development) - [Prerequisites](#prerequisites) - [Setup](#setup) @@ -74,7 +74,7 @@ Hangar is containerized via Docker; simply build the docker image and deploy to #### Authentication -Authentication uses OAuth via Ping Federate OR Slack but it can be easily modified to use a new callback from a different OAuth provider. See the [Slack](#slack) or [Ping Federate](#ping-federate) section below for full details on how to setup and configure your Slack app. +Authentication uses OAuth via Ping Federate OR Slack but it can be easily modified to use a new callback from a different OAuth provider. See the [Ping Federate](#ping-federate) or [Slack](#slack) below for full details on how to setup and configure your SSO solution. #### Feature Utilization From 3753b4d3f48757fd7ab95a7745ab2a68530f8284 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:48:47 -0600 Subject: [PATCH 07/11] changing default to be pingfed --- packages/shared/src/config/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/config/auth.ts b/packages/shared/src/config/auth.ts index 8fe246b14..724789755 100644 --- a/packages/shared/src/config/auth.ts +++ b/packages/shared/src/config/auth.ts @@ -7,5 +7,5 @@ type AuthData = { * Configuration for authentication throughout the app */ export const Auth: AuthData = { - method: 'slack', // Change this value to drive auth throughout the app + method: 'pingfed', // Change this value to drive auth throughout the app }; From 5b65914d4258db07a63a6bc516a445bd8c5e40bb Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:50:18 -0600 Subject: [PATCH 08/11] changing redirect timer to be shorter for pingfed --- .../layout/RedirectToAuthModal/RedirectToAuthModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx b/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx index d969a5db5..8e24864a5 100644 --- a/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx +++ b/packages/web/src/components/layout/RedirectToAuthModal/RedirectToAuthModal.tsx @@ -6,7 +6,7 @@ import { useRedirectToAuth } from './useRedirectToAuth'; import { SlackContent } from './SlackContent'; import { PingfedContent } from './PingfedContent'; -const countdownDurationSeconds = 15; +const countdownDurationSeconds = Config.Auth.method === 'slack' ? 15 : 5; export const RedirectToAuthModal: React.FC = () => { const { redirect } = useRedirectToAuth(); From cadcbebb8b8ae1d254417126ff803561b012767b Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Tue, 9 Jan 2024 16:57:58 -0600 Subject: [PATCH 09/11] prettier --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe6fd10cf..a574dc7a2 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ Hangar is a hackathon management platform that can help with everything from pro ## Table of Contents - [What is Hangar?](#what-is-hangar) - - [Features](#features) + - [Features](#features) - [Table of Contents](#table-of-contents) - [Using Hangar](#using-hangar) - - [Deployment](#deployment) - - [Authentication](#authentication) - - [Feature Utilization](#feature-utilization) - - [Customization](#customization) + - [Deployment](#deployment) + - [Authentication](#authentication) + - [Feature Utilization](#feature-utilization) + - [Customization](#customization) - [Development](#development) - [Prerequisites](#prerequisites) - [Setup](#setup) From d674c6fa9c85de203b13b36e0f12eb4c0ef88898 Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Wed, 10 Jan 2024 09:52:04 -0600 Subject: [PATCH 10/11] refactoring slack auth variable names --- packages/api/src/api/auth/callback/slack/get.ts | 6 +++++- .../utils/authUrlFormatters/formatSlackAuthUrl.ts | 2 +- packages/api/src/env/auth/slackAuth.ts | 11 ++++------- packages/api/src/env/index.ts | 5 ----- .../api/tests/api/auth/callback/slack/get.test.ts | 12 ++++++------ .../authUrlFormatters/formatSlackAuthUrl.test.ts | 4 ++-- 6 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/api/src/api/auth/callback/slack/get.ts b/packages/api/src/api/auth/callback/slack/get.ts index a9b1d36e7..e828d2ce8 100644 --- a/packages/api/src/api/auth/callback/slack/get.ts +++ b/packages/api/src/api/auth/callback/slack/get.ts @@ -16,7 +16,11 @@ export type SlackTokenData = { export const get = async (req: Request, res: Response) => { const myCode: string = req.query[codeQueryParam] as string; - const { slackClientID, slackClientSecret, slackBotToken } = slackAuth; + const { + clientId: slackClientID, + clientSecret: slackClientSecret, + botToken: slackBotToken, + } = slackAuth; const returnTo = req.query[Config.global.authReturnUriParamName] as string | undefined; diff --git a/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts index 273499232..ec522794e 100644 --- a/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts +++ b/packages/api/src/api/auth/utils/authUrlFormatters/formatSlackAuthUrl.ts @@ -12,7 +12,7 @@ export const slackAuthBaseUrl: string = export const formatSlackAuthUrl: AuthUrlFormatter = ({ returnTo }) => { const queryArgs = new URLSearchParams({ redirect_uri: formatRedirectUri({ returnTo }), - client_id: slackAuth.slackClientID, + client_id: slackAuth.clientId, }).toString(); return `${slackAuthBaseUrl}${queryArgs}`; diff --git a/packages/api/src/env/auth/slackAuth.ts b/packages/api/src/env/auth/slackAuth.ts index 9a63098fc..354639f99 100644 --- a/packages/api/src/env/auth/slackAuth.ts +++ b/packages/api/src/env/auth/slackAuth.ts @@ -2,12 +2,9 @@ import setEnv from '@americanairlines/simple-env'; export const slackAuth = setEnv({ required: { - slackBotToken: 'SLACK_BOT_TOKEN', - slackSigningSecret: 'SLACK_SIGNING_SECRET', - slackClientID: 'SLACK_CLIENT_ID', - slackClientSecret: 'SLACK_CLIENT_SECRET', - }, - optional: { - slackLogLevel: 'SLACK_LOG_LEVEL', + botToken: 'SLACK_BOT_TOKEN', + signingSecret: 'SLACK_SIGNING_SECRET', + clientId: 'SLACK_CLIENT_ID', + clientSecret: 'SLACK_CLIENT_SECRET', }, }); diff --git a/packages/api/src/env/index.ts b/packages/api/src/env/index.ts index a473c6631..af7962e45 100644 --- a/packages/api/src/env/index.ts +++ b/packages/api/src/env/index.ts @@ -6,8 +6,3 @@ config(); // Must be called before exports * All core environment variables */ export * from './env'; - -/** - * All auth environment variables - */ -export * as Auth from './auth'; diff --git a/packages/api/tests/api/auth/callback/slack/get.test.ts b/packages/api/tests/api/auth/callback/slack/get.test.ts index a9331f0c1..cc5a41749 100644 --- a/packages/api/tests/api/auth/callback/slack/get.test.ts +++ b/packages/api/tests/api/auth/callback/slack/get.test.ts @@ -16,9 +16,9 @@ jest.mock('../../../../../src/api/auth/utils/formatRedirectUri'); jest.mock('../../../../../src/env/auth'); (slackAuth as Partial) = { - slackClientID: 'mockClientID', - slackBotToken: 'mockBotToken', - slackClientSecret: 'mockClientSecret', + clientId: 'mockClientID', + botToken: 'mockBotToken', + clientSecret: 'mockClientSecret', }; const mockToken = { @@ -53,12 +53,12 @@ describe('Slack auth callback', () => { await get(mockReq as any, {} as any); - expect(webClientSpy).toBeCalledWith(slackAuth.slackBotToken); + expect(webClientSpy).toBeCalledWith(slackAuth.botToken); expect(mockTokenMethod).toHaveBeenCalledTimes(1); expect(mockTokenMethod).toHaveBeenCalledWith( expect.objectContaining({ - client_id: slackAuth.slackClientID, - client_secret: slackAuth.slackClientSecret, + client_id: slackAuth.clientId, + client_secret: slackAuth.clientSecret, code: mockReq.query.code, redirect_uri: mockRedirectUri, }), diff --git a/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts index 0747d6623..02d2a5a3e 100644 --- a/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts +++ b/packages/api/tests/api/auth/callback/slack/utils/authUrlFormatters/formatSlackAuthUrl.test.ts @@ -11,7 +11,7 @@ jest.mock('../../../../../../../src/api/auth/utils/formatRedirectUri'); const formatRedirectUriMock = getMock(formatRedirectUri); (slackAuth as Partial) = { - slackClientID: 'mockClientId', + clientId: 'mockClientId', }; describe('formatSlackAuthUrl', () => { @@ -26,7 +26,7 @@ describe('formatSlackAuthUrl', () => { expect(formatRedirectUriMock).toBeCalledWith({ returnTo }); expect(redirectUri).toEqual( - `${slackAuthBaseUrl}redirect_uri=${mockRedirectUri}&client_id=${slackAuth.slackClientID}`, + `${slackAuthBaseUrl}redirect_uri=${mockRedirectUri}&client_id=${slackAuth.clientId}`, ); }); }); From 0b84165f90f4e9b70cc2c48ffc5764f05347154c Mon Sep 17 00:00:00 2001 From: Spencer Kaiser Date: Wed, 10 Jan 2024 09:52:50 -0600 Subject: [PATCH 11/11] cleanup --- packages/api/src/api/auth/callback/slack/get.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/api/src/api/auth/callback/slack/get.ts b/packages/api/src/api/auth/callback/slack/get.ts index e828d2ce8..8e5bec70a 100644 --- a/packages/api/src/api/auth/callback/slack/get.ts +++ b/packages/api/src/api/auth/callback/slack/get.ts @@ -16,20 +16,16 @@ export type SlackTokenData = { export const get = async (req: Request, res: Response) => { const myCode: string = req.query[codeQueryParam] as string; - const { - clientId: slackClientID, - clientSecret: slackClientSecret, - botToken: slackBotToken, - } = slackAuth; + const { clientId, clientSecret, botToken } = slackAuth; const returnTo = req.query[Config.global.authReturnUriParamName] as string | undefined; - const client = new WebClient(slackBotToken); + const client = new WebClient(botToken); try { const fullToken = await client.openid.connect.token({ code: myCode, - client_id: slackClientID, - client_secret: slackClientSecret, + client_id: clientId, + client_secret: clientSecret, redirect_uri: formatRedirectUri({ returnTo }), });