Skip to content

Commit

Permalink
feat: enable SAML app support for OSS version
Browse files Browse the repository at this point in the history
  • Loading branch information
darcyYe committed Feb 5, 2025
1 parent c25e435 commit 17cc461
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 20 deletions.
16 changes: 16 additions & 0 deletions .changeset/silent-moons-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@logto/console": minor
"@logto/phrases": minor
"@logto/core": minor
---

add support on SAML applications

Logto now supports acting as a SAML identity provider (IdP), enabling enterprise users to achieve secure Single Sign-On (SSO) through the standardized SAML protocol. Key features include:

- Full support for SAML 2.0 protocol
- Flexible attribute mapping configuration
- Metadata auto-configuration support
- Enterprise-grade encryption and signing

[View full documentation](https://docs.logto.io/integrate-logto/saml-app) for more details.
1 change: 0 additions & 1 deletion packages/console/src/assets/docs/guides/saml-idp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const metadata = Object.freeze({
target: ApplicationType.SAML,
isThirdParty: false,
skipGuideAfterCreation: true,
isCloud: true,
} satisfies GuideMetadata);

export default metadata;
7 changes: 1 addition & 6 deletions packages/console/src/components/Guide/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,7 @@ export const useAppGuideMetadata = (): {
.filter(
({ metadata: { target, isCloud, isDevFeature } }) =>
target !== 'API' && (isCloudEnv || !isCloud) && (isDevFeaturesEnabled || !isDevFeature)
/**
* Show SAML guides when it is:
* 1. Cloud env
*/
)
.filter(({ metadata: { target } }) => target !== ApplicationType.SAML || isCloudEnv),
),
[]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,6 @@ function GuideLibrary({ className, hasCardBorder, hasCardButton, onSelectGuide }
<CheckboxGroup
className={styles.checkboxGroup}
options={allAppGuideCategories
/**
* Show SAML guides when it is:
* 1. Cloud env
*/
.filter((category) => category !== 'SAML' || isCloud)
.filter((category) => isCloud || category !== 'Protected')
.map((category) => ({
title: `guide.categories.${category}`,
Expand Down
8 changes: 2 additions & 6 deletions packages/core/src/routes/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ const createRouters = (tenant: TenantContext) => {
systemRoutes(managementRouter, tenant);
subjectTokenRoutes(managementRouter, tenant);
accountCentersRoutes(managementRouter, tenant);
if (EnvSet.values.isCloud || EnvSet.values.isIntegrationTest) {
samlApplicationRoutes(managementRouter, tenant);
}
samlApplicationRoutes(managementRouter, tenant);

const anonymousRouter: AnonymousRouter = new Router();

Expand All @@ -117,9 +115,7 @@ const createRouters = (tenant: TenantContext) => {
wellKnownRoutes(anonymousRouter, tenant);
statusRoutes(anonymousRouter, tenant);
authnRoutes(anonymousRouter, tenant);
if (EnvSet.values.isCloud || EnvSet.values.isIntegrationTest) {
samlApplicationAnonymousRoutes(anonymousRouter, tenant);
}
samlApplicationAnonymousRoutes(anonymousRouter, tenant);

wellKnownOpenApiRoutes(anonymousRouter, {
experienceRouters: [experienceRouter, interactionRouter],
Expand Down
32 changes: 30 additions & 2 deletions packages/core/src/routes/saml-application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ import { generateInternalSecret } from '#src/routes/applications/application-sec
import type { ManagementApiRouter, RouterInitArgs } from '#src/routes/types.js';
import { getSamlAppCallbackUrl } from '#src/saml-application/SamlApplication/utils.js';
import assertThat from '#src/utils/assert-that.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';

export default function samlApplicationRoutes<T extends ManagementApiRouter>(
...[router, { id: tenantId, queries, libraries }]: RouterInitArgs<T>
) {
const {
applications: { insertApplication, findApplicationById, deleteApplicationById },
applications: {
countApplications,
insertApplication,
findApplicationById,
deleteApplicationById,
},
samlApplicationConfigs: { insertSamlApplicationConfig },
samlApplicationSecrets: {
deleteSamlApplicationSecretById,
Expand All @@ -49,7 +55,29 @@ export default function samlApplicationRoutes<T extends ManagementApiRouter>(

router.post(
'/saml-applications',
koaQuotaGuard({ key: 'samlApplicationsLimit', quota }),
EnvSet.values.isCloud
? koaQuotaGuard({ key: 'samlApplicationsLimit', quota })
: // OSS can create at most 3 SAML apps.
async (ctx, next) => {
const { searchParams } = ctx.URL;
// This will only parse the `search` query param, other params will be ignored. Please use query guard to validate them.
const search = parseSearchParamsForSearch(searchParams);
const { count: samlAppCount } = await countApplications({
search,
types: [ApplicationType.SAML],
});

assertThat(
samlAppCount < 3,
new RequestError({
code: 'application.saml.reach_oss_limit',
status: 403,
limit: 3,
})
);

return next();

Check warning on line 79 in packages/core/src/routes/saml-application/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/saml-application/index.ts#L62-L79

Added lines #L62 - L79 were not covered by tests
},
koaGuard({
body: samlApplicationCreateGuard,
response: samlApplicationResponseGuard,
Expand Down
1 change: 1 addition & 0 deletions packages/phrases/src/locales/en/errors/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const application = {
saml: {
use_saml_app_api: 'Use `[METHOD] /saml-applications(/.*)?` API to operate SAML app.',
saml_application_only: 'The API is only available for SAML applications.',
reach_oss_limit: 'You CAN NOT create more SAML apps since the limit of {{limit}} is hit.',
acs_url_binding_not_supported:
'Only HTTP-POST binding is supported for receiving SAML assertions.',
can_not_delete_active_secret: 'Can not delete the active secret.',
Expand Down

0 comments on commit 17cc461

Please sign in to comment.