mirror of https://github.com/boxyhq/jackson.git
166 lines
5.0 KiB
TypeScript
166 lines
5.0 KiB
TypeScript
import crypto from 'crypto';
|
|
import { IConnectionAPIController, OIDCSSOConnection, OIDCSSORecord, Storable } from '../../typings';
|
|
import * as dbutils from '../../db/utils';
|
|
import {
|
|
extractHostName,
|
|
extractRedirectUrls,
|
|
IndexNames,
|
|
validateSSOConnection,
|
|
validateRedirectUrl,
|
|
} from '../utils';
|
|
import { JacksonError } from '../error';
|
|
|
|
const oidc = {
|
|
create: async (body: OIDCSSOConnection, connectionStore: Storable) => {
|
|
const {
|
|
defaultRedirectUrl,
|
|
redirectUrl,
|
|
tenant,
|
|
product,
|
|
name,
|
|
description,
|
|
oidcDiscoveryUrl = '',
|
|
oidcClientId = '',
|
|
oidcClientSecret = '',
|
|
} = body;
|
|
|
|
let connectionClientSecret: string;
|
|
|
|
validateSSOConnection(body, 'oidc');
|
|
|
|
const redirectUrlList = extractRedirectUrls(redirectUrl);
|
|
|
|
validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
|
|
|
|
const record: Partial<OIDCSSORecord> = {
|
|
defaultRedirectUrl,
|
|
redirectUrl: redirectUrlList,
|
|
tenant,
|
|
product,
|
|
name,
|
|
description,
|
|
clientID: '',
|
|
clientSecret: '',
|
|
};
|
|
|
|
// from OpenID Provider
|
|
record.oidcProvider = {
|
|
discoveryUrl: oidcDiscoveryUrl,
|
|
clientId: oidcClientId,
|
|
clientSecret: oidcClientSecret,
|
|
};
|
|
|
|
// extract provider
|
|
const providerName = extractHostName(oidcDiscoveryUrl);
|
|
record.oidcProvider.provider = providerName ? providerName : 'Unknown';
|
|
|
|
// Use the clientId from the OpenID Provider to generate the clientID hash for the connection
|
|
record.clientID = dbutils.keyDigest(dbutils.keyFromParts(tenant, product, oidcClientId));
|
|
|
|
const exists = await connectionStore.get(record.clientID);
|
|
|
|
if (exists) {
|
|
connectionClientSecret = exists.clientSecret;
|
|
} else {
|
|
connectionClientSecret = crypto.randomBytes(24).toString('hex');
|
|
}
|
|
|
|
record.clientSecret = connectionClientSecret;
|
|
|
|
await connectionStore.put(record.clientID, record, {
|
|
// secondary index on tenant + product
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(tenant, product),
|
|
});
|
|
|
|
return record as OIDCSSORecord;
|
|
},
|
|
|
|
update: async (
|
|
body: OIDCSSOConnection & { clientID: string; clientSecret: string },
|
|
connectionStore: Storable,
|
|
connectionsGetter: IConnectionAPIController['getConnections']
|
|
) => {
|
|
const {
|
|
defaultRedirectUrl,
|
|
redirectUrl,
|
|
name,
|
|
description,
|
|
oidcDiscoveryUrl,
|
|
oidcClientId,
|
|
oidcClientSecret,
|
|
...clientInfo
|
|
} = body;
|
|
|
|
if (!clientInfo?.clientID) {
|
|
throw new JacksonError('Please provide clientID', 400);
|
|
}
|
|
|
|
if (!clientInfo?.clientSecret) {
|
|
throw new JacksonError('Please provide clientSecret', 400);
|
|
}
|
|
|
|
if (!clientInfo?.tenant) {
|
|
throw new JacksonError('Please provide tenant', 400);
|
|
}
|
|
|
|
if (!clientInfo?.product) {
|
|
throw new JacksonError('Please provide product', 400);
|
|
}
|
|
|
|
if (description && description.length > 100) {
|
|
throw new JacksonError('Description should not exceed 100 characters', 400);
|
|
}
|
|
|
|
const redirectUrlList = redirectUrl ? extractRedirectUrls(redirectUrl) : null;
|
|
validateRedirectUrl({ defaultRedirectUrl, redirectUrlList });
|
|
|
|
const _savedConnection = (await connectionsGetter(clientInfo))[0] as OIDCSSORecord;
|
|
|
|
if (_savedConnection.clientSecret !== clientInfo?.clientSecret) {
|
|
throw new JacksonError('clientSecret mismatch', 400);
|
|
}
|
|
|
|
let oidcProvider;
|
|
if (_savedConnection && typeof _savedConnection.oidcProvider === 'object') {
|
|
oidcProvider = { ..._savedConnection.oidcProvider };
|
|
|
|
if (oidcClientId && typeof oidcClientId === 'string') {
|
|
const clientID = dbutils.keyDigest(
|
|
dbutils.keyFromParts(clientInfo.tenant, clientInfo.product, oidcClientId)
|
|
);
|
|
if (clientID !== clientInfo?.clientID) {
|
|
throw new JacksonError('Tenant/Product config mismatch with OIDC Provider metadata', 400);
|
|
}
|
|
}
|
|
|
|
if (oidcClientSecret && typeof oidcClientSecret === 'string') {
|
|
oidcProvider.clientSecret = oidcClientSecret;
|
|
}
|
|
|
|
if (oidcDiscoveryUrl && typeof oidcDiscoveryUrl === 'string') {
|
|
oidcProvider.discoveryUrl = oidcDiscoveryUrl;
|
|
const providerName = extractHostName(oidcDiscoveryUrl);
|
|
oidcProvider.provider = providerName ? providerName : 'Unknown';
|
|
}
|
|
}
|
|
|
|
const record = {
|
|
..._savedConnection,
|
|
name: name ? name : _savedConnection.name,
|
|
description: description ? description : _savedConnection.description,
|
|
defaultRedirectUrl: defaultRedirectUrl ? defaultRedirectUrl : _savedConnection.defaultRedirectUrl,
|
|
redirectUrl: redirectUrlList ? redirectUrlList : _savedConnection.redirectUrl,
|
|
oidcProvider: oidcProvider ? oidcProvider : _savedConnection.oidcProvider,
|
|
};
|
|
|
|
await connectionStore.put(clientInfo?.clientID, record, {
|
|
// secondary index on tenant + product
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(_savedConnection.tenant, _savedConnection.product),
|
|
});
|
|
},
|
|
};
|
|
|
|
export default oidc;
|