diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1017ca24384..b715ada409b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "8.12.2" + ".": "8.12.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8dea63a2d..248faeef57b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [8.12.3](https://github.com/ParabolInc/parabol/compare/v8.12.2...v8.12.3) (2024-12-13) + + +### Fixed + +* Refresh the SAML request URL for each login attempt ([#10593](https://github.com/ParabolInc/parabol/issues/10593)) ([22d89e5](https://github.com/ParabolInc/parabol/commit/22d89e50a5d97ff1ed8ccf8596e42aa066ad00de)) + ## [8.12.2](https://github.com/ParabolInc/parabol/compare/v8.12.1...v8.12.2) (2024-12-12) diff --git a/package.json b/package.json index e35f44a9055..cb709babb39 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "8.12.2", + "version": "8.12.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 72c943f1101..b0f51a473c8 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "8.12.2", + "version": "8.12.3", "description": "A cron job scheduler", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/chronos#readme", @@ -25,6 +25,6 @@ }, "dependencies": { "cron": "^2.3.1", - "parabol-server": "8.12.2" + "parabol-server": "8.12.3" } } diff --git a/packages/client/Atmosphere.ts b/packages/client/Atmosphere.ts index 6d34782ae72..1e1f1c5e399 100644 --- a/packages/client/Atmosphere.ts +++ b/packages/client/Atmosphere.ts @@ -12,6 +12,7 @@ import { ConcreteRequest, Environment, FetchFunction, + FetchQueryFetchPolicy, GraphQLResponse, GraphQLTaggedNode, Network, @@ -340,13 +341,22 @@ export default class Atmosphere extends Environment { fetchQuery = async ( taggedNode: GraphQLTaggedNode, - variables: Variables = {} + variables: Variables = {}, + cacheConfig?: { + networkCacheConfig?: CacheConfig + fetchPolicy?: FetchQueryFetchPolicy + } ) => { let res: T['response'] try { - res = await fetchQuery(this, taggedNode, variables, { - fetchPolicy: 'store-or-network' - }).toPromise() + res = await fetchQuery( + this, + taggedNode, + variables, + cacheConfig ?? { + fetchPolicy: 'store-or-network' + } + ).toPromise() } catch (e) { return null } diff --git a/packages/client/components/EmailPasswordAuthForm.tsx b/packages/client/components/EmailPasswordAuthForm.tsx index 7c64424bff9..65c2a7d1728 100644 --- a/packages/client/components/EmailPasswordAuthForm.tsx +++ b/packages/client/components/EmailPasswordAuthForm.tsx @@ -97,8 +97,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const signInWithSSOOnly = isSSOAuthEnabled && !isInternalAuthEnabled const [isSSO, setIsSSO] = useState(isSSODefault || signInWithSSOOnly) const [pendingDomain, setPendingDomain] = useState('') - const [ssoURL, setSSOURL] = useState('') - const [ssoDomain, setSSODomain] = useState('') + const [ssoDomain, setSSODomain] = useState() const {submitMutation, onCompleted, submitting, error, onError} = useMutationProps() const atmosphere = useAtmosphere() const {history} = useRouter() @@ -131,9 +130,10 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const domain = getSSODomainFromEmail(email) if (domain && domain !== pendingDomain) { setPendingDomain(domain) + // Fetch the url to verify SSO is configured for this domain. + // Don't cache it as we need a fresh one for login const url = await getSSOUrl(atmosphere, email) - setSSODomain(domain) - setSSOURL(url || '') + setSSODomain(url ? domain : undefined) } } } @@ -148,8 +148,8 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const tryLoginWithSSO = async (email: string) => { const domain = getSSODomainFromEmail(email)! - const validSSOURL = domain === ssoDomain && ssoURL - const isProbablySSO = isSSO || !fields.password.value || validSSOURL + const hadValidSSOURL = domain === ssoDomain + const isProbablySSO = isSSO || !fields.password.value || hadValidSSOURL const top = getOffsetTop?.() || 56 let optimisticPopup if (isProbablySSO) { @@ -168,7 +168,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { getOAuthPopupFeatures({width: 385, height: 576, top}) ) } - const url = validSSOURL || (await getSSOUrl(atmosphere, email)) + const url = await getSSOUrl(atmosphere, email) if (!url) { optimisticPopup?.close() return false diff --git a/packages/client/package.json b/packages/client/package.json index 55c2cb37724..2b99089aab2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "8.12.2", + "version": "8.12.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/client/utils/getSAMLIdP.ts b/packages/client/utils/getSAMLIdP.ts index 6d3924453c3..aed822d7dda 100644 --- a/packages/client/utils/getSAMLIdP.ts +++ b/packages/client/utils/getSAMLIdP.ts @@ -9,7 +9,9 @@ const query = graphql` ` const getSAMLIdP = async (atmosphere: Atmosphere, variables: getSAMLIdPQuery['variables']) => { - const res = await atmosphere.fetchQuery(query, variables) + const res = await atmosphere.fetchQuery(query, variables, { + fetchPolicy: 'network-only' + }) return res?.SAMLIdP ?? null } diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 00cb977628d..500079705ad 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "8.12.2", + "version": "8.12.3", "description": "A service that computes embedding vectors from Parabol objects", "author": "Jordan Husney ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/embedder#readme", diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index eec413db524..77af9d687d5 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "8.12.2", + "version": "8.12.3", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -25,8 +25,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "8.12.2", - "parabol-server": "8.12.2", + "parabol-client": "8.12.3", + "parabol-server": "8.12.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 791f0278300..1f792843cd1 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -2,7 +2,7 @@ "name": "integration-tests", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "8.12.2", + "version": "8.12.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts index 86d5fcc0a4b..a1148d6669f 100644 --- a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts +++ b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts @@ -38,7 +38,7 @@ const resolveDowngradeToStarter = async ( .execute(), pg .updateTable('SAML') - .set({metadata: null, url: null, lastUpdatedBy: user.id}) + .set({metadata: null, metadataURL: null, lastUpdatedBy: user.id}) .where('orgId', '=', orgId) .execute(), updateTeamByOrgId( diff --git a/packages/server/graphql/private/mutations/loginSAML.ts b/packages/server/graphql/private/mutations/loginSAML.ts index 9e861aa4f98..a5bdb5bfbdd 100644 --- a/packages/server/graphql/private/mutations/loginSAML.ts +++ b/packages/server/graphql/private/mutations/loginSAML.ts @@ -115,13 +115,14 @@ const loginSAML: MutationResolvers['loginSAML'] = async ( if (newMetadata) { // The user is updating their SAML metadata // Revalidate it & persist to DB + // Generate the URL to verify the metadata, don't persist it as it needs to be generated fresh const url = getSignOnURL(metadata, normalizedName) if (url instanceof Error) { return standardError(url) } await pg .updateTable('SAML') - .set({metadata: newMetadata, metadataURL: newMetadataURL, url}) + .set({metadata: newMetadata, metadataURL: newMetadataURL}) .where('id', '=', normalizedName) .execute() } diff --git a/packages/server/package.json b/packages/server/package.json index ed8008c3aa8..f5bfaca3d18 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -3,7 +3,7 @@ "description": "An open-source app for building smarter, more agile teams.", "author": "Parabol Inc. (http://github.com/ParabolInc)", "license": "AGPL-3.0", - "version": "8.12.2", + "version": "8.12.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -131,7 +131,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "8.12.2", + "parabol-client": "8.12.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/packages/server/postgres/migrations/2024-12-12T08:40:19.695Z_removeURLFromSAML.ts b/packages/server/postgres/migrations/2024-12-12T08:40:19.695Z_removeURLFromSAML.ts new file mode 100644 index 00000000000..8a8cd5fc15b --- /dev/null +++ b/packages/server/postgres/migrations/2024-12-12T08:40:19.695Z_removeURLFromSAML.ts @@ -0,0 +1,9 @@ +import type {Kysely} from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema.alterTable('SAML').dropColumn('url').execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable('SAML').addColumn('url', 'varchar(2056)').execute() +} diff --git a/packages/server/utils/getSAMLURLFromEmail.ts b/packages/server/utils/getSAMLURLFromEmail.ts index feb6d9a3075..ea16992fb18 100644 --- a/packages/server/utils/getSAMLURLFromEmail.ts +++ b/packages/server/utils/getSAMLURLFromEmail.ts @@ -2,6 +2,7 @@ import base64url from 'base64url' import getSSODomainFromEmail from 'parabol-client/utils/getSSODomainFromEmail' import {URL} from 'url' import {DataLoaderWorker} from '../graphql/graphql' +import getSignOnURL from '../graphql/public/mutations/helpers/SAMLHelpers/getSignOnURL' import getKysely from '../postgres/getKysely' export const isSingleTenantSSO = @@ -26,14 +27,17 @@ const getSAMLURLFromEmail = async ( if (isSingleTenantSSO) { // For PPMI use const pg = getKysely() - const instanceURLres = await pg + const instanceRes = await pg .selectFrom('SAML') - .select('url') - .where('url', 'is not', null) + .select(['id', 'metadata']) + .where('metadata', 'is not', null) .limit(1) .executeTakeFirst() - const instanceURL = instanceURLres?.url - if (!instanceURL) return null + if (!instanceRes) return null + const {id, metadata} = instanceRes + if (!metadata) return null + const instanceURL = getSignOnURL(metadata, id) + if (instanceURL instanceof Error) return null return urlWithRelayState(instanceURL, isInvited) } if (!email) return null @@ -42,8 +46,10 @@ const getSAMLURLFromEmail = async ( const saml = await dataLoader.get('samlByDomain').load(domainName) if (!saml) return null - const {url} = saml - if (!url) return null + const {id, metadata} = saml + if (!metadata) return null + const url = getSignOnURL(metadata, id) + if (url instanceof Error) return null return urlWithRelayState(url, isInvited) }