mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
chore(core): guard idp-initiated sso console and api (#6690)
* chore(core): guard idp-initiated sso management api to cloud only guard idp-initiated sso management api to cloud only * fix(test): enable routes for integration tests enable routes for integration tests * feat(core, console): apply idp-initiated sso quota guard apply idp-initiate sso quota guard
This commit is contained in:
parent
c7f326edce
commit
14e65924f2
12 changed files with 287 additions and 268 deletions
|
@ -52,7 +52,7 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@logto/cloud": "0.2.5-1661979",
|
"@logto/cloud": "0.2.5-5e334eb",
|
||||||
"@silverhand/eslint-config": "6.0.1",
|
"@silverhand/eslint-config": "6.0.1",
|
||||||
"@silverhand/ts-config": "6.0.0",
|
"@silverhand/ts-config": "6.0.0",
|
||||||
"@types/node": "^20.11.20",
|
"@types/node": "^20.11.20",
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/roboto-mono": "^5.0.0",
|
"@fontsource/roboto-mono": "^5.0.0",
|
||||||
"@jest/types": "^29.5.0",
|
"@jest/types": "^29.5.0",
|
||||||
"@logto/cloud": "0.2.5-6654b82",
|
"@logto/cloud": "0.2.5-5e334eb",
|
||||||
"@logto/connector-kit": "workspace:^4.0.0",
|
"@logto/connector-kit": "workspace:^4.0.0",
|
||||||
"@logto/core-kit": "workspace:^2.5.0",
|
"@logto/core-kit": "workspace:^2.5.0",
|
||||||
"@logto/elements": "workspace:^0.0.1",
|
"@logto/elements": "workspace:^0.0.1",
|
||||||
|
|
|
@ -28,6 +28,7 @@ export const skuQuotaItemPhrasesMap: Record<
|
||||||
customJwtEnabled: 'custom_jwt_enabled.name',
|
customJwtEnabled: 'custom_jwt_enabled.name',
|
||||||
subjectTokenEnabled: 'impersonation_enabled.name',
|
subjectTokenEnabled: 'impersonation_enabled.name',
|
||||||
bringYourUiEnabled: 'bring_your_ui_enabled.name',
|
bringYourUiEnabled: 'bring_your_ui_enabled.name',
|
||||||
|
idpInitiatedSsoEnabled: 'idp_initiated_sso_enabled.name',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const skuQuotaItemUnlimitedPhrasesMap: Record<
|
export const skuQuotaItemUnlimitedPhrasesMap: Record<
|
||||||
|
@ -55,6 +56,7 @@ export const skuQuotaItemUnlimitedPhrasesMap: Record<
|
||||||
customJwtEnabled: 'custom_jwt_enabled.unlimited',
|
customJwtEnabled: 'custom_jwt_enabled.unlimited',
|
||||||
subjectTokenEnabled: 'impersonation_enabled.unlimited',
|
subjectTokenEnabled: 'impersonation_enabled.unlimited',
|
||||||
bringYourUiEnabled: 'bring_your_ui_enabled.unlimited',
|
bringYourUiEnabled: 'bring_your_ui_enabled.unlimited',
|
||||||
|
idpInitiatedSsoEnabled: 'idp_initiated_sso_enabled.unlimited',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const skuQuotaItemLimitedPhrasesMap: Record<
|
export const skuQuotaItemLimitedPhrasesMap: Record<
|
||||||
|
@ -82,6 +84,7 @@ export const skuQuotaItemLimitedPhrasesMap: Record<
|
||||||
customJwtEnabled: 'custom_jwt_enabled.limited',
|
customJwtEnabled: 'custom_jwt_enabled.limited',
|
||||||
subjectTokenEnabled: 'impersonation_enabled.limited',
|
subjectTokenEnabled: 'impersonation_enabled.limited',
|
||||||
bringYourUiEnabled: 'bring_your_ui_enabled.limited',
|
bringYourUiEnabled: 'bring_your_ui_enabled.limited',
|
||||||
|
idpInitiatedSsoEnabled: 'idp_initiated_sso_enabled.limited',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const skuQuotaItemNotEligiblePhrasesMap: Record<
|
export const skuQuotaItemNotEligiblePhrasesMap: Record<
|
||||||
|
@ -109,5 +112,6 @@ export const skuQuotaItemNotEligiblePhrasesMap: Record<
|
||||||
customJwtEnabled: 'custom_jwt_enabled.not_eligible',
|
customJwtEnabled: 'custom_jwt_enabled.not_eligible',
|
||||||
subjectTokenEnabled: 'impersonation_enabled.not_eligible',
|
subjectTokenEnabled: 'impersonation_enabled.not_eligible',
|
||||||
bringYourUiEnabled: 'bring_your_ui_enabled.not_eligible',
|
bringYourUiEnabled: 'bring_your_ui_enabled.not_eligible',
|
||||||
|
idpInitiatedSsoEnabled: 'idp_initiated_sso_enabled.not_eligible',
|
||||||
};
|
};
|
||||||
/* === for new pricing model === */
|
/* === for new pricing model === */
|
||||||
|
|
|
@ -58,6 +58,8 @@ export const defaultLogtoSku: LogtoSkuResponse = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
type: LogtoSkuType.Basic,
|
type: LogtoSkuType.Basic,
|
||||||
unitPrice: 0,
|
unitPrice: 0,
|
||||||
|
productId: null,
|
||||||
|
defaultPriceId: null,
|
||||||
quota: {
|
quota: {
|
||||||
// A soft limit for abuse monitoring
|
// A soft limit for abuse monitoring
|
||||||
mauLimit: 100,
|
mauLimit: 100,
|
||||||
|
@ -80,6 +82,7 @@ export const defaultLogtoSku: LogtoSkuResponse = {
|
||||||
customJwtEnabled: true,
|
customJwtEnabled: true,
|
||||||
subjectTokenEnabled: true,
|
subjectTokenEnabled: true,
|
||||||
bringYourUiEnabled: true,
|
bringYourUiEnabled: true,
|
||||||
|
idpInitiatedSsoEnabled: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,6 +108,7 @@ export const defaultSubscriptionQuota: NewSubscriptionQuota = {
|
||||||
customJwtEnabled: false,
|
customJwtEnabled: false,
|
||||||
subjectTokenEnabled: false,
|
subjectTokenEnabled: false,
|
||||||
bringYourUiEnabled: false,
|
bringYourUiEnabled: false,
|
||||||
|
idpInitiatedSsoEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultSubscriptionUsage: NewSubscriptionCountBasedUsage = {
|
export const defaultSubscriptionUsage: NewSubscriptionCountBasedUsage = {
|
||||||
|
@ -125,6 +129,7 @@ export const defaultSubscriptionUsage: NewSubscriptionCountBasedUsage = {
|
||||||
customJwtEnabled: false,
|
customJwtEnabled: false,
|
||||||
subjectTokenEnabled: false,
|
subjectTokenEnabled: false,
|
||||||
bringYourUiEnabled: false,
|
bringYourUiEnabled: false,
|
||||||
|
idpInitiatedSsoEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAdminTenantEndpoint = () => {
|
const getAdminTenantEndpoint = () => {
|
||||||
|
|
|
@ -67,12 +67,11 @@ function EnterpriseSsoDetails() {
|
||||||
|
|
||||||
const isDarkModeEnabled = signInExperience?.color.isDarkModeEnabled ?? false;
|
const isDarkModeEnabled = signInExperience?.color.isDarkModeEnabled ?? false;
|
||||||
|
|
||||||
const isIdpInitiatedAuthEnabled = useMemo(
|
const isIdpInitiatedAuthConfigEnabled = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isCloud &&
|
isCloud &&
|
||||||
ssoConnector?.providerType === SsoProviderType.SAML &&
|
ssoConnector?.providerType === SsoProviderType.SAML &&
|
||||||
// TODO: @simeng: Replace this with new IdP-initiated auth quota guard
|
currentSubscriptionQuota.idpInitiatedSsoEnabled,
|
||||||
Boolean(currentSubscriptionQuota.enterpriseSsoLimit),
|
|
||||||
[ssoConnector, currentSubscriptionQuota]
|
[ssoConnector, currentSubscriptionQuota]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -153,7 +152,7 @@ function EnterpriseSsoDetails() {
|
||||||
>
|
>
|
||||||
<DynamicT forKey="enterprise_sso_details.tab_experience" />
|
<DynamicT forKey="enterprise_sso_details.tab_experience" />
|
||||||
</TabNavItem>
|
</TabNavItem>
|
||||||
{isIdpInitiatedAuthEnabled && (
|
{isIdpInitiatedAuthConfigEnabled && (
|
||||||
<TabNavItem
|
<TabNavItem
|
||||||
href={getSsoConnectorDetailsPathname(
|
href={getSsoConnectorDetailsPathname(
|
||||||
ssoConnectorId,
|
ssoConnectorId,
|
||||||
|
@ -183,7 +182,7 @@ function EnterpriseSsoDetails() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isIdpInitiatedAuthEnabled && tab === EnterpriseSsoDetailsTabs.IdpInitiatedAuth && (
|
{isIdpInitiatedAuthConfigEnabled && tab === EnterpriseSsoDetailsTabs.IdpInitiatedAuth && (
|
||||||
<IdpInitiatedAuth ssoConnector={ssoConnector} />
|
<IdpInitiatedAuth ssoConnector={ssoConnector} />
|
||||||
)}
|
)}
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@logto/cloud": "0.2.5-1661979",
|
"@logto/cloud": "0.2.5-5e334eb",
|
||||||
"@silverhand/eslint-config": "6.0.1",
|
"@silverhand/eslint-config": "6.0.1",
|
||||||
"@silverhand/ts-config": "6.0.0",
|
"@silverhand/ts-config": "6.0.0",
|
||||||
"@types/adm-zip": "^0.5.5",
|
"@types/adm-zip": "^0.5.5",
|
||||||
|
|
|
@ -205,8 +205,7 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
||||||
// All the rest of the request body will be validated and parsed by the connector.
|
// All the rest of the request body will be validated and parsed by the connector.
|
||||||
const { RelayState: jti } = body;
|
const { RelayState: jti } = body;
|
||||||
|
|
||||||
// IdP initiated SSO will not have the jti in the RelayState.
|
// IdP initiated SSO does not provide the RelayState, we need to check if the IdP initiated SSO flow is enabled.
|
||||||
// Trigger the IdP initiated SSO flow if enabled for the current connector.
|
|
||||||
if (!jti && EnvSet.values.isDevFeaturesEnabled) {
|
if (!jti && EnvSet.values.isDevFeaturesEnabled) {
|
||||||
const idpInitiatedAuthConfig =
|
const idpInitiatedAuthConfig =
|
||||||
await queries.ssoConnectors.getIdpInitiatedAuthConfigByConnectorId(connectorId);
|
await queries.ssoConnectors.getIdpInitiatedAuthConfigByConnectorId(connectorId);
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
{
|
{
|
||||||
"name": "Dev feature"
|
"name": "Dev feature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cloud only"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import koaGuard from '#src/middleware/koa-guard.js';
|
||||||
import { ssoConnectorFactories } from '#src/sso/index.js';
|
import { ssoConnectorFactories } from '#src/sso/index.js';
|
||||||
import { tableToPathname } from '#src/utils/SchemaRouter.js';
|
import { tableToPathname } from '#src/utils/SchemaRouter.js';
|
||||||
|
|
||||||
|
import { koaQuotaGuard } from '../../middleware/koa-quota-guard.js';
|
||||||
import assertThat from '../../utils/assert-that.js';
|
import assertThat from '../../utils/assert-that.js';
|
||||||
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
|
import { type ManagementApiRouter, type RouterInitArgs } from '../types.js';
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ export default function ssoConnectorIdpInitiatedAuthConfigRoutes<T extends Manag
|
||||||
queries,
|
queries,
|
||||||
libraries: {
|
libraries: {
|
||||||
ssoConnectors: { getSsoConnectorById, createSsoConnectorIdpInitiatedAuthConfig },
|
ssoConnectors: { getSsoConnectorById, createSsoConnectorIdpInitiatedAuthConfig },
|
||||||
|
quota,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
] = args;
|
] = args;
|
||||||
|
@ -32,6 +34,7 @@ export default function ssoConnectorIdpInitiatedAuthConfigRoutes<T extends Manag
|
||||||
|
|
||||||
router.put(
|
router.put(
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
|
koaQuotaGuard({ key: 'idpInitiatedSsoEnabled', quota }),
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: ssoConnectorIdpInitiatedAuthConfigCreateGuard,
|
body: ssoConnectorIdpInitiatedAuthConfigCreateGuard,
|
||||||
params: z.object({ id: z.string().min(1) }),
|
params: z.object({ id: z.string().min(1) }),
|
||||||
|
|
|
@ -303,7 +303,10 @@ export default function singleSignOnConnectorsRoutes<T extends ManagementApiRout
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (EnvSet.values.isDevFeaturesEnabled) {
|
if (
|
||||||
|
EnvSet.values.isDevFeaturesEnabled &&
|
||||||
|
(EnvSet.values.isCloud || EnvSet.values.isIntegrationTest)
|
||||||
|
) {
|
||||||
ssoConnectorIdpInitiatedAuthConfigRoutes(...args);
|
ssoConnectorIdpInitiatedAuthConfigRoutes(...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,12 @@ const quota_item = {
|
||||||
unlimited: 'Bring your UI',
|
unlimited: 'Bring your UI',
|
||||||
not_eligible: 'Remove your custom UI assets',
|
not_eligible: 'Remove your custom UI assets',
|
||||||
},
|
},
|
||||||
|
idp_initiated_sso_enabled: {
|
||||||
|
name: 'IDP-initiated SSO',
|
||||||
|
limited: 'IDP-initiated SSO',
|
||||||
|
unlimited: 'IDP-initiated SSO',
|
||||||
|
not_eligible: 'IDP-initiated SSO not allowed',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(quota_item);
|
export default Object.freeze(quota_item);
|
||||||
|
|
511
pnpm-lock.yaml
511
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue