mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core,phrases): add api to receive and handle saml sso assertion
This commit is contained in:
parent
f8450a50ae
commit
3b63203b49
31 changed files with 176 additions and 1 deletions
|
@ -6,6 +6,8 @@ import { z } from 'zod';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { verifyBearerTokenFromRequest } from '#src/middleware/koa-auth/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import { ssoConnectorFactories } from '#src/sso/index.js';
|
||||
import { SsoProviderName } from '#src/sso/types/index.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import {
|
||||
getConnectorSessionResultFromJti,
|
||||
|
@ -19,11 +21,12 @@ import type { AnonymousRouter, RouterInitArgs } from './types.js';
|
|||
* This router will have a route `/authn` to authenticate tokens with a general manner.
|
||||
*/
|
||||
export default function authnRoutes<T extends AnonymousRouter>(
|
||||
...[router, { envSet, provider, libraries }]: RouterInitArgs<T>
|
||||
...[router, { id: tenantId, envSet, provider, libraries }]: RouterInitArgs<T>
|
||||
) {
|
||||
const {
|
||||
users: { findUserRoles },
|
||||
socials: { getConnector },
|
||||
ssoConnector: { getSsoConnectorById },
|
||||
} = libraries;
|
||||
|
||||
const hasuraResponseGuard = z.object({
|
||||
|
@ -144,4 +147,57 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: refactor this, this SAML API for SSO is quite similar to the one for normal social sign-in, most of the logics can be reused.
|
||||
router.post(
|
||||
'/authn/saml/sso/:ssoConnectorId',
|
||||
/**
|
||||
* The API does not care the type of the SAML assertion request body, simply pass this to
|
||||
* SSO connector's built-in methods.
|
||||
*/
|
||||
koaGuard({
|
||||
body: jsonObjectGuard,
|
||||
params: z.object({ ssoConnectorId: z.string().min(1) }),
|
||||
status: 302,
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { ssoConnectorId },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
const ssoConnector = await getSsoConnectorById(ssoConnectorId);
|
||||
|
||||
const samlAssertionGuard = z.object({ SAMLResponse: z.string(), RelayState: z.string() });
|
||||
const samlAssertionParseResult = samlAssertionGuard.safeParse(body);
|
||||
|
||||
if (!samlAssertionParseResult.success) {
|
||||
throw new ConnectorError(
|
||||
ConnectorErrorCodes.InvalidResponse,
|
||||
samlAssertionParseResult.error
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since `RelayState` will be returned with value unchanged, we use it to pass `jti`
|
||||
* to find the connector session we used to store essential information.
|
||||
*/
|
||||
const { RelayState: jti } = samlAssertionParseResult.data;
|
||||
|
||||
const getSession = async () => getConnectorSessionResultFromJti(jti, provider);
|
||||
const setSession = async (connectorSession: ConnectorSession) =>
|
||||
assignConnectorSessionResultViaJti(jti, provider, connectorSession);
|
||||
|
||||
if (ssoConnector.providerName !== SsoProviderName.SAML) {
|
||||
throw new RequestError({ code: 'sso_connector.saml_only' });
|
||||
}
|
||||
|
||||
const { constructor } = ssoConnectorFactories[ssoConnector.providerName];
|
||||
const { validateSamlAssertion } = new constructor(ssoConnector, tenantId);
|
||||
const redirectTo = await validateSamlAssertion({ body }, getSession, setSession);
|
||||
|
||||
ctx.redirect(redirectTo);
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/de/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/de/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
5
packages/phrases/src/locales/en/errors/sso-connector.ts
Normal file
5
packages/phrases/src/locales/en/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
const sso_connector = {
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/es/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/es/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/fr/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/fr/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/it/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/it/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/ja/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/ja/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/ko/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/ko/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
6
packages/phrases/src/locales/ru/errors/sso-connector.ts
Normal file
6
packages/phrases/src/locales/ru/errors/sso-connector.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
|
@ -16,6 +16,7 @@ import role from './role.js';
|
|||
import scope from './scope.js';
|
||||
import session from './session.js';
|
||||
import sign_in_experiences from './sign-in-experiences.js';
|
||||
import sso_connector from './sso-connector.js';
|
||||
import storage from './storage.js';
|
||||
import subscription from './subscription.js';
|
||||
import swagger from './swagger.js';
|
||||
|
@ -46,6 +47,7 @@ const errors = {
|
|||
subscription,
|
||||
application,
|
||||
organization,
|
||||
sso_connector,
|
||||
};
|
||||
|
||||
export default Object.freeze(errors);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
const sso_connector = {
|
||||
/** UNTRANSLATED */
|
||||
saml_only: 'The endpoint only applies to SAML SSO connectors.',
|
||||
};
|
||||
|
||||
export default Object.freeze(sso_connector);
|
Loading…
Reference in a new issue