0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-27 21:39:16 -05:00
logto/packages/connectors/connector-saml/src/utils.ts
Gao Sun 6b322a537c
refactor: add connector packages
the initial commit to move all connector packages to the main
repo.
2023-04-01 15:53:14 +08:00

111 lines
3 KiB
TypeScript

import type { SetSession } from '@logto/connector-kit';
import { ConnectorError, ConnectorErrorCodes, socialUserInfoGuard } from '@logto/connector-kit';
import { XMLValidator } from 'fast-xml-parser';
import * as saml from 'samlify';
import type { ESamlHttpRequest, ProfileMap, SamlConfig } from './types.js';
export const userProfileMapping = (
originUserProfile: Record<string, unknown>,
keyMapping: ProfileMap
) => {
const keyMap = new Map(
Object.entries(keyMapping).map(([destination, source]) => [source, destination])
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mappedUserProfile = Object.fromEntries(
Object.entries(originUserProfile)
.filter(([key, value]) => keyMap.get(key) && value)
.map(([key, value]) => [keyMap.get(key), value])
);
const result = socialUserInfoGuard.safeParse(mappedUserProfile);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, result.error);
}
return result.data;
};
export const getUserInfoFromRawUserProfile = (
rawUserProfile: Record<string, unknown>,
keyMapping: ProfileMap
) => {
const userProfile = userProfileMapping(rawUserProfile, keyMapping);
const result = socialUserInfoGuard.safeParse(userProfile);
if (!result.success) {
throw new ConnectorError(ConnectorErrorCodes.General, result.error);
}
return result.data;
};
export const samlAssertionHandler = async (
request: ESamlHttpRequest,
options: SamlConfig,
setSession: SetSession
): Promise<void | Record<string, unknown>> => {
const {
entityID,
x509Certificate,
idpMetadataXml,
encryptAssertion,
encPrivateKey,
encPrivateKeyPass,
messageSigningOrder,
timeout,
} = options;
// eslint-disable-next-line new-cap
const identityProvider = saml.IdentityProvider({
metadata: idpMetadataXml,
messageSigningOrder,
isAssertionEncrypted: encryptAssertion,
});
// eslint-disable-next-line new-cap
const serviceProvider = saml.ServiceProvider({
entityID,
signingCert: x509Certificate,
isAssertionEncrypted: encryptAssertion,
encPrivateKey,
encPrivateKeyPass,
clockDrifts: [-timeout, timeout],
});
// Used to check whether xml content is valid in format.
saml.setSchemaValidator({
validate: async (xmlContent: string) => {
try {
XMLValidator.validate(xmlContent, {
allowBooleanAttributes: true,
});
return true;
} catch {
return false;
}
},
});
try {
const assertionResult = await serviceProvider.parseLoginResponse(
identityProvider,
'post',
request
);
await setSession({
extractedRawProfile: {
...(Boolean(assertionResult.extract.nameID) && {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
id: assertionResult.extract.nameID,
}),
...assertionResult.extract.attributes,
},
});
} catch (error: unknown) {
throw new ConnectorError(ConnectorErrorCodes.General, String(error));
}
};